1 /* iax2_analysis_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "iax2_analysis_dialog.h"
23 #include <ui_iax2_analysis_dialog.h>
26 #include "frame_tvbuff.h"
28 #include <epan/epan_dissect.h>
29 #include <epan/rtp_pt.h>
31 #include <epan/dfilter/dfilter.h>
33 #include <epan/dissectors/packet-iax2.h>
35 #include "ui/help_url.h"
36 #ifdef IAX2_RTP_STREAM_CHECK
37 #include "ui/rtp_stream.h"
39 #include <wsutil/utf8_entities.h>
41 #include <wsutil/g711.h>
42 #include <wsutil/pint.h>
44 #include <QFileDialog>
45 #include <QMessageBox>
46 #include <QPushButton>
47 #include <QTemporaryFile>
49 #include <ui/qt/utils/color_utils.h>
50 #include <ui/qt/utils/qt_ui_utils.h>
51 #include <ui/qt/utils/stock_icon.h>
52 #include "wireshark_application.h"
55 * @file RTP stream analysis dialog
57 * Displays forward and reverse RTP streams and graphs each stream
61 // - Progress bar for tapping and saving.
62 // - Add a refresh button and/or action.
63 // - Fixup output file names.
64 // - Add a graph title and legend when saving?
75 static const QRgb color_rtp_warn_ = 0xffdbbf;
77 enum { iax2_analysis_type_ = 1000 };
78 class Iax2AnalysisTreeWidgetItem : public QTreeWidgetItem
81 Iax2AnalysisTreeWidgetItem(QTreeWidget *tree, tap_iax2_stat_t *statinfo, packet_info *pinfo) :
82 QTreeWidgetItem(tree, iax2_analysis_type_)
84 frame_num_ = pinfo->num;
85 pkt_len_ = pinfo->fd->pkt_len;
86 flags_ = statinfo->flags;
87 if (flags_ & STAT_FLAG_FIRST) {
91 delta_ = statinfo->delta;
92 jitter_ = statinfo->jitter;
94 bandwidth_ = statinfo->bandwidth;
97 QColor bg_color = QColor();
100 if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
101 status = QObject::tr("Wrong sequence number");
102 bg_color = ColorUtils::expert_color_error;
103 } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
104 status = QObject::tr("Payload changed to PT=%1").arg(statinfo->pt);
105 bg_color = color_rtp_warn_;
106 } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
107 status = QObject::tr("Incorrect timestamp");
108 /* color = COLOR_WARNING; */
109 bg_color = color_rtp_warn_;
110 } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
111 && !(statinfo->flags & STAT_FLAG_FIRST)
112 && !(statinfo->flags & STAT_FLAG_PT_CN)
113 && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
114 && !(statinfo->flags & STAT_FLAG_MARKER)) {
115 status = QObject::tr("Marker missing?");
116 bg_color = color_rtp_warn_;
118 if (statinfo->flags & STAT_FLAG_MARKER) {
119 bg_color = color_rtp_warn_;
123 if (status.isEmpty()) {
125 status = UTF8_CHECK_MARK;
128 setText(packet_col_, QString::number(frame_num_));
129 setText(delta_col_, QString::number(delta_, 'f', 2));
130 setText(jitter_col_, QString::number(jitter_, 'f', 2));
131 setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
132 setText(status_col_, status);
133 setText(length_col_, QString::number(pkt_len_));
135 setTextAlignment(packet_col_, Qt::AlignRight);
136 setTextAlignment(delta_col_, Qt::AlignRight);
137 setTextAlignment(jitter_col_, Qt::AlignRight);
138 setTextAlignment(bandwidth_col_, Qt::AlignRight);
139 setTextAlignment(length_col_, Qt::AlignRight);
141 if (bg_color.isValid()) {
142 for (int col = 0; col < columnCount(); col++) {
143 setBackground(col, bg_color);
144 setForeground(col, ColorUtils::expert_color_foreground);
149 guint32 frameNum() { return frame_num_; }
150 bool frameStatus() { return ok_; }
152 QList<QVariant> rowData() {
153 QString status_str = ok_ ? "OK" : text(status_col_);
155 return QList<QVariant>()
156 << frame_num_ << delta_ << jitter_ << bandwidth_
157 << status_str << pkt_len_;
160 bool operator< (const QTreeWidgetItem &other) const
162 if (other.type() != iax2_analysis_type_) return QTreeWidgetItem::operator< (other);
163 const Iax2AnalysisTreeWidgetItem *other_row = static_cast<const Iax2AnalysisTreeWidgetItem *>(&other);
165 switch (treeWidget()->sortColumn()) {
167 return frame_num_ < other_row->frame_num_;
170 return delta_ < other_row->delta_;
173 return jitter_ < other_row->jitter_;
175 case (bandwidth_col_):
176 return bandwidth_ < other_row->bandwidth_;
179 return pkt_len_ < other_row->pkt_len_;
185 // Fall back to string comparison
186 return QTreeWidgetItem::operator <(other);
206 Iax2AnalysisDialog::Iax2AnalysisDialog(QWidget &parent, CaptureFile &cf) :
207 WiresharkDialog(parent, cf),
208 ui(new Ui::Iax2AnalysisDialog),
213 save_payload_error_(TAP_IAX2_NO_ERROR)
216 loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
217 setWindowSubtitle(tr("IAX2 Stream Analysis"));
219 ui->progressFrame->hide();
221 stream_ctx_menu_.addAction(ui->actionGoToPacket);
222 stream_ctx_menu_.addAction(ui->actionNextProblem);
223 stream_ctx_menu_.addSeparator();
224 stream_ctx_menu_.addAction(ui->actionSaveAudio);
225 stream_ctx_menu_.addAction(ui->actionSaveForwardAudio);
226 stream_ctx_menu_.addAction(ui->actionSaveReverseAudio);
227 stream_ctx_menu_.addSeparator();
228 stream_ctx_menu_.addAction(ui->actionSaveCsv);
229 stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
230 stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
231 stream_ctx_menu_.addSeparator();
232 stream_ctx_menu_.addAction(ui->actionSaveGraph);
233 ui->forwardTreeWidget->installEventFilter(this);
234 ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
235 connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
236 SLOT(showStreamMenu(QPoint)));
237 ui->reverseTreeWidget->installEventFilter(this);
238 ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
239 connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
240 SLOT(showStreamMenu(QPoint)));
241 connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
242 this, SLOT(graphClicked(QMouseEvent*)));
244 graph_ctx_menu_.addAction(ui->actionSaveGraph);
246 QStringList header_labels;
247 for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
248 header_labels << ui->forwardTreeWidget->headerItem()->text(i);
250 ui->reverseTreeWidget->setHeaderLabels(header_labels);
252 memset(&src_fwd_, 0, sizeof(address));
253 memset(&dst_fwd_, 0, sizeof(address));
254 memset(&src_rev_, 0, sizeof(address));
255 memset(&dst_rev_, 0, sizeof(address));
257 QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
258 << ui->fJitterCheckBox << ui->fDiffCheckBox
259 << ui->rJitterCheckBox << ui->rDiffCheckBox;
261 for (int i = 0; i < num_graphs_; i++) {
262 QCPGraph *graph = ui->streamGraph->addGraph();
263 graph->setPen(QPen(ColorUtils::graphColor(i)));
264 graph->setName(graph_cbs[i]->text());
266 graph_cbs[i]->setChecked(true);
267 graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
269 ui->streamGraph->xAxis->setLabel("Arrival Time");
270 ui->streamGraph->yAxis->setLabel("Value (ms)");
272 // We keep our temp files open for the lifetime of the dialog. The GTK+
273 // UI opens and closes at various points.
274 QString tempname = QString("%1/wireshark_iax2_f").arg(QDir::tempPath());
275 fwd_tempfile_ = new QTemporaryFile(tempname, this);
276 fwd_tempfile_->open();
277 tempname = QString("%1/wireshark_iax2_r").arg(QDir::tempPath());
278 rev_tempfile_ = new QTemporaryFile(tempname, this);
279 rev_tempfile_->open();
281 if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
282 err_str_ = tr("Unable to save RTP data.");
283 ui->actionSaveAudio->setEnabled(false);
284 ui->actionSaveForwardAudio->setEnabled(false);
285 ui->actionSaveReverseAudio->setEnabled(false);
288 QMenu *save_menu = new QMenu();
289 save_menu->addAction(ui->actionSaveAudio);
290 save_menu->addAction(ui->actionSaveForwardAudio);
291 save_menu->addAction(ui->actionSaveReverseAudio);
292 save_menu->addSeparator();
293 save_menu->addAction(ui->actionSaveCsv);
294 save_menu->addAction(ui->actionSaveForwardCsv);
295 save_menu->addAction(ui->actionSaveReverseCsv);
296 save_menu->addSeparator();
297 save_menu->addAction(ui->actionSaveGraph);
298 ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu);
300 ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
303 updateStatistics(); // Initialize stats if an error occurs
306 /* Only accept Voice or MiniPacket packets */
307 const gchar filter_text[] = "iax2.call && (ip || ipv6)";
309 const gchar filter_text[] = "iax2 && (ip || ipv6)";
314 /* Try to compile the filter. */
315 if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
316 err_str_ = QString(err_msg);
322 if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) {
323 err_str_ = tr("Please select an IAX2 packet.");
324 save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
329 frame_data *fdata = cap_file_.capFile()->current_frame;
331 if (!cf_read_record(cap_file_.capFile(), fdata)) close();
335 epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
336 epan_dissect_prime_with_dfilter(&edt, sfcode);
337 epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->phdr,
338 frame_tvbuff_new_buffer(fdata, &cap_file_.capFile()->buf), fdata, NULL);
340 // This shouldn't happen (the menu item should be disabled) but check anyway
341 if (!dfilter_apply_edt(sfcode, &edt)) {
342 epan_dissect_cleanup(&edt);
343 dfilter_free(sfcode);
344 err_str_ = tr("Please select an IAX2 packet.");
345 save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
350 dfilter_free(sfcode);
352 /* ok, it is a IAX2 frame, so let's get the ip and port values */
353 copy_address(&(src_fwd_), &(edt.pi.src));
354 copy_address(&(dst_fwd_), &(edt.pi.dst));
355 port_src_fwd_ = edt.pi.srcport;
356 port_dst_fwd_ = edt.pi.destport;
358 /* assume the inverse ip/port combination for the reverse direction */
359 copy_address(&(src_rev_), &(edt.pi.dst));
360 copy_address(&(dst_rev_), &(edt.pi.src));
361 port_src_rev_ = edt.pi.destport;
362 port_dst_rev_ = edt.pi.srcport;
364 #ifdef IAX2_RTP_STREAM_CHECK
365 rtpstream_tapinfot tapinfo;
367 /* Register the tap listener */
368 memset(&tapinfo, 0, sizeof(rtpstream_tapinfot));
369 tapinfo.tap_data = this;
370 tapinfo.mode = TAP_ANALYSE;
372 // register_tap_listener_rtp_stream(&tapinfo, NULL);
373 /* Scan for RTP streams (redissect all packets) */
374 rtpstream_scan(&tapinfo, cap_file_.capFile(), NULL);
377 GList *filtered_list = NULL;
378 for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) {
379 rtp_stream_info_t * strinfo = (rtp_stream_info_t*)(strinfo_list->data);
380 << address_to_qstring(&strinfo->dest_addr) << address_to_qstring(&src_rev_) << address_to_qstring(&dst_rev_);
381 if (addresses_equal(&(strinfo->src_addr), &(src_fwd_))
382 && (strinfo->src_port == port_src_fwd_)
383 && (addresses_equal(&(strinfo->dest_addr), &(dst_fwd_)))
384 && (strinfo->dest_port == port_dst_fwd_))
387 filtered_list = g_list_prepend(filtered_list, strinfo);
390 if (addresses_equal(&(strinfo->src_addr), &(src_rev_))
391 && (strinfo->src_port == port_src_rev_)
392 && (addresses_equal(&(strinfo->dest_addr), &(dst_rev_)))
393 && (strinfo->dest_port == port_dst_rev_))
396 filtered_list = g_list_append(filtered_list, strinfo);
400 if (num_streams > 1) {
401 // Open the RTP streams dialog.
405 connect(ui->tabWidget, SIGNAL(currentChanged(int)),
406 this, SLOT(updateWidgets()));
407 connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
408 this, SLOT(updateWidgets()));
409 connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
410 this, SLOT(updateWidgets()));
411 connect(&cap_file_, SIGNAL(captureFileClosing()),
412 this, SLOT(updateWidgets()));
415 registerTapListener("IAX2", this, NULL, 0, tapReset, tapPacket, tapDraw);
416 cap_file_.retapPackets();
417 removeTapListeners();
422 Iax2AnalysisDialog::~Iax2AnalysisDialog()
425 // remove_tap_listener_rtp_stream(&tapinfo);
426 delete fwd_tempfile_;
427 delete rev_tempfile_;
430 void Iax2AnalysisDialog::updateWidgets()
432 bool enable_tab = false;
433 QString hint = err_str_;
435 if (hint.isEmpty() || save_payload_error_ != TAP_IAX2_NO_ERROR) {
436 /* We cannot save the payload but can still display the widget
441 bool enable_nav = false;
443 && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
444 && ui->forwardTreeWidget->selectedItems().length() > 0)
445 || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
446 && ui->reverseTreeWidget->selectedItems().length() > 0))) {
449 ui->actionGoToPacket->setEnabled(enable_nav);
450 ui->actionNextProblem->setEnabled(enable_nav);
453 hint.append(tr(" G: Go to packet, N: Next problem packet"));
456 bool enable_save_fwd_audio = fwd_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
457 bool enable_save_rev_audio = rev_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
458 ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
459 ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio);
460 ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio);
462 bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
463 bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
464 ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
465 ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
466 ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
468 ui->tabWidget->setEnabled(enable_tab);
469 hint.prepend("<small><i>");
470 hint.append("</i></small>");
471 ui->hintLabel->setText(hint);
473 WiresharkDialog::updateWidgets();
476 void Iax2AnalysisDialog::on_actionGoToPacket_triggered()
478 if (file_closed_) return;
479 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
480 if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
482 QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
483 if (ti->type() != iax2_analysis_type_) return;
485 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
486 emit goToPacket(ra_ti->frameNum());
489 void Iax2AnalysisDialog::on_actionNextProblem_triggered()
491 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
492 if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
494 // Choose convenience over correctness.
495 if (cur_tree->selectedItems().length() < 1) {
496 cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
499 QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
500 if (sel_ti->type() != iax2_analysis_type_) return;
501 QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
502 while (test_ti != sel_ti) {
503 if (!test_ti) test_ti = cur_tree->topLevelItem(0);
504 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)test_ti);
505 if (!ra_ti->frameStatus()) {
506 cur_tree->setCurrentItem(ra_ti);
510 test_ti = cur_tree->itemBelow(test_ti);
514 void Iax2AnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
516 ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
520 void Iax2AnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
522 ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
526 void Iax2AnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
528 ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
532 void Iax2AnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
534 ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
538 void Iax2AnalysisDialog::on_actionSaveAudio_triggered()
540 saveAudio(dir_both_);
543 void Iax2AnalysisDialog::on_actionSaveForwardAudio_triggered()
545 saveAudio(dir_forward_);
548 void Iax2AnalysisDialog::on_actionSaveReverseAudio_triggered()
550 saveAudio(dir_reverse_);
553 void Iax2AnalysisDialog::on_actionSaveCsv_triggered()
558 void Iax2AnalysisDialog::on_actionSaveForwardCsv_triggered()
560 saveCsv(dir_forward_);
563 void Iax2AnalysisDialog::on_actionSaveReverseCsv_triggered()
565 saveCsv(dir_reverse_);
568 void Iax2AnalysisDialog::on_actionSaveGraph_triggered()
570 ui->tabWidget->setCurrentWidget(ui->graphTab);
572 QString file_name, extension;
573 QDir path(wsApp->lastOpenDir());
574 QString pdf_filter = tr("Portable Document Format (*.pdf)");
575 QString png_filter = tr("Portable Network Graphics (*.png)");
576 QString bmp_filter = tr("Windows Bitmap (*.bmp)");
577 // Gaze upon my beautiful graph with lossy artifacts!
578 QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
579 QString filter = QString("%1;;%2;;%3;;%4")
585 QString save_file = path.canonicalPath();
587 save_file += QString("/%1").arg(cap_file_.fileTitle());
589 file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
590 save_file, filter, &extension);
592 if (!file_name.isEmpty()) {
593 bool save_ok = false;
594 // http://www.qcustomplot.com/index.php/support/forum/63
595 // ui->streamGraph->legend->setVisible(true);
596 if (extension.compare(pdf_filter) == 0) {
597 save_ok = ui->streamGraph->savePdf(file_name);
598 } else if (extension.compare(png_filter) == 0) {
599 save_ok = ui->streamGraph->savePng(file_name);
600 } else if (extension.compare(bmp_filter) == 0) {
601 save_ok = ui->streamGraph->saveBmp(file_name);
602 } else if (extension.compare(jpeg_filter) == 0) {
603 save_ok = ui->streamGraph->saveJpg(file_name);
605 // ui->streamGraph->legend->setVisible(false);
606 // else error dialog?
608 path = QDir(file_name);
609 wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
614 void Iax2AnalysisDialog::on_buttonBox_helpRequested()
616 wsApp->helpTopicAction(HELP_IAX2_ANALYSIS_DIALOG);
619 void Iax2AnalysisDialog::tapReset(void *tapinfoptr)
621 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
622 if (!iax2_analysis_dialog) return;
624 iax2_analysis_dialog->resetStatistics();
627 gboolean Iax2AnalysisDialog::tapPacket(void *tapinfoptr, packet_info *pinfo, struct epan_dissect *, const void *iax2info_ptr)
629 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
630 if (!iax2_analysis_dialog) return FALSE;
632 const iax2_info_t *iax2info = (const iax2_info_t *)iax2info_ptr;
633 if (!iax2info) return FALSE;
635 /* we ignore packets that are not displayed */
636 if (pinfo->fd->flags.passed_dfilter == 0)
639 /* we ignore packets that carry no data */
640 if (iax2info->payload_len < 1)
643 /* is it the forward direction? */
644 else if ((cmp_address(&(iax2_analysis_dialog->src_fwd_), &(pinfo->src)) == 0)
645 && (iax2_analysis_dialog->port_src_fwd_ == pinfo->srcport)
646 && (cmp_address(&(iax2_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0)
647 && (iax2_analysis_dialog->port_dst_fwd_ == pinfo->destport)) {
649 iax2_analysis_dialog->addPacket(true, pinfo, iax2info);
651 /* is it the reversed direction? */
652 else if ((cmp_address(&(iax2_analysis_dialog->src_rev_), &(pinfo->src)) == 0)
653 && (iax2_analysis_dialog->port_src_rev_ == pinfo->srcport)
654 && (cmp_address(&(iax2_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0)
655 && (iax2_analysis_dialog->port_dst_rev_ == pinfo->destport)) {
657 iax2_analysis_dialog->addPacket(false, pinfo, iax2info);
662 void Iax2AnalysisDialog::tapDraw(void *tapinfoptr)
664 Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
665 if (!iax2_analysis_dialog) return;
666 iax2_analysis_dialog->updateStatistics();
669 void Iax2AnalysisDialog::resetStatistics()
671 memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
672 memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
674 fwd_statinfo_.first_packet = TRUE;
675 rev_statinfo_.first_packet = TRUE;
676 fwd_statinfo_.reg_pt = PT_UNDEFINED;
677 rev_statinfo_.reg_pt = PT_UNDEFINED;
679 ui->forwardTreeWidget->clear();
680 ui->reverseTreeWidget->clear();
682 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
683 ui->streamGraph->graph(i)->clearData();
686 fwd_time_vals_.clear();
687 fwd_jitter_vals_.clear();
688 fwd_diff_vals_.clear();
689 rev_time_vals_.clear();
690 rev_jitter_vals_.clear();
691 rev_diff_vals_.clear();
693 fwd_tempfile_->resize(0);
694 rev_tempfile_->resize(0);
697 void Iax2AnalysisDialog::addPacket(bool forward, packet_info *pinfo, const struct _iax2_info_t *iax2info)
699 /* add this RTP for future listening using the RTP Player*/
700 // add_rtp_packet(rtpinfo, pinfo);
703 iax2_packet_analyse(&fwd_statinfo_, pinfo, iax2info);
704 new Iax2AnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo);
706 fwd_time_vals_.append((fwd_statinfo_.time));
707 fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
708 fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
710 savePayload(fwd_tempfile_, pinfo, iax2info);
712 iax2_packet_analyse(&rev_statinfo_, pinfo, iax2info);
713 new Iax2AnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo);
715 rev_time_vals_.append((rev_statinfo_.time));
716 rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
717 rev_diff_vals_.append(rev_statinfo_.diff * 1000);
719 savePayload(rev_tempfile_, pinfo, iax2info);
724 // iax2_analysis.c:rtp_packet_save_payload
725 const guint8 silence_pcmu_ = 0xff;
726 const guint8 silence_pcma_ = 0x55;
727 void Iax2AnalysisDialog::savePayload(QTemporaryFile *tmpfile, packet_info *pinfo, const struct _iax2_info_t *iax2info)
729 /* Is this the first packet we got in this direction? */
730 // if (statinfo->flags & STAT_FLAG_FIRST) {
731 // if (saveinfo->fp == NULL) {
732 // saveinfo->saved = FALSE;
733 // saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
735 // saveinfo->saved = TRUE;
739 /* Save the voice information */
741 /* If there was already an error, we quit */
742 if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return;
744 /* Quit if the captured length and packet length aren't equal.
746 if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
748 err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
749 save_payload_error_ = TAP_IAX2_WRONG_LENGTH;
753 if (iax2info->payload_len > 0) {
754 const char *data = (const char *) iax2info->payload_data;
757 nchars = tmpfile->write(data, iax2info->payload_len);
758 if (nchars != (iax2info->payload_len)) {
759 /* Write error or short write */
760 err_str_ = tr("Can't save in a file: File I/O problem.");
761 save_payload_error_ = TAP_IAX2_FILE_IO_ERROR;
770 void Iax2AnalysisDialog::updateStatistics()
772 double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; // s
773 double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
774 #if 0 // Marked as "TODO" in tap-iax2-analysis.c:128
775 unsigned int f_expected = fwd_statinfo_.stop_seq_nr - fwd_statinfo_.start_seq_nr + 1;
776 unsigned int r_expected = rev_statinfo_.stop_seq_nr - rev_statinfo_.start_seq_nr + 1;
777 int f_lost = f_expected - fwd_statinfo_.total_nr;
778 int r_lost = r_expected - rev_statinfo_.total_nr;
779 double f_perc, r_perc;
782 f_perc = (double)(f_lost*100)/(double)f_expected;
787 r_perc = (double)(r_lost*100)/(double)r_expected;
793 QString stats_tables = "<html><head></head><body>\n";
794 stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
795 .arg(address_to_qstring(&src_fwd_, true))
797 stats_tables += QString("<br>%1:%2</p>\n")
798 .arg(address_to_qstring(&dst_fwd_, true))
800 stats_tables += "<h4>Forward</h4>\n";
801 stats_tables += "<p><table>\n";
802 stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
803 .arg(fwd_statinfo_.max_delta, 0, 'f', 2)
804 .arg(fwd_statinfo_.max_nr);
805 stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
806 .arg(fwd_statinfo_.max_jitter, 0, 'f', 2);
807 stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
808 .arg(fwd_statinfo_.mean_jitter, 0, 'f', 2);
809 stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
810 .arg(fwd_statinfo_.total_nr);
812 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
814 stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
815 .arg(f_lost).arg(f_perc, 0, 'f', 2);
816 stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
817 .arg(fwd_statinfo_.sequence);
819 stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
820 .arg(f_duration, 0, 'f', 2);
821 stats_tables += "</table></p>\n";
823 stats_tables += "<h4>Reverse</h4>\n";
824 stats_tables += "<p><table>\n";
825 stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
826 .arg(rev_statinfo_.max_delta, 0, 'f', 2)
827 .arg(rev_statinfo_.max_nr);
828 stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
829 .arg(rev_statinfo_.max_jitter, 0, 'f', 2);
830 stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
831 .arg(rev_statinfo_.mean_jitter, 0, 'f', 2);
832 stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
833 .arg(rev_statinfo_.total_nr);
835 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
837 stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
838 .arg(r_lost).arg(r_perc, 0, 'f', 2);
839 stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
840 .arg(rev_statinfo_.sequence);
842 stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
843 .arg(r_duration, 0, 'f', 2);
844 stats_tables += "</table></p></body>\n";
846 ui->statisticsLabel->setText(stats_tables);
848 for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
849 ui->forwardTreeWidget->resizeColumnToContents(col);
850 ui->reverseTreeWidget->resizeColumnToContents(col);
853 graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
854 graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
855 graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
856 graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
863 void Iax2AnalysisDialog::updateGraph()
865 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
866 if (ui->streamGraph->graph(i)->visible()) {
867 ui->streamGraph->graph(i)->rescaleAxes(i > 0);
870 ui->streamGraph->replot();
873 // iax2_analysis.c:copy_file
874 enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
875 void Iax2AnalysisDialog::saveAudio(Iax2AnalysisDialog::StreamDirection direction)
877 if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
883 caption = tr("Save forward stream audio");
886 caption = tr("Save reverse stream audio");
890 caption = tr("Save audio");
894 QString ext_filter = tr("Sun Audio (*.au)");
895 if (direction != dir_both_) {
896 ext_filter.append(tr(";;Raw (*.raw)"));
899 QString file_path = QFileDialog::getSaveFileName(
900 this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
901 ext_filter, &sel_filter);
903 if (file_path.isEmpty()) return;
905 int save_format = save_audio_none_;
906 if (file_path.endsWith(".au")) {
907 save_format = save_audio_au_;
908 } else if (file_path.endsWith(".raw")) {
909 save_format = save_audio_raw_;
912 if (save_format == save_audio_none_) {
913 QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
917 QFile save_file(file_path);
920 gboolean stop_flag = FALSE;
923 save_file.open(QIODevice::WriteOnly);
924 fwd_tempfile_->seek(0);
925 rev_tempfile_->seek(0);
927 if (save_file.error() != QFile::NoError) {
928 QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
932 ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
933 ui->progressFrame->showProgress(true, true, &stop_flag);
935 if (save_format == save_audio_au_) { /* au format */
936 /* First we write the .au header. XXX Hope this is endian independent */
937 /* the magic word 0x2e736e64 == .snd */
938 phton32(pd, 0x2e736e64);
939 nchars = save_file.write((const char *)pd, 4);
942 /* header offset == 24 bytes */
944 nchars = save_file.write((const char *)pd, 4);
947 /* total length; it is permitted to set this to 0xffffffff */
948 phton32(pd, 0xffffffff);
949 nchars = save_file.write((const char *)pd, 4);
952 /* encoding format == 16-bit linear PCM */
954 nchars = save_file.write((const char *)pd, 4);
957 /* sample rate == 8000 Hz */
959 nchars = save_file.write((const char *)pd, 4);
964 nchars = save_file.write((const char *)pd, 4);
969 /* Only forward direction */
973 while (fwd_tempfile_->getChar(&f_rawvalue)) {
977 ui->progressFrame->setValue(int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size()));
979 if (fwd_statinfo_.pt == PT_PCMU) {
980 sample = ulaw2linear((unsigned char)f_rawvalue);
982 } else if (fwd_statinfo_.pt == PT_PCMA) {
983 sample = alaw2linear((unsigned char)f_rawvalue);
989 nchars = save_file.write((const char *)pd, 2);
996 /* Only reverse direction */
1000 while (rev_tempfile_->getChar(&r_rawvalue)) {
1004 ui->progressFrame->setValue(int(rev_tempfile_->pos() * 100 / rev_tempfile_->size()));
1006 if (rev_statinfo_.pt == PT_PCMU) {
1007 sample = ulaw2linear((unsigned char)r_rawvalue);
1008 phton16(pd, sample);
1009 } else if (rev_statinfo_.pt == PT_PCMA) {
1010 sample = alaw2linear((unsigned char)r_rawvalue);
1011 phton16(pd, sample);
1016 nchars = save_file.write((const char *)pd, 2);
1023 /* Both directions */
1026 char f_rawvalue, r_rawvalue;
1027 guint32 f_write_silence = 0;
1028 guint32 r_write_silence = 0;
1029 /* since conversation in one way can start later than in the other one,
1030 * we have to write some silence information for one channel */
1031 if (fwd_statinfo_.start_time > rev_statinfo_.start_time) {
1032 f_write_silence = (guint32)
1033 ((fwd_statinfo_.start_time - rev_statinfo_.start_time)
1035 } else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) {
1036 r_write_silence = (guint32)
1037 ((rev_statinfo_.start_time - fwd_statinfo_.start_time)
1044 int fwd_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1045 int rev_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1046 ui->progressFrame->setValue(qMin(fwd_pct, rev_pct));
1048 if (f_write_silence > 0) {
1049 rev_tempfile_->getChar(&r_rawvalue);
1050 switch (fwd_statinfo_.reg_pt) {
1052 f_rawvalue = silence_pcmu_;
1055 f_rawvalue = silence_pcma_;
1062 } else if (r_write_silence > 0) {
1063 fwd_tempfile_->getChar(&f_rawvalue);
1064 switch (rev_statinfo_.reg_pt) {
1066 r_rawvalue = silence_pcmu_;
1069 r_rawvalue = silence_pcma_;
1077 fwd_tempfile_->getChar(&f_rawvalue);
1078 rev_tempfile_->getChar(&r_rawvalue);
1080 if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd())
1082 if ((fwd_statinfo_.pt == PT_PCMU)
1083 && (rev_statinfo_.pt == PT_PCMU)) {
1084 sample = (ulaw2linear((unsigned char)r_rawvalue)
1085 + ulaw2linear((unsigned char)f_rawvalue)) / 2;
1086 phton16(pd, sample);
1088 else if ((fwd_statinfo_.pt == PT_PCMA)
1089 && (rev_statinfo_.pt == PT_PCMA)) {
1090 sample = (alaw2linear((unsigned char)r_rawvalue)
1091 + alaw2linear((unsigned char)f_rawvalue)) / 2;
1092 phton16(pd, sample);
1097 nchars = save_file.write((const char *)pd, 2);
1104 } else if (save_format == save_audio_raw_) { /* raw format */
1108 switch (direction) {
1109 /* Only forward direction */
1110 case dir_forward_: {
1111 progress_pct = int(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
1112 tempfile = fwd_tempfile_;
1115 /* only reversed direction */
1116 case dir_reverse_: {
1117 progress_pct = int(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
1118 tempfile = rev_tempfile_;
1126 int chunk_size = 65536;
1127 /* XXX how do you just copy the file? */
1128 while (chunk_size > 0) {
1131 QByteArray bytes = tempfile->read(chunk_size);
1132 ui->progressFrame->setValue(progress_pct);
1134 if (!save_file.write(bytes)) {
1137 chunk_size = bytes.length();
1142 ui->progressFrame->hide();
1147 // XXX The GTK+ UI saves the length and timestamp.
1148 void Iax2AnalysisDialog::saveCsv(Iax2AnalysisDialog::StreamDirection direction)
1152 switch (direction) {
1154 caption = tr("Save forward stream CSV");
1157 caption = tr("Save reverse stream CSV");
1161 caption = tr("Save CSV");
1165 QString file_path = QFileDialog::getSaveFileName(
1166 this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
1167 tr("Comma-separated values (*.csv)"));
1169 if (file_path.isEmpty()) return;
1171 QFile save_file(file_path);
1172 save_file.open(QFile::WriteOnly);
1174 if (direction == dir_forward_ || direction == dir_both_) {
1175 save_file.write("Forward\n");
1177 for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
1178 QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
1179 if (ti->type() != iax2_analysis_type_) continue;
1180 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1182 foreach (QVariant v, ra_ti->rowData()) {
1185 } else if ((int) v.type() == (int) QMetaType::QString) {
1186 values << QString("\"%1\"").arg(v.toString());
1188 values << v.toString();
1191 save_file.write(values.join(",").toUtf8());
1192 save_file.write("\n");
1195 if (direction == dir_both_) {
1196 save_file.write("\n");
1198 if (direction == dir_reverse_ || direction == dir_both_) {
1199 save_file.write("Reverse\n");
1201 for (int row = 0; row < ui->reverseTreeWidget->topLevelItemCount(); row++) {
1202 QTreeWidgetItem *ti = ui->reverseTreeWidget->topLevelItem(row);
1203 if (ti->type() != iax2_analysis_type_) continue;
1204 Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
1206 foreach (QVariant v, ra_ti->rowData()) {
1209 } else if ((int) v.type() == (int) QMetaType::QString) {
1210 values << QString("\"%1\"").arg(v.toString());
1212 values << v.toString();
1215 save_file.write(values.join(",").toUtf8());
1216 save_file.write("\n");
1221 bool Iax2AnalysisDialog::eventFilter(QObject *, QEvent *event)
1223 if (event->type() != QEvent::KeyPress) return false;
1225 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
1227 switch(kevt->key()) {
1229 on_actionGoToPacket_triggered();
1232 on_actionNextProblem_triggered();
1240 void Iax2AnalysisDialog::graphClicked(QMouseEvent *event)
1243 if (event->button() == Qt::RightButton) {
1244 graph_ctx_menu_.exec(event->globalPos());
1248 void Iax2AnalysisDialog::showStreamMenu(QPoint pos)
1250 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
1251 if (!cur_tree) return;
1254 stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1263 * indent-tabs-mode: nil
1266 * ex: set shiftwidth=4 tabstop=8 expandtab:
1267 * :indentSize=4:tabSize=8:noTabs=true: