summaryrefslogtreecommitdiff
path: root/ui/qt/iax2_analysis_dialog.cpp
diff options
context:
space:
mode:
authorGerald Combs <gerald@wireshark.org>2015-09-03 11:18:13 -0700
committerGerald Combs <gerald@wireshark.org>2015-09-09 21:57:08 +0000
commitcd9f163eb91d4c70977f669472d5acaf7a4fbe7c (patch)
tree268428ad90ed1a46ae67df78c3124fc751e40b2d /ui/qt/iax2_analysis_dialog.cpp
parent302b03a0bbe3702f93eced884d8103528c35e8d7 (diff)
downloadwireshark-cd9f163eb91d4c70977f669472d5acaf7a4fbe7c.tar.gz
Add the IAX2 Analysis dialog.
Copied from the RTP Analysis dialog, just like the GTK+ version. Change-Id: I111020bc4073a3a3ba583bdace51a91ee5fef300 Reviewed-on: https://code.wireshark.org/review/10447 Petri-Dish: Gerald Combs <gerald@wireshark.org> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt/iax2_analysis_dialog.cpp')
-rw-r--r--ui/qt/iax2_analysis_dialog.cpp1330
1 files changed, 1330 insertions, 0 deletions
diff --git a/ui/qt/iax2_analysis_dialog.cpp b/ui/qt/iax2_analysis_dialog.cpp
new file mode 100644
index 0000000000..6b166a148f
--- /dev/null
+++ b/ui/qt/iax2_analysis_dialog.cpp
@@ -0,0 +1,1330 @@
+/* iax2_analysis_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "iax2_analysis_dialog.h"
+#include "ui_iax2_analysis_dialog.h"
+
+#include "file.h"
+#include "frame_tvbuff.h"
+
+#include <epan/epan_dissect.h>
+#include <epan/rtp_pt.h>
+
+#include <epan/dfilter/dfilter.h>
+
+#include <epan/dissectors/packet-iax2.h>
+
+#include "ui/help_url.h"
+#ifdef IAX2_RTP_STREAM_CHECK
+#include "ui/rtp_stream.h"
+#endif
+#include "ui/utf8_entities.h"
+
+#include <wsutil/g711.h>
+#include <wsutil/pint.h>
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QTemporaryFile>
+
+#include "color_utils.h"
+#include "qt_ui_utils.h"
+#include "stock_icon.h"
+#include "wireshark_application.h"
+
+/*
+ * @file RTP stream analysis dialog
+ *
+ * Displays forward and reverse RTP streams and graphs each stream
+ */
+
+// To do:
+// - Progress bar for tapping and saving.
+// - Add a refresh button and/or action.
+// - Fixup output file names.
+// - Add a graph title and legend when saving?
+
+enum {
+ packet_col_,
+ delta_col_,
+ jitter_col_,
+ bandwidth_col_,
+ status_col_,
+ length_col_
+};
+
+static const QRgb color_rtp_warn_ = 0xffdbbf;
+
+enum { iax2_analysis_type_ = 1000 };
+class Iax2AnalysisTreeWidgetItem : public QTreeWidgetItem
+{
+public:
+ Iax2AnalysisTreeWidgetItem(QTreeWidget *tree, tap_iax2_stat_t *statinfo, packet_info *pinfo) :
+ QTreeWidgetItem(tree, iax2_analysis_type_)
+ {
+ frame_num_ = pinfo->fd->num;
+ pkt_len_ = pinfo->fd->pkt_len;
+ flags_ = statinfo->flags;
+ if (flags_ & STAT_FLAG_FIRST) {
+ delta_ = 0.0;
+ jitter_ = 0.0;
+ } else {
+ delta_ = statinfo->delta;
+ jitter_ = statinfo->jitter;
+ }
+ bandwidth_ = statinfo->bandwidth;
+ ok_ = false;
+
+ QColor bg_color = QColor();
+ QString status;
+
+ if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
+ status = QObject::tr("Wrong sequence number");
+ bg_color = ColorUtils::expert_color_error;
+ } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
+ status = QObject::tr("Payload changed to PT=%1").arg(statinfo->pt);
+ bg_color = color_rtp_warn_;
+ } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
+ status = QObject::tr("Incorrect timestamp");
+ /* color = COLOR_WARNING; */
+ bg_color = color_rtp_warn_;
+ } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
+ && !(statinfo->flags & STAT_FLAG_FIRST)
+ && !(statinfo->flags & STAT_FLAG_PT_CN)
+ && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
+ && !(statinfo->flags & STAT_FLAG_MARKER)) {
+ status = QObject::tr("Marker missing?");
+ bg_color = color_rtp_warn_;
+ } else {
+ if (statinfo->flags & STAT_FLAG_MARKER) {
+ bg_color = color_rtp_warn_;
+ }
+ }
+
+ if (status.isEmpty()) {
+ ok_ = true;
+ status = UTF8_CHECK_MARK;
+ }
+
+ setText(packet_col_, QString::number(frame_num_));
+ setText(delta_col_, QString::number(delta_, 'f', 2));
+ setText(jitter_col_, QString::number(jitter_, 'f', 2));
+ setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
+ setText(status_col_, status);
+ setText(length_col_, QString::number(pkt_len_));
+
+ setTextAlignment(packet_col_, Qt::AlignRight);
+ setTextAlignment(delta_col_, Qt::AlignRight);
+ setTextAlignment(jitter_col_, Qt::AlignRight);
+ setTextAlignment(bandwidth_col_, Qt::AlignRight);
+ setTextAlignment(length_col_, Qt::AlignRight);
+
+ if (bg_color.isValid()) {
+ for (int col = 0; col < columnCount(); col++) {
+ setBackground(col, bg_color);
+ setForeground(col, ColorUtils::expert_color_foreground);
+ }
+ }
+ }
+
+ guint32 frameNum() { return frame_num_; }
+ bool frameStatus() { return ok_; }
+
+ QList<QVariant> rowData() {
+ QString marker_str;
+ QString status_str = ok_ ? "OK" : text(status_col_);
+
+ if (marker_) marker_str = "SET";
+
+ return QList<QVariant>()
+ << frame_num_ << delta_ << jitter_ << bandwidth_
+ << status_str << pkt_len_;
+ }
+
+ bool operator< (const QTreeWidgetItem &other) const
+ {
+ if (other.type() != iax2_analysis_type_) return QTreeWidgetItem::operator< (other);
+ const Iax2AnalysisTreeWidgetItem *other_row = static_cast<const Iax2AnalysisTreeWidgetItem *>(&other);
+
+ switch (treeWidget()->sortColumn()) {
+ case (packet_col_):
+ return frame_num_ < other_row->frame_num_;
+ break;
+ case (delta_col_):
+ return delta_ < other_row->delta_;
+ break;
+ case (jitter_col_):
+ return jitter_ < other_row->jitter_;
+ break;
+ case (bandwidth_col_):
+ return bandwidth_ < other_row->bandwidth_;
+ break;
+ case (length_col_):
+ return pkt_len_ < other_row->pkt_len_;
+ break;
+ default:
+ break;
+ }
+
+ // Fall back to string comparison
+ return QTreeWidgetItem::operator <(other);
+ }
+private:
+ guint32 frame_num_;
+ guint32 pkt_len_;
+ guint32 flags_;
+ double delta_;
+ double jitter_;
+ double bandwidth_;
+ bool marker_;
+ bool ok_;
+};
+
+enum {
+ fwd_jitter_graph_,
+ fwd_diff_graph_,
+ rev_jitter_graph_,
+ rev_diff_graph_,
+ num_graphs_
+};
+
+Iax2AnalysisDialog::Iax2AnalysisDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf),
+ ui(new Ui::Iax2AnalysisDialog),
+ port_src_fwd_(0),
+ port_dst_fwd_(0),
+ port_src_rev_(0),
+ port_dst_rev_(0)
+{
+ ui->setupUi(this);
+ setWindowSubtitle(tr("IAX2 Stream Analysis"));
+
+ // XXX Use recent settings instead
+ resize(parent.width() * 4 / 5, parent.height() * 4 / 5);
+ ui->progressFrame->hide();
+
+ stream_ctx_menu_.addAction(ui->actionGoToPacket);
+ stream_ctx_menu_.addAction(ui->actionNextProblem);
+ stream_ctx_menu_.addSeparator();
+ stream_ctx_menu_.addAction(ui->actionSaveAudio);
+ stream_ctx_menu_.addAction(ui->actionSaveForwardAudio);
+ stream_ctx_menu_.addAction(ui->actionSaveReverseAudio);
+ stream_ctx_menu_.addSeparator();
+ stream_ctx_menu_.addAction(ui->actionSaveCsv);
+ stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
+ stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
+ stream_ctx_menu_.addSeparator();
+ stream_ctx_menu_.addAction(ui->actionSaveGraph);
+ ui->forwardTreeWidget->installEventFilter(this);
+ ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
+ SLOT(showStreamMenu(QPoint)));
+ ui->reverseTreeWidget->installEventFilter(this);
+ ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
+ SLOT(showStreamMenu(QPoint)));
+ connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
+ this, SLOT(graphClicked(QMouseEvent*)));
+
+ graph_ctx_menu_.addAction(ui->actionSaveGraph);
+
+ QStringList header_labels;
+ for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
+ header_labels << ui->forwardTreeWidget->headerItem()->text(i);
+ }
+ ui->reverseTreeWidget->setHeaderLabels(header_labels);
+
+ memset(&src_fwd_, 0, sizeof(address));
+ memset(&dst_fwd_, 0, sizeof(address));
+ memset(&src_rev_, 0, sizeof(address));
+ memset(&dst_rev_, 0, sizeof(address));
+
+ QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
+ << ui->fJitterCheckBox << ui->fDiffCheckBox
+ << ui->rJitterCheckBox << ui->rDiffCheckBox;
+
+ for (int i = 0; i < num_graphs_; i++) {
+ QCPGraph *graph = ui->streamGraph->addGraph();
+ graph->setPen(QPen(ColorUtils::graph_colors_[i]));
+ graph->setName(graph_cbs[i]->text());
+ graphs_ << graph;
+ graph_cbs[i]->setChecked(true);
+ graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graph_colors_[i], QPalette::Text));
+ }
+ ui->streamGraph->xAxis->setLabel("Arrival Time");
+ ui->streamGraph->yAxis->setLabel("Value (ms)");
+
+ // We keep our temp files open for the lifetime of the dialog. The GTK+
+ // UI opens and closes at various points.
+ QString tempname = QString("%1/wireshark_iax2_f").arg(QDir::tempPath());
+ fwd_tempfile_ = new QTemporaryFile(tempname, this);
+ fwd_tempfile_->open();
+ tempname = QString("%1/wireshark_iax2_r").arg(QDir::tempPath());
+ rev_tempfile_ = new QTemporaryFile(tempname, this);
+ rev_tempfile_->open();
+
+ if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
+ err_str_ = tr("Unable to save RTP data.");
+ ui->actionSaveAudio->setEnabled(false);
+ ui->actionSaveForwardAudio->setEnabled(false);
+ ui->actionSaveReverseAudio->setEnabled(false);
+ }
+
+ QMenu *save_menu = new QMenu();
+ save_menu->addAction(ui->actionSaveAudio);
+ save_menu->addAction(ui->actionSaveForwardAudio);
+ save_menu->addAction(ui->actionSaveReverseAudio);
+ save_menu->addSeparator();
+ save_menu->addAction(ui->actionSaveCsv);
+ save_menu->addAction(ui->actionSaveForwardCsv);
+ save_menu->addAction(ui->actionSaveReverseCsv);
+ save_menu->addSeparator();
+ save_menu->addAction(ui->actionSaveGraph);
+ ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu);
+
+ const gchar *filter_text = "iax2 && (ip || ipv6)";
+ dfilter_t *sfcode;
+ gchar *err_msg;
+
+ if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
+ QMessageBox::warning(this, tr("No IAX2 packets found"), QString("%1").arg(err_msg));
+ g_free(err_msg);
+ close();
+ }
+
+ if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close();
+
+ frame_data *fdata = cap_file_.capFile()->current_frame;
+
+ if (!cf_read_record(cap_file_.capFile(), fdata)) close();
+
+ epan_dissect_t edt;
+
+ epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
+ epan_dissect_prime_dfilter(&edt, sfcode);
+ epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->phdr,
+ frame_tvbuff_new_buffer(fdata, &cap_file_.capFile()->buf), fdata, NULL);
+
+ // This shouldn't happen (the menu item should be disabled) but check anyway
+ if (!dfilter_apply_edt(sfcode, &edt)) {
+ epan_dissect_cleanup(&edt);
+ dfilter_free(sfcode);
+ err_str_ = tr("Please select an IAX2 packet");
+ updateWidgets();
+ return;
+ }
+
+ dfilter_free(sfcode);
+
+ /* ok, it is a IAX2 frame, so let's get the ip and port values */
+ COPY_ADDRESS(&(src_fwd_), &(edt.pi.src));
+ COPY_ADDRESS(&(dst_fwd_), &(edt.pi.dst));
+ port_src_fwd_ = edt.pi.srcport;
+ port_dst_fwd_ = edt.pi.destport;
+
+ /* assume the inverse ip/port combination for the reverse direction */
+ COPY_ADDRESS(&(src_rev_), &(edt.pi.dst));
+ COPY_ADDRESS(&(dst_rev_), &(edt.pi.src));
+ port_src_rev_ = edt.pi.destport;
+ port_dst_rev_ = edt.pi.srcport;
+
+#if 0
+ /* check if it is Voice or MiniPacket */
+ bool ok;
+ getIntFromProtoTree(edt.tree, "iax2", "iax2.call", &ok);
+ if (!ok) {
+ err_str_ = tr("Please select an IAX2 packet.");
+ updateWidgets();
+ return;
+ }
+#endif
+
+#ifdef IAX2_RTP_STREAM_CHECK
+ rtpstream_tapinfot tapinfo;
+
+ /* Register the tap listener */
+ memset(&tapinfo, 0, sizeof(rtpstream_tapinfot));
+ tapinfo.tap_data = this;
+ tapinfo.mode = TAP_ANALYSE;
+
+// register_tap_listener_rtp_stream(&tapinfo, NULL);
+ /* Scan for RTP streams (redissect all packets) */
+ rtpstream_scan(&tapinfo, cap_file_.capFile(), NULL);
+
+ int num_streams = 0;
+ GList *filtered_list = NULL;
+ for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) {
+ rtp_stream_info_t * strinfo = (rtp_stream_info_t*)(strinfo_list->data);
+ << address_to_qstring(&strinfo->dest_addr) << address_to_qstring(&src_rev_) << address_to_qstring(&dst_rev_);
+ if (ADDRESSES_EQUAL(&(strinfo->src_addr), &(src_fwd_))
+ && (strinfo->src_port == port_src_fwd_)
+ && (ADDRESSES_EQUAL(&(strinfo->dest_addr), &(dst_fwd_)))
+ && (strinfo->dest_port == port_dst_fwd_))
+ {
+ ++num_streams;
+ filtered_list = g_list_prepend(filtered_list, strinfo);
+ }
+
+ if (ADDRESSES_EQUAL(&(strinfo->src_addr), &(src_rev_))
+ && (strinfo->src_port == port_src_rev_)
+ && (ADDRESSES_EQUAL(&(strinfo->dest_addr), &(dst_rev_)))
+ && (strinfo->dest_port == port_dst_rev_))
+ {
+ ++num_streams;
+ filtered_list = g_list_append(filtered_list, strinfo);
+ }
+ }
+
+ if (num_streams > 1) {
+ // Open the RTP streams dialog.
+ }
+#endif
+
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)),
+ this, SLOT(updateWidgets()));
+ connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
+ this, SLOT(updateWidgets()));
+ connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
+ this, SLOT(updateWidgets()));
+ connect(&cap_file_, SIGNAL(captureFileClosing()),
+ this, SLOT(updateWidgets()));
+ updateWidgets();
+
+ registerTapListener("IAX2", this, NULL, 0, tapReset, tapPacket, tapDraw);
+ cap_file_.retapPackets();
+ removeTapListeners();
+
+ updateStatistics();
+}
+
+Iax2AnalysisDialog::~Iax2AnalysisDialog()
+{
+ delete ui;
+// remove_tap_listener_rtp_stream(&tapinfo);
+ delete fwd_tempfile_;
+ delete rev_tempfile_;
+}
+
+void Iax2AnalysisDialog::updateWidgets()
+{
+ bool enable_tab = false;
+ QString hint = err_str_;
+
+ if (hint.isEmpty()) {
+ enable_tab = true;
+ }
+
+ bool enable_nav = false;
+ if (!file_closed_
+ && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
+ && ui->forwardTreeWidget->selectedItems().length() > 0)
+ || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
+ && ui->reverseTreeWidget->selectedItems().length() > 0))) {
+ enable_nav = true;
+ }
+ ui->actionGoToPacket->setEnabled(enable_nav);
+ ui->actionNextProblem->setEnabled(enable_nav);
+
+ if (enable_nav) {
+ hint.append(tr(" G: Go to packet, N: Next problem packet"));
+ }
+
+ bool enable_save_fwd_audio = fwd_tempfile_->isOpen();
+ bool enable_save_rev_audio = rev_tempfile_->isOpen();
+ ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
+ ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio);
+ ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio);
+
+ bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
+ bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
+ ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
+ ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
+ ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
+
+ ui->tabWidget->setEnabled(enable_tab);
+ hint.prepend("<small><i>");
+ hint.append("</i></small>");
+ ui->hintLabel->setText(hint);
+}
+
+void Iax2AnalysisDialog::on_actionGoToPacket_triggered()
+{
+ if (file_closed_) return;
+ QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
+ if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
+
+ QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
+ if (ti->type() != iax2_analysis_type_) return;
+
+ Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
+ emit goToPacket(ra_ti->frameNum());
+}
+
+void Iax2AnalysisDialog::on_actionNextProblem_triggered()
+{
+ QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
+ if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
+
+ // Choose convenience over correctness.
+ if (cur_tree->selectedItems().length() < 1) {
+ cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
+ }
+
+ QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
+ if (sel_ti->type() != iax2_analysis_type_) return;
+ QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
+ while (test_ti != sel_ti) {
+ if (!test_ti) test_ti = cur_tree->topLevelItem(0);
+ Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)test_ti);
+ if (!ra_ti->frameStatus()) {
+ cur_tree->setCurrentItem(ra_ti);
+ break;
+ }
+
+ test_ti = cur_tree->itemBelow(test_ti);
+ }
+}
+
+void Iax2AnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void Iax2AnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void Iax2AnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void Iax2AnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void Iax2AnalysisDialog::on_actionSaveAudio_triggered()
+{
+ saveAudio(dir_both_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveForwardAudio_triggered()
+{
+ saveAudio(dir_forward_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveReverseAudio_triggered()
+{
+ saveAudio(dir_reverse_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveCsv_triggered()
+{
+ saveCsv(dir_both_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveForwardCsv_triggered()
+{
+ saveCsv(dir_forward_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveReverseCsv_triggered()
+{
+ saveCsv(dir_reverse_);
+}
+
+void Iax2AnalysisDialog::on_actionSaveGraph_triggered()
+{
+ ui->tabWidget->setCurrentWidget(ui->graphTab);
+
+ QString file_name, extension;
+ QDir path(wsApp->lastOpenDir());
+ QString pdf_filter = tr("Portable Document Format (*.pdf)");
+ QString png_filter = tr("Portable Network Graphics (*.png)");
+ QString bmp_filter = tr("Windows Bitmap (*.bmp)");
+ // Gaze upon my beautiful graph with lossy artifacts!
+ QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
+ QString filter = QString("%1;;%2;;%3;;%4")
+ .arg(pdf_filter)
+ .arg(png_filter)
+ .arg(bmp_filter)
+ .arg(jpeg_filter);
+
+ QString save_file = path.canonicalPath();
+ if (!file_closed_) {
+ save_file += QString("/%1").arg(cap_file_.fileTitle());
+ }
+ file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
+ save_file, filter, &extension);
+
+ if (!file_name.isEmpty()) {
+ bool save_ok = false;
+ // http://www.qcustomplot.com/index.php/support/forum/63
+// ui->streamGraph->legend->setVisible(true);
+ if (extension.compare(pdf_filter) == 0) {
+ save_ok = ui->streamGraph->savePdf(file_name);
+ } else if (extension.compare(png_filter) == 0) {
+ save_ok = ui->streamGraph->savePng(file_name);
+ } else if (extension.compare(bmp_filter) == 0) {
+ save_ok = ui->streamGraph->saveBmp(file_name);
+ } else if (extension.compare(jpeg_filter) == 0) {
+ save_ok = ui->streamGraph->saveJpg(file_name);
+ }
+// ui->streamGraph->legend->setVisible(false);
+ // else error dialog?
+ if (save_ok) {
+ path = QDir(file_name);
+ wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
+ }
+ }
+}
+
+void Iax2AnalysisDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_IAX2_ANALYSIS_DIALOG);
+}
+
+void Iax2AnalysisDialog::tapReset(void *tapinfoptr)
+{
+ Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
+ if (!iax2_analysis_dialog) return;
+
+ iax2_analysis_dialog->resetStatistics();
+}
+
+gboolean Iax2AnalysisDialog::tapPacket(void *tapinfoptr, packet_info *pinfo, struct epan_dissect *, const void *iax2info_ptr)
+{
+ Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
+ if (!iax2_analysis_dialog) return FALSE;
+
+ const iax2_info_t *iax2info = (const iax2_info_t *)iax2info_ptr;
+ if (!iax2info) return FALSE;
+
+ /* we ignore packets that are not displayed */
+ if (pinfo->fd->flags.passed_dfilter == 0)
+ return FALSE;
+
+ /* we ignore packets that carry no data */
+ if (iax2info->payload_len < 1)
+ return FALSE;
+
+ /* is it the forward direction? */
+ else if ((CMP_ADDRESS(&(iax2_analysis_dialog->src_fwd_), &(pinfo->src)) == 0)
+ && (iax2_analysis_dialog->port_src_fwd_ == pinfo->srcport)
+ && (CMP_ADDRESS(&(iax2_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0)
+ && (iax2_analysis_dialog->port_dst_fwd_ == pinfo->destport)) {
+
+ iax2_analysis_dialog->addPacket(true, pinfo, iax2info);
+ }
+ /* is it the reversed direction? */
+ else if ((CMP_ADDRESS(&(iax2_analysis_dialog->src_rev_), &(pinfo->src)) == 0)
+ && (iax2_analysis_dialog->port_src_rev_ == pinfo->srcport)
+ && (CMP_ADDRESS(&(iax2_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0)
+ && (iax2_analysis_dialog->port_dst_rev_ == pinfo->destport)) {
+
+ iax2_analysis_dialog->addPacket(false, pinfo, iax2info);
+ }
+ return FALSE;
+}
+
+void Iax2AnalysisDialog::tapDraw(void *tapinfoptr)
+{
+ Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
+ if (!iax2_analysis_dialog) return;
+ iax2_analysis_dialog->updateStatistics();
+}
+
+void Iax2AnalysisDialog::resetStatistics()
+{
+ memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
+ memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
+
+ fwd_statinfo_.first_packet = TRUE;
+ rev_statinfo_.first_packet = TRUE;
+ fwd_statinfo_.reg_pt = PT_UNDEFINED;
+ rev_statinfo_.reg_pt = PT_UNDEFINED;
+
+ ui->forwardTreeWidget->clear();
+ ui->reverseTreeWidget->clear();
+
+ for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
+ ui->streamGraph->graph(i)->clearData();
+ }
+
+ fwd_time_vals_.clear();
+ fwd_jitter_vals_.clear();
+ fwd_diff_vals_.clear();
+ rev_time_vals_.clear();
+ rev_jitter_vals_.clear();
+ rev_diff_vals_.clear();
+
+ fwd_tempfile_->resize(0);
+ rev_tempfile_->resize(0);
+}
+
+void Iax2AnalysisDialog::addPacket(bool forward, packet_info *pinfo, const struct _iax2_info_t *iax2info)
+{
+ /* add this RTP for future listening using the RTP Player*/
+// add_rtp_packet(rtpinfo, pinfo);
+
+ if (forward) {
+ iax2_packet_analyse(&fwd_statinfo_, pinfo, iax2info);
+ new Iax2AnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo);
+
+ fwd_time_vals_.append((fwd_statinfo_.time - fwd_statinfo_.start_time));
+ fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
+ fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
+
+ savePayload(fwd_tempfile_, pinfo, iax2info);
+ } else {
+ iax2_packet_analyse(&rev_statinfo_, pinfo, iax2info);
+ new Iax2AnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo);
+
+ rev_time_vals_.append((rev_statinfo_.time - rev_statinfo_.start_time));
+ rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
+ rev_diff_vals_.append(rev_statinfo_.diff * 1000);
+
+ savePayload(rev_tempfile_, pinfo, iax2info);
+ }
+
+}
+
+// iax2_analysis.c:rtp_packet_save_payload
+const guint8 silence_pcmu_ = 0xff;
+const guint8 silence_pcma_ = 0x55;
+void Iax2AnalysisDialog::savePayload(QTemporaryFile *tmpfile, packet_info *pinfo, const struct _iax2_info_t *iax2info)
+{
+ /* Is this the first packet we got in this direction? */
+// if (statinfo->flags & STAT_FLAG_FIRST) {
+// if (saveinfo->fp == NULL) {
+// saveinfo->saved = FALSE;
+// saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
+// } else {
+// saveinfo->saved = TRUE;
+// }
+// }
+
+ /* Save the voice information */
+
+ /* If there was already an error, we quit */
+ if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return;
+
+ /* Quit if the captured length and packet length aren't equal.
+ */
+ if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
+ tmpfile->close();
+ err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
+ return;
+ }
+
+ if (iax2info->payload_len > 0) {
+ const char *data = (const char *) iax2info->payload_data;
+ size_t nchars;
+
+ nchars = tmpfile->write(data, iax2info->payload_len);
+ if (nchars != (iax2info->payload_len)) {
+ /* Write error or short write */
+ err_str_ = tr("Can't save in a file: File I/O problem.");
+ tmpfile->close();
+ return;
+ }
+ return;
+ }
+ return;
+}
+
+void Iax2AnalysisDialog::updateStatistics()
+{
+ double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; // s
+ double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
+#if 0 // Marked as "TODO" in tap-iax2-analysis.c:128
+ unsigned int f_expected = fwd_statinfo_.stop_seq_nr - fwd_statinfo_.start_seq_nr + 1;
+ unsigned int r_expected = rev_statinfo_.stop_seq_nr - rev_statinfo_.start_seq_nr + 1;
+ int f_lost = f_expected - fwd_statinfo_.total_nr;
+ int r_lost = r_expected - rev_statinfo_.total_nr;
+ double f_perc, r_perc;
+
+ if (f_expected) {
+ f_perc = (double)(f_lost*100)/(double)f_expected;
+ } else {
+ f_perc = 0;
+ }
+ if (r_expected) {
+ r_perc = (double)(r_lost*100)/(double)r_expected;
+ } else {
+ r_perc = 0;
+ }
+#endif
+
+ QString stats_tables = "<html><head></head><body>\n";
+ stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
+ .arg(address_to_qstring(&src_fwd_, true))
+ .arg(port_src_fwd_);
+ stats_tables += QString("<br>%1:%2</p>\n")
+ .arg(address_to_qstring(&dst_fwd_, true))
+ .arg(port_dst_fwd_);
+ stats_tables += "<h4>Forward</h4>\n";
+ stats_tables += "<p><table>\n";
+ stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
+ .arg(fwd_statinfo_.max_delta, 0, 'f', 2)
+ .arg(fwd_statinfo_.max_nr);
+ stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
+ .arg(fwd_statinfo_.max_jitter, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
+ .arg(fwd_statinfo_.mean_jitter, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
+ .arg(fwd_statinfo_.total_nr);
+#if 0
+ stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
+ .arg(f_expected);
+ stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
+ .arg(f_lost).arg(f_perc, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
+ .arg(fwd_statinfo_.sequence);
+#endif
+ stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
+ .arg(f_duration, 0, 'f', 2);
+ stats_tables += "</table></p>\n";
+
+ stats_tables += "<h4>Reverse</h4>\n";
+ stats_tables += "<p><table>\n";
+ stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
+ .arg(rev_statinfo_.max_delta, 0, 'f', 2)
+ .arg(rev_statinfo_.max_nr);
+ stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
+ .arg(rev_statinfo_.max_jitter, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
+ .arg(rev_statinfo_.mean_jitter, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
+ .arg(rev_statinfo_.total_nr);
+#if 0
+ stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
+ .arg(r_expected);
+ stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
+ .arg(r_lost).arg(r_perc, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
+ .arg(rev_statinfo_.sequence);
+#endif
+ stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
+ .arg(r_duration, 0, 'f', 2);
+ stats_tables += "</table></p></body>\n";
+
+ ui->statisticsLabel->setText(stats_tables);
+
+ for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
+ ui->forwardTreeWidget->resizeColumnToContents(col);
+ ui->reverseTreeWidget->resizeColumnToContents(col);
+ }
+
+ graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
+ graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
+ graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
+ graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
+
+ updateGraph();
+
+ updateWidgets();
+}
+
+void Iax2AnalysisDialog::updateGraph()
+{
+ for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
+ if (ui->streamGraph->graph(i)->visible()) {
+ ui->streamGraph->graph(i)->rescaleAxes(i > 0);
+ }
+ }
+ ui->streamGraph->replot();
+}
+
+// iax2_analysis.c:copy_file
+enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
+void Iax2AnalysisDialog::saveAudio(Iax2AnalysisDialog::StreamDirection direction)
+{
+ if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
+
+ QString caption;
+
+ switch (direction) {
+ case dir_forward_:
+ caption = tr("Save forward stream audio");
+ break;
+ case dir_reverse_:
+ caption = tr("Save reverse stream audio");
+ break;
+ case dir_both_:
+ default:
+ caption = tr("Save audio");
+ break;
+ }
+
+ QString ext_filter = tr("Sun Audio (*.au)");
+ if (direction != dir_both_) {
+ ext_filter.append(tr(";;Raw (*.raw)"));
+ }
+ QString sel_filter;
+ QString file_path = QFileDialog::getSaveFileName(
+ this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
+ ext_filter, &sel_filter);
+
+ if (file_path.isEmpty()) return;
+
+ int save_format = save_audio_none_;
+ if (file_path.endsWith(".au")) {
+ save_format = save_audio_au_;
+ } else if (file_path.endsWith(".raw")) {
+ save_format = save_audio_raw_;
+ }
+
+ if (save_format == save_audio_none_) {
+ QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
+ return;
+ }
+
+ QFile save_file(file_path);
+ gint16 sample;
+ gchar pd[4];
+ gboolean stop_flag = FALSE;
+ size_t nchars;
+
+ save_file.open(QIODevice::WriteOnly);
+ fwd_tempfile_->seek(0);
+ rev_tempfile_->seek(0);
+
+ if (save_file.error() != QFile::NoError) {
+ QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
+ return;
+ }
+
+ ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
+ ui->progressFrame->showProgress(true, true, &stop_flag);
+
+ if (save_format == save_audio_au_) { /* au format */
+ /* First we write the .au header. XXX Hope this is endian independent */
+ /* the magic word 0x2e736e64 == .snd */
+ phton32(pd, 0x2e736e64);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+ /* header offset == 24 bytes */
+ phton32(pd, 24);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+ /* total length; it is permitted to set this to 0xffffffff */
+ phton32(pd, 0xffffffff);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+ /* encoding format == 16-bit linear PCM */
+ phton32(pd, 3);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+ /* sample rate == 8000 Hz */
+ phton32(pd, 8000);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+ /* channels == 1 */
+ phton32(pd, 1);
+ nchars = save_file.write((const char *)pd, 4);
+ if (nchars != 4)
+ goto copy_file_err;
+
+ switch (direction) {
+ /* Only forward direction */
+ case dir_forward_:
+ {
+ char f_rawvalue;
+ while (fwd_tempfile_->getChar(&f_rawvalue)) {
+ if (stop_flag) {
+ break;
+ }
+ ui->progressFrame->setValue(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
+
+ if (fwd_statinfo_.pt == PT_PCMU) {
+ sample = ulaw2linear((unsigned char)f_rawvalue);
+ phton16(pd, sample);
+ } else if (fwd_statinfo_.pt == PT_PCMA) {
+ sample = alaw2linear((unsigned char)f_rawvalue);
+ phton16(pd, sample);
+ } else {
+ goto copy_file_err;
+ }
+
+ nchars = save_file.write((const char *)pd, 2);
+ if (nchars < 2) {
+ goto copy_file_err;
+ }
+ }
+ break;
+ }
+ /* Only reverse direction */
+ case dir_reverse_:
+ {
+ char r_rawvalue;
+ while (rev_tempfile_->getChar(&r_rawvalue)) {
+ if (stop_flag) {
+ break;
+ }
+ ui->progressFrame->setValue(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
+
+ if (rev_statinfo_.pt == PT_PCMU) {
+ sample = ulaw2linear((unsigned char)r_rawvalue);
+ phton16(pd, sample);
+ } else if (rev_statinfo_.pt == PT_PCMA) {
+ sample = alaw2linear((unsigned char)r_rawvalue);
+ phton16(pd, sample);
+ } else {
+ goto copy_file_err;
+ }
+
+ nchars = save_file.write((const char *)pd, 2);
+ if (nchars < 2) {
+ goto copy_file_err;
+ }
+ }
+ break;
+ }
+ /* Both directions */
+ case dir_both_:
+ {
+ char f_rawvalue, r_rawvalue;
+ guint32 f_write_silence = 0;
+ guint32 r_write_silence = 0;
+ /* since conversation in one way can start later than in the other one,
+ * we have to write some silence information for one channel */
+ if (fwd_statinfo_.start_time > rev_statinfo_.start_time) {
+ f_write_silence = (guint32)
+ ((fwd_statinfo_.start_time - rev_statinfo_.start_time)
+ * (8000/1000));
+ } else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) {
+ r_write_silence = (guint32)
+ ((rev_statinfo_.start_time - fwd_statinfo_.start_time)
+ * (8000/1000));
+ }
+ for (;;) {
+ if (stop_flag) {
+ break;
+ }
+ int fwd_pct = fwd_tempfile_->pos() * 100 / fwd_tempfile_->size();
+ int rev_pct = rev_tempfile_->pos() * 100 / rev_tempfile_->size();
+ ui->progressFrame->setValue(qMin(fwd_pct, rev_pct));
+
+ if (f_write_silence > 0) {
+ rev_tempfile_->getChar(&r_rawvalue);
+ switch (fwd_statinfo_.reg_pt) {
+ case PT_PCMU:
+ f_rawvalue = silence_pcmu_;
+ break;
+ case PT_PCMA:
+ f_rawvalue = silence_pcma_;
+ break;
+ default:
+ f_rawvalue = 0;
+ break;
+ }
+ f_write_silence--;
+ } else if (r_write_silence > 0) {
+ fwd_tempfile_->getChar(&f_rawvalue);
+ switch (rev_statinfo_.reg_pt) {
+ case PT_PCMU:
+ r_rawvalue = silence_pcmu_;
+ break;
+ case PT_PCMA:
+ r_rawvalue = silence_pcma_;
+ break;
+ default:
+ r_rawvalue = 0;
+ break;
+ }
+ r_write_silence--;
+ } else {
+ fwd_tempfile_->getChar(&f_rawvalue);
+ rev_tempfile_->getChar(&r_rawvalue);
+ }
+ if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd())
+ break;
+ if ((fwd_statinfo_.pt == PT_PCMU)
+ && (rev_statinfo_.pt == PT_PCMU)) {
+ sample = (ulaw2linear((unsigned char)r_rawvalue)
+ + ulaw2linear((unsigned char)f_rawvalue)) / 2;
+ phton16(pd, sample);
+ }
+ else if ((fwd_statinfo_.pt == PT_PCMA)
+ && (rev_statinfo_.pt == PT_PCMA)) {
+ sample = (alaw2linear((unsigned char)r_rawvalue)
+ + alaw2linear((unsigned char)f_rawvalue)) / 2;
+ phton16(pd, sample);
+ } else {
+ goto copy_file_err;
+ }
+
+ nchars = save_file.write((const char *)pd, 2);
+ if (nchars < 2) {
+ goto copy_file_err;
+ }
+ }
+ }
+ }
+ } else if (save_format == save_audio_raw_) { /* raw format */
+ QFile *tempfile;
+ int progress_pct;
+
+ switch (direction) {
+ /* Only forward direction */
+ case dir_forward_: {
+ progress_pct = fwd_tempfile_->pos() * 100 / fwd_tempfile_->size();
+ tempfile = fwd_tempfile_;
+ break;
+ }
+ /* only reversed direction */
+ case dir_reverse_: {
+ progress_pct = rev_tempfile_->pos() * 100 / rev_tempfile_->size();
+ tempfile = rev_tempfile_;
+ break;
+ }
+ default: {
+ goto copy_file_err;
+ }
+ }
+
+ int chunk_size = 65536;
+ /* XXX how do you just copy the file? */
+ while (chunk_size > 0) {
+ if (stop_flag)
+ break;
+ QByteArray bytes = tempfile->read(chunk_size);
+ ui->progressFrame->setValue(progress_pct);
+
+ if (!save_file.write(bytes)) {
+ goto copy_file_err;
+ }
+ chunk_size = bytes.length();
+ }
+ }
+
+copy_file_err:
+ ui->progressFrame->hide();
+ updateWidgets();
+ return;
+}
+
+// XXX The GTK+ UI saves the length and timestamp.
+void Iax2AnalysisDialog::saveCsv(Iax2AnalysisDialog::StreamDirection direction)
+{
+ QString caption;
+
+ switch (direction) {
+ case dir_forward_:
+ caption = tr("Save forward stream CSV");
+ break;
+ case dir_reverse_:
+ caption = tr("Save reverse stream CSV");
+ break;
+ case dir_both_:
+ default:
+ caption = tr("Save CSV");
+ break;
+ }
+
+ QString file_path = QFileDialog::getSaveFileName(
+ this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
+ tr("Comma-separated values (*.csv)"));
+
+ if (file_path.isEmpty()) return;
+
+ QFile save_file(file_path);
+ save_file.open(QFile::WriteOnly);
+
+ if (direction == dir_forward_ || direction == dir_both_) {
+ save_file.write("Forward\n");
+
+ for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
+ if (ti->type() != iax2_analysis_type_) continue;
+ Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
+ QStringList values;
+ foreach (QVariant v, ra_ti->rowData()) {
+ if (!v.isValid()) {
+ values << "\"\"";
+ } else if ((int) v.type() == (int) QMetaType::QString) {
+ values << QString("\"%1\"").arg(v.toString());
+ } else {
+ values << v.toString();
+ }
+ }
+ save_file.write(values.join(",").toUtf8());
+ save_file.write("\n");
+ }
+ }
+ if (direction == dir_both_) {
+ save_file.write("\n");
+ }
+ if (direction == dir_reverse_ || direction == dir_both_) {
+ save_file.write("Reverse\n");
+
+ for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
+ if (ti->type() != iax2_analysis_type_) continue;
+ Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
+ QStringList values;
+ foreach (QVariant v, ra_ti->rowData()) {
+ if (!v.isValid()) {
+ values << "\"\"";
+ } else if ((int) v.type() == (int) QMetaType::QString) {
+ values << QString("\"%1\"").arg(v.toString());
+ } else {
+ values << v.toString();
+ }
+ }
+ save_file.write(values.join(",").toUtf8());
+ save_file.write("\n");
+ }
+ }
+}
+
+#if 0
+// Adapted from iax2_analysis.c:process_node
+guint32 Iax2AnalysisDialog::processNode(proto_node *ptree_node, header_field_info *hfinformation, const gchar *proto_field, bool *ok)
+{
+ field_info *finfo;
+ proto_node *proto_sibling_node;
+ header_field_info *hfssrc;
+ ipv4_addr *ipv4;
+
+ finfo = PNODE_FINFO(ptree_node);
+
+ /* Caller passed top of the protocol tree. Expected child node */
+ g_assert(finfo);
+
+ if (hfinformation == (finfo->hfinfo)) {
+ hfssrc = proto_registrar_get_byname(proto_field);
+ if (hfssrc == NULL) {
+ return 0;
+ }
+ for (ptree_node = ptree_node->first_child;
+ ptree_node != NULL;
+ ptree_node = ptree_node->next) {
+ finfo = PNODE_FINFO(ptree_node);
+ if (hfssrc == finfo->hfinfo) {
+ guint32 result;
+ if (hfinformation->type == FT_IPv4) {
+ ipv4 = (ipv4_addr *)fvalue_get(&finfo->value);
+ result = ipv4_get_net_order_addr(ipv4);
+ } else {
+ result = fvalue_get_uinteger(&finfo->value);
+ }
+ if (ok) *ok = true;
+ return result;
+ }
+ }
+ if (!ptree_node) {
+ return 0;
+ }
+ }
+
+ proto_sibling_node = ptree_node->next;
+
+ if (proto_sibling_node) {
+ return processNode(proto_sibling_node, hfinformation, proto_field, ok);
+ } else {
+ return 0;
+ }
+}
+
+// Adapted from iax2_analysis.c:get_int_value_from_proto_tree
+guint32 Iax2AnalysisDialog::getIntFromProtoTree(proto_tree *protocol_tree, const gchar *proto_name, const gchar *proto_field, bool *ok)
+{
+ proto_node *ptree_node;
+ header_field_info *hfinformation;
+
+ if (ok) *ok = false;
+
+ hfinformation = proto_registrar_get_byname(proto_name);
+ if (hfinformation == NULL) {
+ return 0;
+ }
+
+ ptree_node = ((proto_node *)protocol_tree)->first_child;
+ if (!ptree_node) {
+ return 0;
+ }
+
+ return processNode(ptree_node, hfinformation, proto_field, ok);
+}
+#endif
+
+bool Iax2AnalysisDialog::eventFilter(QObject *, QEvent *event)
+{
+ if (event->type() != QEvent::KeyPress) return false;
+
+ QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
+
+ switch(kevt->key()) {
+ case Qt::Key_G:
+ on_actionGoToPacket_triggered();
+ return true;
+ case Qt::Key_N:
+ on_actionNextProblem_triggered();
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+void Iax2AnalysisDialog::graphClicked(QMouseEvent *event)
+{
+ updateWidgets();
+ if (event->button() == Qt::RightButton) {
+ graph_ctx_menu_.exec(event->globalPos());
+ }
+}
+
+void Iax2AnalysisDialog::showStreamMenu(QPoint pos)
+{
+ QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
+ if (!cur_tree) return;
+
+ updateWidgets();
+ stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
+}
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */