1 /* iax2_analysis_dialog.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "iax2_analysis_dialog.h"
11 #include <ui_iax2_analysis_dialog.h>
12
13 #include "file.h"
14 #include "frame_tvbuff.h"
15
16 #include <epan/epan_dissect.h>
17 #include <epan/rtp_pt.h>
18
19 #include <epan/dfilter/dfilter.h>
20
21 #include <epan/dissectors/packet-iax2.h>
22
23 #include "ui/help_url.h"
24 #ifdef IAX2_RTP_STREAM_CHECK
25 #include "ui/rtp_stream.h"
26 #endif
27 #include <wsutil/utf8_entities.h>
28
29 #include <wsutil/g711.h>
30 #include <wsutil/pint.h>
31
32 #include <QMessageBox>
33 #include <QPushButton>
34 #include <QTemporaryFile>
35
36 #include <ui/qt/utils/color_utils.h>
37 #include <ui/qt/utils/qt_ui_utils.h>
38 #include <ui/qt/utils/stock_icon.h>
39 #include "wireshark_application.h"
40 #include "ui/qt/widgets/wireshark_file_dialog.h"
41
42 /*
43 * @file RTP stream analysis dialog
44 *
45 * Displays forward and reverse RTP streams and graphs each stream
46 */
47
48 // To do:
49 // - Progress bar for tapping and saving.
50 // - Add a refresh button and/or action.
51 // - Fixup output file names.
52 // - Add a graph title and legend when saving?
53
54 enum {
55 packet_col_,
56 delta_col_,
57 jitter_col_,
58 bandwidth_col_,
59 status_col_,
60 length_col_
61 };
62
63 static const QRgb color_rtp_warn_ = 0xffdbbf;
64
65 enum { iax2_analysis_type_ = 1000 };
66 class Iax2AnalysisTreeWidgetItem : public QTreeWidgetItem
67 {
68 public:
Iax2AnalysisTreeWidgetItem(QTreeWidget * tree,tap_iax2_stat_t * statinfo,packet_info * pinfo)69 Iax2AnalysisTreeWidgetItem(QTreeWidget *tree, tap_iax2_stat_t *statinfo, packet_info *pinfo) :
70 QTreeWidgetItem(tree, iax2_analysis_type_),
71 frame_num_(pinfo->num),
72 pkt_len_(pinfo->fd->pkt_len),
73 flags_(statinfo->flags),
74 bandwidth_(statinfo->bandwidth),
75 ok_(false)
76 {
77 if (flags_ & STAT_FLAG_FIRST) {
78 delta_ = 0.0;
79 jitter_ = 0.0;
80 } else {
81 delta_ = statinfo->delta;
82 jitter_ = statinfo->jitter;
83 }
84
85 QColor bg_color = QColor();
86 QString status;
87
88 if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
89 status = QObject::tr("Wrong sequence number");
90 bg_color = ColorUtils::expert_color_error;
91 } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
92 status = QObject::tr("Payload changed to PT=%1").arg(statinfo->pt);
93 bg_color = color_rtp_warn_;
94 } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
95 status = QObject::tr("Incorrect timestamp");
96 /* color = COLOR_WARNING; */
97 bg_color = color_rtp_warn_;
98 } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
99 && !(statinfo->flags & STAT_FLAG_FIRST)
100 && !(statinfo->flags & STAT_FLAG_PT_CN)
101 && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
102 && !(statinfo->flags & STAT_FLAG_MARKER)) {
103 status = QObject::tr("Marker missing?");
104 bg_color = color_rtp_warn_;
105 } else {
106 if (statinfo->flags & STAT_FLAG_MARKER) {
107 bg_color = color_rtp_warn_;
108 }
109 }
110
111 if (status.isEmpty()) {
112 ok_ = true;
113 status = UTF8_CHECK_MARK;
114 }
115
116 setText(packet_col_, QString::number(frame_num_));
117 setText(delta_col_, QString::number(delta_, 'f', 2));
118 setText(jitter_col_, QString::number(jitter_, 'f', 2));
119 setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
120 setText(status_col_, status);
121 setText(length_col_, QString::number(pkt_len_));
122
123 setTextAlignment(packet_col_, Qt::AlignRight);
124 setTextAlignment(delta_col_, Qt::AlignRight);
125 setTextAlignment(jitter_col_, Qt::AlignRight);
126 setTextAlignment(bandwidth_col_, Qt::AlignRight);
127 setTextAlignment(length_col_, Qt::AlignRight);
128
129 if (bg_color.isValid()) {
130 for (int col = 0; col < columnCount(); col++) {
131 setBackground(col, bg_color);
132 setForeground(col, ColorUtils::expert_color_foreground);
133 }
134 }
135 }
136
frameNum()137 guint32 frameNum() { return frame_num_; }
frameStatus()138 bool frameStatus() { return ok_; }
139
rowData()140 QList<QVariant> rowData() {
141 QString status_str = ok_ ? "OK" : text(status_col_);
142
143 return QList<QVariant>()
144 << frame_num_ << delta_ << jitter_ << bandwidth_
145 << status_str << pkt_len_;
146 }
147
operator <(const QTreeWidgetItem & other) const148 bool operator< (const QTreeWidgetItem &other) const
149 {
150 if (other.type() != iax2_analysis_type_) return QTreeWidgetItem::operator< (other);
151 const Iax2AnalysisTreeWidgetItem *other_row = static_cast<const Iax2AnalysisTreeWidgetItem *>(&other);
152
153 switch (treeWidget()->sortColumn()) {
154 case (packet_col_):
155 return frame_num_ < other_row->frame_num_;
156 break;
157 case (delta_col_):
158 return delta_ < other_row->delta_;
159 break;
160 case (jitter_col_):
161 return jitter_ < other_row->jitter_;
162 break;
163 case (bandwidth_col_):
164 return bandwidth_ < other_row->bandwidth_;
165 break;
166 case (length_col_):
167 return pkt_len_ < other_row->pkt_len_;
168 break;
169 default:
170 break;
171 }
172
173 // Fall back to string comparison
174 return QTreeWidgetItem::operator <(other);
175 }
176 private:
177 guint32 frame_num_;
178 guint32 pkt_len_;
179 guint32 flags_;
180 double delta_;
181 double jitter_;
182 double bandwidth_;
183 bool ok_;
184 };
185
186 enum {
187 fwd_jitter_graph_,
188 fwd_diff_graph_,
189 rev_jitter_graph_,
190 rev_diff_graph_,
191 num_graphs_
192 };
193
Iax2AnalysisDialog(QWidget & parent,CaptureFile & cf)194 Iax2AnalysisDialog::Iax2AnalysisDialog(QWidget &parent, CaptureFile &cf) :
195 WiresharkDialog(parent, cf),
196 ui(new Ui::Iax2AnalysisDialog),
197 save_payload_error_(TAP_IAX2_NO_ERROR)
198 {
199 ui->setupUi(this);
200 loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
201 setWindowSubtitle(tr("IAX2 Stream Analysis"));
202
203 ui->progressFrame->hide();
204
205 stream_ctx_menu_.addAction(ui->actionGoToPacket);
206 stream_ctx_menu_.addAction(ui->actionNextProblem);
207 stream_ctx_menu_.addSeparator();
208 stream_ctx_menu_.addAction(ui->actionSaveAudio);
209 stream_ctx_menu_.addAction(ui->actionSaveForwardAudio);
210 stream_ctx_menu_.addAction(ui->actionSaveReverseAudio);
211 stream_ctx_menu_.addSeparator();
212 stream_ctx_menu_.addAction(ui->actionSaveCsv);
213 stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
214 stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
215 stream_ctx_menu_.addSeparator();
216 stream_ctx_menu_.addAction(ui->actionSaveGraph);
217 set_action_shortcuts_visible_in_context_menu(stream_ctx_menu_.actions());
218
219 ui->forwardTreeWidget->installEventFilter(this);
220 ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
221 connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
222 SLOT(showStreamMenu(QPoint)));
223 ui->reverseTreeWidget->installEventFilter(this);
224 ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
225 connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
226 SLOT(showStreamMenu(QPoint)));
227 connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
228 this, SLOT(graphClicked(QMouseEvent*)));
229
230 graph_ctx_menu_.addAction(ui->actionSaveGraph);
231
232 QStringList header_labels;
233 for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
234 header_labels << ui->forwardTreeWidget->headerItem()->text(i);
235 }
236 ui->reverseTreeWidget->setHeaderLabels(header_labels);
237
238 memset(&fwd_id_, 0, sizeof(fwd_id_));
239 memset(&rev_id_, 0, sizeof(rev_id_));
240
241 QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
242 << ui->fJitterCheckBox << ui->fDiffCheckBox
243 << ui->rJitterCheckBox << ui->rDiffCheckBox;
244
245 for (int i = 0; i < num_graphs_; i++) {
246 QCPGraph *graph = ui->streamGraph->addGraph();
247 graph->setPen(QPen(ColorUtils::graphColor(i)));
248 graph->setName(graph_cbs[i]->text());
249 graphs_ << graph;
250 graph_cbs[i]->setChecked(true);
251 graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
252 }
253 ui->streamGraph->xAxis->setLabel("Arrival Time");
254 ui->streamGraph->yAxis->setLabel("Value (ms)");
255
256 // We keep our temp files open for the lifetime of the dialog. The GTK+
257 // UI opens and closes at various points.
258 QString tempname = QString("%1/wireshark_iax2_f").arg(QDir::tempPath());
259 fwd_tempfile_ = new QTemporaryFile(tempname, this);
260 fwd_tempfile_->open();
261 tempname = QString("%1/wireshark_iax2_r").arg(QDir::tempPath());
262 rev_tempfile_ = new QTemporaryFile(tempname, this);
263 rev_tempfile_->open();
264
265 if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
266 err_str_ = tr("Unable to save RTP data.");
267 ui->actionSaveAudio->setEnabled(false);
268 ui->actionSaveForwardAudio->setEnabled(false);
269 ui->actionSaveReverseAudio->setEnabled(false);
270 }
271
272 QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
273 QMenu *save_menu = new QMenu(save_bt);
274 save_menu->addAction(ui->actionSaveAudio);
275 save_menu->addAction(ui->actionSaveForwardAudio);
276 save_menu->addAction(ui->actionSaveReverseAudio);
277 save_menu->addSeparator();
278 save_menu->addAction(ui->actionSaveCsv);
279 save_menu->addAction(ui->actionSaveForwardCsv);
280 save_menu->addAction(ui->actionSaveReverseCsv);
281 save_menu->addSeparator();
282 save_menu->addAction(ui->actionSaveGraph);
283 save_bt->setMenu(save_menu);
284
285 ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
286
287 resetStatistics();
288 updateStatistics(); // Initialize stats if an error occurs
289
290 #if 0
291 /* Only accept Voice or MiniPacket packets */
292 const gchar filter_text[] = "iax2.call && (ip || ipv6)";
293 #else
294 const gchar filter_text[] = "iax2 && (ip || ipv6)";
295 #endif
296 dfilter_t *sfcode;
297 gchar *err_msg;
298
299 /* Try to compile the filter. */
300 if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
301 err_str_ = QString(err_msg);
302 g_free(err_msg);
303 updateWidgets();
304 return;
305 }
306
307 if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) {
308 err_str_ = tr("Please select an IAX2 packet.");
309 save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
310 updateWidgets();
311 return;
312 }
313
314 if (!cf_read_current_record(cap_file_.capFile())) close();
315
316 frame_data *fdata = cap_file_.capFile()->current_frame;
317
318 epan_dissect_t edt;
319
320 epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
321 epan_dissect_prime_with_dfilter(&edt, sfcode);
322 epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->rec,
323 frame_tvbuff_new_buffer(&cap_file_.capFile()->provider, fdata, &cap_file_.capFile()->buf),
324 fdata, NULL);
325
326 // This shouldn't happen (the menu item should be disabled) but check anyway
327 if (!dfilter_apply_edt(sfcode, &edt)) {
328 epan_dissect_cleanup(&edt);
329 dfilter_free(sfcode);
330 err_str_ = tr("Please select an IAX2 packet.");
331 save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
332 updateWidgets();
333 return;
334 }
335
336 dfilter_free(sfcode);
337
338 /* ok, it is a IAX2 frame, so let's get the ip and port values */
339 rtpstream_id_copy_pinfo(&(edt.pi),&(fwd_id_),FALSE);
340
341 /* assume the inverse ip/port combination for the reverse direction */
342 rtpstream_id_copy_pinfo(&(edt.pi),&(rev_id_),TRUE);
343
344 epan_dissect_cleanup(&edt);
345
346 #ifdef IAX2_RTP_STREAM_CHECK
347 rtpstream_tapinfo_t tapinfo;
348
349 /* Register the tap listener */
350 rtpstream_info_init(&tapinfo);
351
352 tapinfo.tap_data = this;
353 tapinfo.mode = TAP_ANALYSE;
354
355 // register_tap_listener_rtpstream(&tapinfo, NULL);
356 /* Scan for RTP streams (redissect all packets) */
357 rtpstream_scan(&tapinfo, cap_file_.capFile(), Q_NULLPTR);
358
359 int num_streams = 0;
360 // TODO: Not used
361 //GList *filtered_list = NULL;
362 for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
363 rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
364 if (rtpstream_id_equal(&(strinfo->id), &(fwd_id_),RTPSTREAM_ID_EQUAL_NONE))
365 {
366 ++num_streams;
367 // TODO: Not used
368 //filtered_list = g_list_prepend(filtered_list, strinfo);
369 }
370
371 if (rtpstream_id_equal(&(strinfo->id), &(rev_id_),RTPSTREAM_ID_EQUAL_NONE))
372 {
373 ++num_streams;
374 // TODO: Not used
375 //filtered_list = g_list_append(filtered_list, strinfo);
376 }
377
378 rtpstream_info_free_data(strinfo);
379 g_free(list->data);
380 }
381 g_list_free(tapinfo->strinfo_list);
382
383 if (num_streams > 1) {
384 // Open the RTP streams dialog.
385 }
386 #endif
387
388 connect(ui->tabWidget, SIGNAL(currentChanged(int)),
389 this, SLOT(updateWidgets()));
390 connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
391 this, SLOT(updateWidgets()));
392 connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
393 this, SLOT(updateWidgets()));
394 updateWidgets();
395
396 registerTapListener("IAX2", this, Q_NULLPTR, 0, tapReset, tapPacket, tapDraw);
397 cap_file_.retapPackets();
398 removeTapListeners();
399
400 updateStatistics();
401 }
402
~Iax2AnalysisDialog()403 Iax2AnalysisDialog::~Iax2AnalysisDialog()
404 {
405 delete ui;
406 // remove_tap_listener_rtpstream(&tapinfo);
407 delete fwd_tempfile_;
408 delete rev_tempfile_;
409 }
410
updateWidgets()411 void Iax2AnalysisDialog::updateWidgets()
412 {
413 bool enable_tab = false;
414 QString hint = err_str_;
415
416 if (hint.isEmpty() || save_payload_error_ != TAP_IAX2_NO_ERROR) {
417 /* We cannot save the payload but can still display the widget
418 or save CSV data */
419 enable_tab = true;
420 }
421
422 bool enable_nav = false;
423 if (!file_closed_
424 && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
425 && ui->forwardTreeWidget->selectedItems().length() > 0)
426 || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
427 && ui->reverseTreeWidget->selectedItems().length() > 0))) {
428 enable_nav = true;
429 }
430 ui->actionGoToPacket->setEnabled(enable_nav);
431 ui->actionNextProblem->setEnabled(enable_nav);
432
433 if (enable_nav) {
434 hint.append(tr(" G: Go to packet, N: Next problem packet"));
435 }
436
437 bool enable_save_fwd_audio = fwd_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
438 bool enable_save_rev_audio = rev_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
439 ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
440 ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio);
441 ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio);
442
443 bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
444 bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
445 ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
446 ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
447 ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
448
449 ui->tabWidget->setEnabled(enable_tab);
450 hint.prepend("<small><i>");
451 hint.append("</i></small>");
452 ui->hintLabel->setText(hint);
453
454 WiresharkDialog::updateWidgets();
455 }
456
on_actionGoToPacket_triggered()457 void Iax2AnalysisDialog::on_actionGoToPacket_triggered()
458 {
459 if (file_closed_) return;
460 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
461 if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
462
463 QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
464 if (ti->type() != iax2_analysis_type_) return;
465
466 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
467 emit goToPacket(ra_ti->frameNum());
468 }
469
on_actionNextProblem_triggered()470 void Iax2AnalysisDialog::on_actionNextProblem_triggered()
471 {
472 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
473 if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
474
475 // Choose convenience over correctness.
476 if (cur_tree->selectedItems().length() < 1) {
477 cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
478 }
479
480 QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
481 if (sel_ti->type() != iax2_analysis_type_) return;
482 QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
483 while (test_ti != sel_ti) {
484 if (!test_ti) test_ti = cur_tree->topLevelItem(0);
485 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)test_ti);
486 if (!ra_ti->frameStatus()) {
487 cur_tree->setCurrentItem(ra_ti);
488 break;
489 }
490
491 test_ti = cur_tree->itemBelow(test_ti);
492 }
493 }
494
on_fJitterCheckBox_toggled(bool checked)495 void Iax2AnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
496 {
497 ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
498 updateGraph();
499 }
500
on_fDiffCheckBox_toggled(bool checked)501 void Iax2AnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
502 {
503 ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
504 updateGraph();
505 }
506
on_rJitterCheckBox_toggled(bool checked)507 void Iax2AnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
508 {
509 ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
510 updateGraph();
511 }
512
on_rDiffCheckBox_toggled(bool checked)513 void Iax2AnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
514 {
515 ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
516 updateGraph();
517 }
518
on_actionSaveAudio_triggered()519 void Iax2AnalysisDialog::on_actionSaveAudio_triggered()
520 {
521 saveAudio(dir_both_);
522 }
523
on_actionSaveForwardAudio_triggered()524 void Iax2AnalysisDialog::on_actionSaveForwardAudio_triggered()
525 {
526 saveAudio(dir_forward_);
527 }
528
on_actionSaveReverseAudio_triggered()529 void Iax2AnalysisDialog::on_actionSaveReverseAudio_triggered()
530 {
531 saveAudio(dir_reverse_);
532 }
533
on_actionSaveCsv_triggered()534 void Iax2AnalysisDialog::on_actionSaveCsv_triggered()
535 {
536 saveCsv(dir_both_);
537 }
538
on_actionSaveForwardCsv_triggered()539 void Iax2AnalysisDialog::on_actionSaveForwardCsv_triggered()
540 {
541 saveCsv(dir_forward_);
542 }
543
on_actionSaveReverseCsv_triggered()544 void Iax2AnalysisDialog::on_actionSaveReverseCsv_triggered()
545 {
546 saveCsv(dir_reverse_);
547 }
548
on_actionSaveGraph_triggered()549 void Iax2AnalysisDialog::on_actionSaveGraph_triggered()
550 {
551 ui->tabWidget->setCurrentWidget(ui->graphTab);
552
553 QString file_name, extension;
554 QDir path(wsApp->lastOpenDir());
555 QString pdf_filter = tr("Portable Document Format (*.pdf)");
556 QString png_filter = tr("Portable Network Graphics (*.png)");
557 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
558 // Gaze upon my beautiful graph with lossy artifacts!
559 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
560 QString filter = QString("%1;;%2;;%3;;%4")
561 .arg(pdf_filter)
562 .arg(png_filter)
563 .arg(bmp_filter)
564 .arg(jpeg_filter);
565
566 QString save_file = path.canonicalPath();
567 if (!file_closed_) {
568 save_file += QString("/%1").arg(cap_file_.fileBaseName());
569 }
570 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
571 save_file, filter, &extension);
572
573 if (!file_name.isEmpty()) {
574 bool save_ok = false;
575 // https://www.qcustomplot.com/index.php/support/forum/63
576 // ui->streamGraph->legend->setVisible(true);
577 if (extension.compare(pdf_filter) == 0) {
578 save_ok = ui->streamGraph->savePdf(file_name);
579 } else if (extension.compare(png_filter) == 0) {
580 save_ok = ui->streamGraph->savePng(file_name);
581 } else if (extension.compare(bmp_filter) == 0) {
582 save_ok = ui->streamGraph->saveBmp(file_name);
583 } else if (extension.compare(jpeg_filter) == 0) {
584 save_ok = ui->streamGraph->saveJpg(file_name);
585 }
586 // ui->streamGraph->legend->setVisible(false);
587 // else error dialog?
588 if (save_ok) {
589 wsApp->setLastOpenDirFromFilename(file_name);
590 }
591 }
592 }
593
on_buttonBox_helpRequested()594 void Iax2AnalysisDialog::on_buttonBox_helpRequested()
595 {
596 wsApp->helpTopicAction(HELP_IAX2_ANALYSIS_DIALOG);
597 }
598
tapReset(void * tapinfoptr)599 void Iax2AnalysisDialog::tapReset(void *tapinfoptr)
600 {
601 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
602 if (!iax2_analysis_dialog) return;
603
604 iax2_analysis_dialog->resetStatistics();
605 }
606
tapPacket(void * tapinfoptr,packet_info * pinfo,struct epan_dissect *,const void * iax2info_ptr)607 tap_packet_status Iax2AnalysisDialog::tapPacket(void *tapinfoptr, packet_info *pinfo, struct epan_dissect *, const void *iax2info_ptr)
608 {
609 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
610 if (!iax2_analysis_dialog) return TAP_PACKET_DONT_REDRAW;
611
612 const iax2_info_t *iax2info = (const iax2_info_t *)iax2info_ptr;
613 if (!iax2info) return TAP_PACKET_DONT_REDRAW;
614
615 /* we ignore packets that are not displayed */
616 if (pinfo->fd->passed_dfilter == 0)
617 return TAP_PACKET_DONT_REDRAW;
618
619 /* we ignore packets that carry no data */
620 if (iax2info->payload_len < 1)
621 return TAP_PACKET_DONT_REDRAW;
622
623 /* is it the forward direction? */
624 else if ((cmp_address(&(iax2_analysis_dialog->fwd_id_.src_addr), &(pinfo->src)) == 0)
625 && (iax2_analysis_dialog->fwd_id_.src_port == pinfo->srcport)
626 && (cmp_address(&(iax2_analysis_dialog->fwd_id_.dst_addr), &(pinfo->dst)) == 0)
627 && (iax2_analysis_dialog->fwd_id_.dst_port == pinfo->destport)) {
628
629 iax2_analysis_dialog->addPacket(true, pinfo, iax2info);
630 }
631 /* is it the reversed direction? */
632 else if ((cmp_address(&(iax2_analysis_dialog->rev_id_.src_addr), &(pinfo->src)) == 0)
633 && (iax2_analysis_dialog->rev_id_.src_port == pinfo->srcport)
634 && (cmp_address(&(iax2_analysis_dialog->rev_id_.dst_addr), &(pinfo->dst)) == 0)
635 && (iax2_analysis_dialog->rev_id_.dst_port == pinfo->destport)) {
636
637 iax2_analysis_dialog->addPacket(false, pinfo, iax2info);
638 }
639 return TAP_PACKET_DONT_REDRAW;
640 }
641
tapDraw(void * tapinfoptr)642 void Iax2AnalysisDialog::tapDraw(void *tapinfoptr)
643 {
644 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
645 if (!iax2_analysis_dialog) return;
646 iax2_analysis_dialog->updateStatistics();
647 }
648
resetStatistics()649 void Iax2AnalysisDialog::resetStatistics()
650 {
651 memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
652 memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
653
654 fwd_statinfo_.first_packet = TRUE;
655 rev_statinfo_.first_packet = TRUE;
656 fwd_statinfo_.reg_pt = PT_UNDEFINED;
657 rev_statinfo_.reg_pt = PT_UNDEFINED;
658
659 ui->forwardTreeWidget->clear();
660 ui->reverseTreeWidget->clear();
661
662 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
663 ui->streamGraph->graph(i)->data()->clear();
664 }
665
666 fwd_time_vals_.clear();
667 fwd_jitter_vals_.clear();
668 fwd_diff_vals_.clear();
669 rev_time_vals_.clear();
670 rev_jitter_vals_.clear();
671 rev_diff_vals_.clear();
672
673 fwd_tempfile_->resize(0);
674 rev_tempfile_->resize(0);
675 }
676
addPacket(bool forward,packet_info * pinfo,const struct _iax2_info_t * iax2info)677 void Iax2AnalysisDialog::addPacket(bool forward, packet_info *pinfo, const struct _iax2_info_t *iax2info)
678 {
679 /* add this RTP for future listening using the RTP Player*/
680 // add_rtp_packet(rtpinfo, pinfo);
681
682 if (forward) {
683 iax2_packet_analyse(&fwd_statinfo_, pinfo, iax2info);
684 new Iax2AnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo);
685
686 fwd_time_vals_.append((fwd_statinfo_.time));
687 fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
688 fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
689
690 savePayload(fwd_tempfile_, pinfo, iax2info);
691 } else {
692 iax2_packet_analyse(&rev_statinfo_, pinfo, iax2info);
693 new Iax2AnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo);
694
695 rev_time_vals_.append((rev_statinfo_.time));
696 rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
697 rev_diff_vals_.append(rev_statinfo_.diff * 1000);
698
699 savePayload(rev_tempfile_, pinfo, iax2info);
700 }
701
702 }
703
704 // iax2_analysis.c:rtp_packet_save_payload
705 const guint8 silence_pcmu_ = 0xff;
706 const guint8 silence_pcma_ = 0x55;
savePayload(QTemporaryFile * tmpfile,packet_info * pinfo,const struct _iax2_info_t * iax2info)707 void Iax2AnalysisDialog::savePayload(QTemporaryFile *tmpfile, packet_info *pinfo, const struct _iax2_info_t *iax2info)
708 {
709 /* Is this the first packet we got in this direction? */
710 // if (statinfo->flags & STAT_FLAG_FIRST) {
711 // if (saveinfo->fp == NULL) {
712 // saveinfo->saved = FALSE;
713 // saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
714 // } else {
715 // saveinfo->saved = TRUE;
716 // }
717 // }
718
719 /* Save the voice information */
720
721 /* If there was already an error, we quit */
722 if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return;
723
724 /* Quit if the captured length and packet length aren't equal.
725 */
726 if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
727 tmpfile->close();
728 err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
729 save_payload_error_ = TAP_IAX2_WRONG_LENGTH;
730 return;
731 }
732
733 if (iax2info->payload_len > 0) {
734 const char *data = (const char *) iax2info->payload_data;
735 qint64 nchars;
736
737 nchars = tmpfile->write(data, iax2info->payload_len);
738 if (nchars != (iax2info->payload_len)) {
739 /* Write error or short write */
740 err_str_ = tr("Can't save in a file: File I/O problem.");
741 save_payload_error_ = TAP_IAX2_FILE_IO_ERROR;
742 tmpfile->close();
743 return;
744 }
745 return;
746 }
747 return;
748 }
749
updateStatistics()750 void Iax2AnalysisDialog::updateStatistics()
751 {
752 double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; // s
753 double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
754 #if 0 // Marked as "TODO" in tap-iax2-analysis.c:128
755 unsigned int f_expected = fwd_statinfo_.stop_seq_nr - fwd_statinfo_.start_seq_nr + 1;
756 unsigned int r_expected = rev_statinfo_.stop_seq_nr - rev_statinfo_.start_seq_nr + 1;
757 int f_lost = f_expected - fwd_statinfo_.total_nr;
758 int r_lost = r_expected - rev_statinfo_.total_nr;
759 double f_perc, r_perc;
760
761 if (f_expected) {
762 f_perc = (double)(f_lost*100)/(double)f_expected;
763 } else {
764 f_perc = 0;
765 }
766 if (r_expected) {
767 r_perc = (double)(r_lost*100)/(double)r_expected;
768 } else {
769 r_perc = 0;
770 }
771 #endif
772
773 QString stats_tables = "<html><head></head><body>\n";
774 stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
775 .arg(address_to_qstring(&fwd_id_.src_addr, true))
776 .arg(fwd_id_.src_port);
777 stats_tables += QString("<br>%1:%2</p>\n")
778 .arg(address_to_qstring(&fwd_id_.dst_addr, true))
779 .arg(fwd_id_.dst_port);
780 stats_tables += "<h4>Forward</h4>\n";
781 stats_tables += "<p><table>\n";
782 stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
783 .arg(fwd_statinfo_.max_delta, 0, 'f', 2)
784 .arg(fwd_statinfo_.max_nr);
785 stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
786 .arg(fwd_statinfo_.max_jitter, 0, 'f', 2);
787 stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
788 .arg(fwd_statinfo_.mean_jitter, 0, 'f', 2);
789 stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
790 .arg(fwd_statinfo_.total_nr);
791 #if 0
792 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
793 .arg(f_expected);
794 stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
795 .arg(f_lost).arg(f_perc, 0, 'f', 2);
796 stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
797 .arg(fwd_statinfo_.sequence);
798 #endif
799 stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
800 .arg(f_duration, 0, 'f', 2);
801 stats_tables += "</table></p>\n";
802
803 stats_tables += "<h4>Reverse</h4>\n";
804 stats_tables += "<p><table>\n";
805 stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
806 .arg(rev_statinfo_.max_delta, 0, 'f', 2)
807 .arg(rev_statinfo_.max_nr);
808 stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
809 .arg(rev_statinfo_.max_jitter, 0, 'f', 2);
810 stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
811 .arg(rev_statinfo_.mean_jitter, 0, 'f', 2);
812 stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
813 .arg(rev_statinfo_.total_nr);
814 #if 0
815 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
816 .arg(r_expected);
817 stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
818 .arg(r_lost).arg(r_perc, 0, 'f', 2);
819 stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
820 .arg(rev_statinfo_.sequence);
821 #endif
822 stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
823 .arg(r_duration, 0, 'f', 2);
824 stats_tables += "</table></p></body>\n";
825
826 ui->statisticsLabel->setText(stats_tables);
827
828 for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
829 ui->forwardTreeWidget->resizeColumnToContents(col);
830 ui->reverseTreeWidget->resizeColumnToContents(col);
831 }
832
833 graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
834 graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
835 graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
836 graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
837
838 updateGraph();
839
840 updateWidgets();
841 }
842
updateGraph()843 void Iax2AnalysisDialog::updateGraph()
844 {
845 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
846 if (ui->streamGraph->graph(i)->visible()) {
847 ui->streamGraph->graph(i)->rescaleAxes(i > 0);
848 }
849 }
850 ui->streamGraph->replot();
851 }
852
853 // iax2_analysis.c:copy_file
854 enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
saveAudio(Iax2AnalysisDialog::StreamDirection direction)855 void Iax2AnalysisDialog::saveAudio(Iax2AnalysisDialog::StreamDirection direction)
856 {
857 if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
858
859 QString caption;
860
861 switch (direction) {
862 case dir_forward_:
863 caption = tr("Save forward stream audio");
864 break;
865 case dir_reverse_:
866 caption = tr("Save reverse stream audio");
867 break;
868 case dir_both_:
869 default:
870 caption = tr("Save audio");
871 break;
872 }
873
874 QString ext_filter = tr("Sun Audio (*.au)");
875 if (direction != dir_both_) {
876 ext_filter.append(tr(";;Raw (*.raw)"));
877 }
878 QString sel_filter;
879 QString file_path = WiresharkFileDialog::getSaveFileName(
880 this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
881 ext_filter, &sel_filter);
882
883 if (file_path.isEmpty()) return;
884
885 int save_format = save_audio_none_;
886 if (file_path.endsWith(".au")) {
887 save_format = save_audio_au_;
888 } else if (file_path.endsWith(".raw")) {
889 save_format = save_audio_raw_;
890 }
891
892 if (save_format == save_audio_none_) {
893 QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
894 return;
895 }
896
897 QFile save_file(file_path);
898 gint16 sample;
899 guint8 pd[4];
900 gboolean stop_flag = FALSE;
901 qint64 nchars;
902
903 save_file.open(QIODevice::WriteOnly);
904 fwd_tempfile_->seek(0);
905 rev_tempfile_->seek(0);
906
907 if (save_file.error() != QFile::NoError) {
908 QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
909 return;
910 }
911
912 ui->hintLabel->setText(tr("Saving %1…").arg(save_file.fileName()));
913 ui->progressFrame->showProgress(tr("Analyzing IAX2"), true, true, &stop_flag);
914
915 if (save_format == save_audio_au_) { /* au format; https://pubs.opengroup.org/external/auformat.html */
916 /* First we write the .au header. All values in the header are
917 * 4-byte big-endian values, so we use pntoh32() to copy them
918 * to a 4-byte buffer, in big-endian order, and then write out
919 * the buffer. */
920
921 /* the magic word 0x2e736e64 == .snd */
922 phton32(pd, 0x2e736e64);
923 nchars = save_file.write((const char *)pd, 4);
924 if (nchars != 4)
925 goto copy_file_err;
926 /* header offset == 24 bytes */
927 phton32(pd, 24);
928 nchars = save_file.write((const char *)pd, 4);
929 if (nchars != 4)
930 goto copy_file_err;
931 /* total length; it is permitted to set this to 0xffffffff */
932 phton32(pd, 0xffffffff);
933 nchars = save_file.write((const char *)pd, 4);
934 if (nchars != 4)
935 goto copy_file_err;
936 /* encoding format == 16-bit linear PCM */
937 phton32(pd, 3);
938 nchars = save_file.write((const char *)pd, 4);
939 if (nchars != 4)
940 goto copy_file_err;
941 /* sample rate == 8000 Hz */
942 phton32(pd, 8000);
943 nchars = save_file.write((const char *)pd, 4);
944 if (nchars != 4)
945 goto copy_file_err;
946 /* channels == 1 */
947 phton32(pd, 1);
948 nchars = save_file.write((const char *)pd, 4);
949 if (nchars != 4)
950 goto copy_file_err;
951
952 switch (direction) {
953 /* Only forward direction */
954 case dir_forward_:
955 {
956 char f_rawvalue;
957 while (fwd_tempfile_->getChar(&f_rawvalue)) {
958 if (stop_flag) {
959 break;
960 }
961 ui->progressFrame->setValue(int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size()));
962
963 if (fwd_statinfo_.pt == PT_PCMU) {
964 sample = ulaw2linear((unsigned char)f_rawvalue);
965 phton16(pd, sample);
966 } else if (fwd_statinfo_.pt == PT_PCMA) {
967 sample = alaw2linear((unsigned char)f_rawvalue);
968 phton16(pd, sample);
969 } else {
970 goto copy_file_err;
971 }
972
973 nchars = save_file.write((const char *)pd, 2);
974 if (nchars < 2) {
975 goto copy_file_err;
976 }
977 }
978 break;
979 }
980 /* Only reverse direction */
981 case dir_reverse_:
982 {
983 char r_rawvalue;
984 while (rev_tempfile_->getChar(&r_rawvalue)) {
985 if (stop_flag) {
986 break;
987 }
988 ui->progressFrame->setValue(int(rev_tempfile_->pos() * 100 / rev_tempfile_->size()));
989
990 if (rev_statinfo_.pt == PT_PCMU) {
991 sample = ulaw2linear((unsigned char)r_rawvalue);
992 phton16(pd, sample);
993 } else if (rev_statinfo_.pt == PT_PCMA) {
994 sample = alaw2linear((unsigned char)r_rawvalue);
995 phton16(pd, sample);
996 } else {
997 goto copy_file_err;
998 }
999
1000 nchars = save_file.write((const char *)pd, 2);
1001 if (nchars < 2) {
1002 goto copy_file_err;
1003 }
1004 }
1005 break;
1006 }
1007 /* Both directions */
1008 case dir_both_:
1009 {
1010 char f_rawvalue, r_rawvalue;
1011 guint32 f_write_silence = 0;
1012 guint32 r_write_silence = 0;
1013 /* since conversation in one way can start later than in the other one,
1014 * we have to write some silence information for one channel */
1015 if (fwd_statinfo_.start_time > rev_statinfo_.start_time) {
1016 f_write_silence = (guint32)
1017 ((fwd_statinfo_.start_time - rev_statinfo_.start_time)
1018 * (8000/1000));
1019 } else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) {
1020 r_write_silence = (guint32)
1021 ((rev_statinfo_.start_time - fwd_statinfo_.start_time)
1022 * (8000/1000));
1023 }
1024 for (;;) {
1025 if (stop_flag) {
1026 break;
1027 }
1028 int fwd_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1029 int rev_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1030 ui->progressFrame->setValue(qMin(fwd_pct, rev_pct));
1031
1032 if (f_write_silence > 0) {
1033 rev_tempfile_->getChar(&r_rawvalue);
1034 switch (fwd_statinfo_.reg_pt) {
1035 case PT_PCMU:
1036 f_rawvalue = silence_pcmu_;
1037 break;
1038 case PT_PCMA:
1039 f_rawvalue = silence_pcma_;
1040 break;
1041 default:
1042 f_rawvalue = 0;
1043 break;
1044 }
1045 f_write_silence--;
1046 } else if (r_write_silence > 0) {
1047 fwd_tempfile_->getChar(&f_rawvalue);
1048 switch (rev_statinfo_.reg_pt) {
1049 case PT_PCMU:
1050 r_rawvalue = silence_pcmu_;
1051 break;
1052 case PT_PCMA:
1053 r_rawvalue = silence_pcma_;
1054 break;
1055 default:
1056 r_rawvalue = 0;
1057 break;
1058 }
1059 r_write_silence--;
1060 } else {
1061 fwd_tempfile_->getChar(&f_rawvalue);
1062 rev_tempfile_->getChar(&r_rawvalue);
1063 }
1064 if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd())
1065 break;
1066 if ((fwd_statinfo_.pt == PT_PCMU)
1067 && (rev_statinfo_.pt == PT_PCMU)) {
1068 sample = (ulaw2linear((unsigned char)r_rawvalue)
1069 + ulaw2linear((unsigned char)f_rawvalue)) / 2;
1070 phton16(pd, sample);
1071 }
1072 else if ((fwd_statinfo_.pt == PT_PCMA)
1073 && (rev_statinfo_.pt == PT_PCMA)) {
1074 sample = (alaw2linear((unsigned char)r_rawvalue)
1075 + alaw2linear((unsigned char)f_rawvalue)) / 2;
1076 phton16(pd, sample);
1077 } else {
1078 goto copy_file_err;
1079 }
1080
1081 nchars = save_file.write((const char *)pd, 2);
1082 if (nchars < 2) {
1083 goto copy_file_err;
1084 }
1085 }
1086 }
1087 }
1088 } else if (save_format == save_audio_raw_) { /* raw format */
1089 QFile *tempfile;
1090 int progress_pct;
1091
1092 switch (direction) {
1093 /* Only forward direction */
1094 case dir_forward_: {
1095 progress_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1096 tempfile = fwd_tempfile_;
1097 break;
1098 }
1099 /* only reversed direction */
1100 case dir_reverse_: {
1101 progress_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1102 tempfile = rev_tempfile_;
1103 break;
1104 }
1105 default: {
1106 goto copy_file_err;
1107 }
1108 }
1109
1110 int chunk_size = 65536;
1111 /* XXX how do you just copy the file? */
1112 while (chunk_size > 0) {
1113 if (stop_flag)
1114 break;
1115 QByteArray bytes = tempfile->read(chunk_size);
1116 ui->progressFrame->setValue(progress_pct);
1117
1118 if (!save_file.write(bytes)) {
1119 goto copy_file_err;
1120 }
1121 chunk_size = bytes.length();
1122 }
1123 }
1124
1125 copy_file_err:
1126 ui->progressFrame->hide();
1127 updateWidgets();
1128 return;
1129 }
1130
1131 // XXX The GTK+ UI saves the length and timestamp.
saveCsv(Iax2AnalysisDialog::StreamDirection direction)1132 void Iax2AnalysisDialog::saveCsv(Iax2AnalysisDialog::StreamDirection direction)
1133 {
1134 QString caption;
1135
1136 switch (direction) {
1137 case dir_forward_:
1138 caption = tr("Save forward stream CSV");
1139 break;
1140 case dir_reverse_:
1141 caption = tr("Save reverse stream CSV");
1142 break;
1143 case dir_both_:
1144 default:
1145 caption = tr("Save CSV");
1146 break;
1147 }
1148
1149 QString file_path = WiresharkFileDialog::getSaveFileName(
1150 this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
1151 tr("Comma-separated values (*.csv)"));
1152
1153 if (file_path.isEmpty()) return;
1154
1155 QFile save_file(file_path);
1156 save_file.open(QFile::WriteOnly);
1157
1158 if (direction == dir_forward_ || direction == dir_both_) {
1159 save_file.write("Forward\n");
1160
1161 for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
1162 QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
1163 if (ti->type() != iax2_analysis_type_) continue;
1164 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1165 QStringList values;
1166 foreach (QVariant v, ra_ti->rowData()) {
1167 if (!v.isValid()) {
1168 values << "\"\"";
1169 } else if ((int) v.type() == (int) QMetaType::QString) {
1170 values << QString("\"%1\"").arg(v.toString());
1171 } else {
1172 values << v.toString();
1173 }
1174 }
1175 save_file.write(values.join(",").toUtf8());
1176 save_file.write("\n");
1177 }
1178 }
1179 if (direction == dir_both_) {
1180 save_file.write("\n");
1181 }
1182 if (direction == dir_reverse_ || direction == dir_both_) {
1183 save_file.write("Reverse\n");
1184
1185 for (int row = 0; row < ui->reverseTreeWidget->topLevelItemCount(); row++) {
1186 QTreeWidgetItem *ti = ui->reverseTreeWidget->topLevelItem(row);
1187 if (ti->type() != iax2_analysis_type_) continue;
1188 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1189 QStringList values;
1190 foreach (QVariant v, ra_ti->rowData()) {
1191 if (!v.isValid()) {
1192 values << "\"\"";
1193 } else if (v.type() == QVariant::String) {
1194 values << QString("\"%1\"").arg(v.toString());
1195 } else {
1196 values << v.toString();
1197 }
1198 }
1199 save_file.write(values.join(",").toUtf8());
1200 save_file.write("\n");
1201 }
1202 }
1203 }
1204
eventFilter(QObject *,QEvent * event)1205 bool Iax2AnalysisDialog::eventFilter(QObject *, QEvent *event)
1206 {
1207 if (event->type() != QEvent::KeyPress) return false;
1208
1209 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
1210
1211 switch(kevt->key()) {
1212 case Qt::Key_G:
1213 on_actionGoToPacket_triggered();
1214 return true;
1215 case Qt::Key_N:
1216 on_actionNextProblem_triggered();
1217 return true;
1218 default:
1219 break;
1220 }
1221 return false;
1222 }
1223
graphClicked(QMouseEvent * event)1224 void Iax2AnalysisDialog::graphClicked(QMouseEvent *event)
1225 {
1226 updateWidgets();
1227 if (event->button() == Qt::RightButton) {
1228 graph_ctx_menu_.exec(event->globalPos());
1229 }
1230 }
1231
showStreamMenu(QPoint pos)1232 void Iax2AnalysisDialog::showStreamMenu(QPoint pos)
1233 {
1234 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
1235 if (!cur_tree) return;
1236
1237 updateWidgets();
1238 stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1239 }
1240