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