summaryrefslogtreecommitdiffstats
path: root/ui/qt/rtp_stream_dialog.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/rtp_stream_dialog.cpp
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/rtp_stream_dialog.cpp')
-rw-r--r--ui/qt/rtp_stream_dialog.cpp1064
1 files changed, 1064 insertions, 0 deletions
diff --git a/ui/qt/rtp_stream_dialog.cpp b/ui/qt/rtp_stream_dialog.cpp
new file mode 100644
index 0000000..6b28a83
--- /dev/null
+++ b/ui/qt/rtp_stream_dialog.cpp
@@ -0,0 +1,1064 @@
+/* rtp_stream_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "rtp_stream_dialog.h"
+#include <ui_rtp_stream_dialog.h>
+
+#include "file.h"
+
+#include "epan/addr_resolv.h"
+#include <epan/rtp_pt.h>
+
+#include <wsutil/utf8_entities.h>
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include "rtp_analysis_dialog.h"
+#include "progress_frame.h"
+#include "main_application.h"
+#include "ui/qt/widgets/wireshark_file_dialog.h"
+
+#include <QAction>
+#include <QClipboard>
+#include <QKeyEvent>
+#include <QPushButton>
+#include <QTextStream>
+#include <QTreeWidgetItem>
+#include <QTreeWidgetItemIterator>
+#include <QDateTime>
+
+#include <ui/qt/utils/color_utils.h>
+
+/*
+ * @file RTP stream dialog
+ *
+ * Displays a list of RTP streams with the following information:
+ * - UDP 4-tuple
+ * - SSRC
+ * - Payload type
+ * - Stats: Packets, lost, max delta, max jitter, mean jitter
+ * - Problems
+ *
+ * Finds reverse streams
+ * "Save As" rtpdump
+ * Mark packets
+ * Go to the setup frame
+ * Prepare filter
+ * Copy As CSV and YAML
+ * Analyze
+ */
+
+// To do:
+// - Add more statistics to the hint text (e.g. lost packets).
+// - Add more statistics to the main list (e.g. stream duration)
+
+const int src_addr_col_ = 0;
+const int src_port_col_ = 1;
+const int dst_addr_col_ = 2;
+const int dst_port_col_ = 3;
+const int ssrc_col_ = 4;
+const int start_time_col_ = 5;
+const int duration_col_ = 6;
+const int payload_col_ = 7;
+const int packets_col_ = 8;
+const int lost_col_ = 9;
+const int min_delta_col_ = 10;
+const int mean_delta_col_ = 11;
+const int max_delta_col_ = 12;
+const int min_jitter_col_ = 13;
+const int mean_jitter_col_ = 14;
+const int max_jitter_col_ = 15;
+const int status_col_ = 16;
+const int ssrc_fmt_col_ = 17;
+const int lost_perc_col_ = 18;
+
+enum { rtp_stream_type_ = 1000 };
+
+bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b);
+
+class RtpStreamTreeWidgetItem : public QTreeWidgetItem
+{
+public:
+ RtpStreamTreeWidgetItem(QTreeWidget *tree, rtpstream_info_t *stream_info) :
+ QTreeWidgetItem(tree, rtp_stream_type_),
+ stream_info_(stream_info),
+ tod_(0)
+ {
+ drawData();
+ }
+
+ rtpstream_info_t *streamInfo() const { return stream_info_; }
+
+ void drawData() {
+ rtpstream_info_calc_t calc;
+
+ if (!stream_info_) {
+ return;
+ }
+ rtpstream_info_calculate(stream_info_, &calc);
+
+ setText(src_addr_col_, calc.src_addr_str);
+ setText(src_port_col_, QString::number(calc.src_port));
+ setText(dst_addr_col_, calc.dst_addr_str);
+ setText(dst_port_col_, QString::number(calc.dst_port));
+ setText(ssrc_col_, QString("0x%1").arg(calc.ssrc, 0, 16));
+ if (tod_) {
+ QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(&stream_info_->start_fd->abs_ts));
+ setText(start_time_col_, QString("%1")
+ .arg(abs_dt.toString("yyyy-MM-dd hh:mm:ss.zzz")));
+ } else {
+ setText(start_time_col_, QString::number(calc.start_time_ms, 'f', 6));
+ }
+ setText(duration_col_, QString::number(calc.duration_ms, 'f', prefs.gui_decimal_places1));
+ setText(payload_col_, calc.all_payload_type_names);
+ setText(packets_col_, QString::number(calc.packet_count));
+ setText(lost_col_, QObject::tr("%1 (%L2%)").arg(calc.lost_num).arg(QString::number(calc.lost_perc, 'f', 1)));
+ setText(min_delta_col_, QString::number(calc.min_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
+ setText(mean_delta_col_, QString::number(calc.mean_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
+ setText(max_delta_col_, QString::number(calc.max_delta, 'f', prefs.gui_decimal_places3)); // This is RTP. Do we need nanoseconds?
+ setText(min_jitter_col_, QString::number(calc.min_jitter, 'f', prefs.gui_decimal_places3));
+ setText(mean_jitter_col_, QString::number(calc.mean_jitter, 'f', prefs.gui_decimal_places3));
+ setText(max_jitter_col_, QString::number(calc.max_jitter, 'f', prefs.gui_decimal_places3));
+
+ if (calc.problem) {
+ setText(status_col_, UTF8_BULLET);
+ setTextAlignment(status_col_, Qt::AlignCenter);
+ QColor bgColor(ColorUtils::warningBackground());
+ QColor textColor(QApplication::palette().text().color());
+ for (int i = 0; i < columnCount(); i++) {
+ QBrush bgBrush = background(i);
+ bgBrush.setColor(bgColor);
+ bgBrush.setStyle(Qt::SolidPattern);
+ setBackground(i, bgBrush);
+ QBrush fgBrush = foreground(i);
+ fgBrush.setColor(textColor);
+ fgBrush.setStyle(Qt::SolidPattern);
+ setForeground(i, fgBrush);
+ }
+ }
+
+ rtpstream_info_calc_free(&calc);
+ }
+ // Return a QString, int, double, or invalid QVariant representing the raw column data.
+ QVariant colData(int col) const {
+ rtpstream_info_calc_t calc;
+ if (!stream_info_) {
+ return QVariant();
+ }
+
+ QVariant ret;
+ rtpstream_info_calculate(stream_info_, &calc);
+
+ switch(col) {
+ case src_addr_col_:
+ ret = QVariant(text(col));
+ break;
+ case src_port_col_:
+ ret = calc.src_port;
+ break;
+ case dst_addr_col_:
+ ret = text(col);
+ break;
+ case dst_port_col_:
+ ret = calc.dst_port;
+ break;
+ case ssrc_col_:
+ ret = calc.ssrc;
+ break;
+ case start_time_col_:
+ ret = calc.start_time_ms;
+ break;
+ case duration_col_:
+ ret = calc.duration_ms;
+ break;
+ case payload_col_:
+ ret = text(col);
+ break;
+ case packets_col_:
+ ret = calc.packet_count;
+ break;
+ case lost_col_:
+ ret = calc.lost_num;
+ break;
+ case min_delta_col_:
+ ret = calc.min_delta;
+ break;
+ case mean_delta_col_:
+ ret = calc.mean_delta;
+ break;
+ case max_delta_col_:
+ ret = calc.max_delta;
+ break;
+ case min_jitter_col_:
+ ret = calc.min_jitter;
+ break;
+ case mean_jitter_col_:
+ ret = calc.mean_jitter;
+ break;
+ case max_jitter_col_:
+ ret = calc.max_jitter;
+ break;
+ case status_col_:
+ ret = calc.problem ? "Problem" : "";
+ break;
+ case ssrc_fmt_col_:
+ ret = QString("0x%1").arg(calc.ssrc, 0, 16);
+ break;
+ case lost_perc_col_:
+ ret = QString::number(calc.lost_perc, 'f', prefs.gui_decimal_places1);
+ break;
+ default:
+ ret = QVariant();
+ break;
+ }
+ rtpstream_info_calc_free(&calc);
+ return ret;
+ }
+
+ bool operator< (const QTreeWidgetItem &other) const
+ {
+ rtpstream_info_calc_t calc1;
+ rtpstream_info_calc_t calc2;
+ bool ret;
+
+ if (other.type() != rtp_stream_type_) return QTreeWidgetItem::operator <(other);
+ const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other);
+
+ switch (treeWidget()->sortColumn()) {
+ case src_addr_col_:
+ return cmp_address(&(stream_info_->id.src_addr), &(other_rstwi.stream_info_->id.src_addr)) < 0;
+ case src_port_col_:
+ return stream_info_->id.src_port < other_rstwi.stream_info_->id.src_port;
+ case dst_addr_col_:
+ return cmp_address(&(stream_info_->id.dst_addr), &(other_rstwi.stream_info_->id.dst_addr)) < 0;
+ case dst_port_col_:
+ return stream_info_->id.dst_port < other_rstwi.stream_info_->id.dst_port;
+ case ssrc_col_:
+ return stream_info_->id.ssrc < other_rstwi.stream_info_->id.ssrc;
+ case start_time_col_:
+ rtpstream_info_calculate(stream_info_, &calc1);
+ rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
+ ret = calc1.start_time_ms < calc2.start_time_ms;
+ rtpstream_info_calc_free(&calc1);
+ rtpstream_info_calc_free(&calc2);
+ return ret;
+ case duration_col_:
+ rtpstream_info_calculate(stream_info_, &calc1);
+ rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
+ ret = calc1.duration_ms < calc2.duration_ms;
+ rtpstream_info_calc_free(&calc1);
+ rtpstream_info_calc_free(&calc2);
+ return ret;
+ case payload_col_:
+ return g_strcmp0(stream_info_->all_payload_type_names, other_rstwi.stream_info_->all_payload_type_names);
+ case packets_col_:
+ return stream_info_->packet_count < other_rstwi.stream_info_->packet_count;
+ case lost_col_:
+ rtpstream_info_calculate(stream_info_, &calc1);
+ rtpstream_info_calculate(other_rstwi.stream_info_, &calc2);
+ /* XXX: Should this sort on the total number or the percentage?
+ * lost_num is displayed first and lost_perc in parenthesis,
+ * so let's use the total number.
+ */
+ ret = calc1.lost_num < calc2.lost_num;
+ rtpstream_info_calc_free(&calc1);
+ rtpstream_info_calc_free(&calc2);
+ return ret;
+ break;
+ case min_delta_col_:
+ return stream_info_->rtp_stats.min_delta < other_rstwi.stream_info_->rtp_stats.min_delta;
+ case mean_delta_col_:
+ return stream_info_->rtp_stats.mean_delta < other_rstwi.stream_info_->rtp_stats.mean_delta;
+ case max_delta_col_:
+ return stream_info_->rtp_stats.max_delta < other_rstwi.stream_info_->rtp_stats.max_delta;
+ case min_jitter_col_:
+ return stream_info_->rtp_stats.min_jitter < other_rstwi.stream_info_->rtp_stats.min_jitter;
+ case mean_jitter_col_:
+ return stream_info_->rtp_stats.mean_jitter < other_rstwi.stream_info_->rtp_stats.mean_jitter;
+ case max_jitter_col_:
+ return stream_info_->rtp_stats.max_jitter < other_rstwi.stream_info_->rtp_stats.max_jitter;
+ default:
+ break;
+ }
+
+ // Fall back to string comparison
+ return QTreeWidgetItem::operator <(other);
+ }
+
+ void setTOD(gboolean tod)
+ {
+ tod_ = tod;
+ }
+
+private:
+ rtpstream_info_t *stream_info_;
+ gboolean tod_;
+};
+
+
+RtpStreamDialog *RtpStreamDialog::pinstance_{nullptr};
+std::mutex RtpStreamDialog::mutex_;
+
+RtpStreamDialog *RtpStreamDialog::openRtpStreamDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list)
+{
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (pinstance_ == nullptr)
+ {
+ pinstance_ = new RtpStreamDialog(parent, cf);
+ connect(pinstance_, SIGNAL(packetsMarked()),
+ packet_list, SLOT(redrawVisiblePackets()));
+ connect(pinstance_, SIGNAL(goToPacket(int)),
+ packet_list, SLOT(goToPacket(int)));
+ }
+ return pinstance_;
+}
+
+RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf),
+ ui(new Ui::RtpStreamDialog),
+ need_redraw_(false)
+{
+ ui->setupUi(this);
+ loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
+ setWindowSubtitle(tr("RTP Streams"));
+ ui->streamTreeWidget->installEventFilter(this);
+
+ ctx_menu_.addMenu(ui->menuSelect);
+ ctx_menu_.addMenu(ui->menuFindReverse);
+ ctx_menu_.addAction(ui->actionGoToSetup);
+ ctx_menu_.addAction(ui->actionMarkPackets);
+ ctx_menu_.addAction(ui->actionPrepareFilter);
+ ctx_menu_.addAction(ui->actionExportAsRtpDump);
+ ctx_menu_.addAction(ui->actionCopyAsCsv);
+ ctx_menu_.addAction(ui->actionCopyAsYaml);
+ ctx_menu_.addAction(ui->actionAnalyze);
+ set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
+
+ ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->streamTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
+ connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
+ SLOT(showStreamMenu(QPoint)));
+
+ find_reverse_button_ = new QToolButton();
+ ui->buttonBox->addButton(find_reverse_button_, QDialogButtonBox::ActionRole);
+ find_reverse_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ find_reverse_button_->setPopupMode(QToolButton::MenuButtonPopup);
+
+ connect(ui->actionFindReverse, &QAction::triggered, this, &RtpStreamDialog::on_actionFindReverseNormal_triggered);
+ find_reverse_button_->setDefaultAction(ui->actionFindReverse);
+ // Overrides text striping of shortcut undercode in QAction
+ find_reverse_button_->setText(ui->actionFindReverseNormal->text());
+ find_reverse_button_->setMenu(ui->menuFindReverse);
+
+ analyze_button_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
+ prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
+ prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
+ connect(prepare_button_, &QPushButton::pressed, this, &RtpStreamDialog::on_actionPrepareFilter_triggered);
+ player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
+ copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
+ copy_button_->setToolTip(ui->actionCopyButton->toolTip());
+ export_button_ = ui->buttonBox->addButton(ui->actionExportAsRtpDump->text(), QDialogButtonBox::ActionRole);
+ export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
+ connect(export_button_, &QPushButton::pressed, this, &RtpStreamDialog::on_actionExportAsRtpDump_triggered);
+
+ QMenu *copy_menu = new QMenu(copy_button_);
+ QAction *ca;
+ ca = copy_menu->addAction(tr("as CSV"));
+ ca->setToolTip(ui->actionCopyAsCsv->toolTip());
+ connect(ca, &QAction::triggered, this, &RtpStreamDialog::on_actionCopyAsCsv_triggered);
+ ca = copy_menu->addAction(tr("as YAML"));
+ ca->setToolTip(ui->actionCopyAsYaml->toolTip());
+ connect(ca, &QAction::triggered, this, &RtpStreamDialog::on_actionCopyAsYaml_triggered);
+ copy_button_->setMenu(copy_menu);
+ connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
+ this, SLOT(captureEvent(CaptureEvent)));
+
+ /* Register the tap listener */
+ memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
+ tapinfo_.tap_reset = tapReset;
+ tapinfo_.tap_draw = tapDraw;
+ tapinfo_.tap_mark_packet = tapMarkPacket;
+ tapinfo_.tap_data = this;
+ tapinfo_.mode = TAP_ANALYSE;
+
+ register_tap_listener_rtpstream(&tapinfo_, NULL, show_tap_registration_error);
+ if (cap_file_.isValid() && cap_file_.capFile()->dfilter) {
+ // Activate display filter checking
+ tapinfo_.apply_display_filter = true;
+ ui->displayFilterCheckBox->setChecked(true);
+ }
+
+ connect(ui->displayFilterCheckBox, &QCheckBox::toggled,
+ this, &RtpStreamDialog::displayFilterCheckBoxToggled);
+ connect(this, SIGNAL(updateFilter(QString, bool)),
+ &parent, SLOT(filterPackets(QString, bool)));
+ connect(&parent, SIGNAL(displayFilterSuccess(bool)),
+ this, SLOT(displayFilterSuccess(bool)));
+ connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
+ connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
+ &parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
+
+ ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
+
+ updateWidgets();
+
+ if (cap_file_.isValid()) {
+ cap_file_.delayedRetapPackets();
+ }
+}
+
+RtpStreamDialog::~RtpStreamDialog()
+{
+ std::lock_guard<std::mutex> lock(mutex_);
+ freeLastSelected();
+ delete ui;
+ rtpstream_reset(&tapinfo_);
+ remove_tap_listener_rtpstream(&tapinfo_);
+ pinstance_ = nullptr;
+}
+
+void RtpStreamDialog::setRtpStreamSelection(rtpstream_id_t *id, bool state)
+{
+ QTreeWidgetItemIterator iter(ui->streamTreeWidget);
+ while (*iter) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
+ rtpstream_info_t *stream_info = rsti->streamInfo();
+ if (stream_info) {
+ if (rtpstream_id_equal(id,&stream_info->id,RTPSTREAM_ID_EQUAL_SSRC)) {
+ (*iter)->setSelected(state);
+ }
+ }
+ ++iter;
+ }
+}
+
+void RtpStreamDialog::selectRtpStream(QVector<rtpstream_id_t *> stream_ids)
+{
+ std::lock_guard<std::mutex> lock(mutex_);
+ foreach(rtpstream_id_t *id, stream_ids) {
+ setRtpStreamSelection(id, true);
+ }
+}
+
+void RtpStreamDialog::deselectRtpStream(QVector<rtpstream_id_t *> stream_ids)
+{
+ std::lock_guard<std::mutex> lock(mutex_);
+ foreach(rtpstream_id_t *id, stream_ids) {
+ setRtpStreamSelection(id, false);
+ }
+}
+
+bool RtpStreamDialog::eventFilter(QObject *, QEvent *event)
+{
+ if (ui->streamTreeWidget->hasFocus() && event->type() == QEvent::KeyPress) {
+ QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
+ switch(keyEvent.key()) {
+ case Qt::Key_G:
+ on_actionGoToSetup_triggered();
+ return true;
+ case Qt::Key_M:
+ on_actionMarkPackets_triggered();
+ return true;
+ case Qt::Key_P:
+ on_actionPrepareFilter_triggered();
+ return true;
+ case Qt::Key_R:
+ if (keyEvent.modifiers() == Qt::ShiftModifier) {
+ on_actionFindReversePair_triggered();
+ } else if (keyEvent.modifiers() == Qt::ControlModifier) {
+ on_actionFindReverseSingle_triggered();
+ } else {
+ on_actionFindReverseNormal_triggered();
+ }
+ return true;
+ case Qt::Key_I:
+ if (keyEvent.modifiers() == Qt::ControlModifier) {
+ // Ctrl+I
+ on_actionSelectInvert_triggered();
+ return true;
+ }
+ break;
+ case Qt::Key_A:
+ if (keyEvent.modifiers() == Qt::ControlModifier) {
+ // Ctrl+A
+ on_actionSelectAll_triggered();
+ return true;
+ } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
+ // Ctrl+Shift+A
+ on_actionSelectNone_triggered();
+ return true;
+ } else if (keyEvent.modifiers() == Qt::NoModifier) {
+ on_actionAnalyze_triggered();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+void RtpStreamDialog::captureEvent(CaptureEvent e)
+{
+ if (e.captureContext() == CaptureEvent::Retap)
+ {
+ switch (e.eventType())
+ {
+ case CaptureEvent::Started:
+ ui->displayFilterCheckBox->setEnabled(false);
+ break;
+ case CaptureEvent::Finished:
+ ui->displayFilterCheckBox->setEnabled(true);
+ break;
+ default:
+ break;
+ }
+ }
+
+}
+
+void RtpStreamDialog::tapReset(rtpstream_tapinfo_t *tapinfo)
+{
+ RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
+ if (rtp_stream_dialog) {
+ rtp_stream_dialog->freeLastSelected();
+ /* Copy currently selected rtpstream_ids */
+ QTreeWidgetItemIterator iter(rtp_stream_dialog->ui->streamTreeWidget);
+ rtpstream_id_t selected_id;
+ while (*iter) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
+ rtpstream_info_t *stream_info = rsti->streamInfo();
+ if ((*iter)->isSelected()) {
+ /* QList.append() does a member by member copy, so allocate new
+ * addresses. rtpstream_id_copy() overwrites all struct members.
+ */
+ rtpstream_id_copy(&stream_info->id, &selected_id);
+ rtp_stream_dialog->last_selected_.append(selected_id);
+ }
+ ++iter;
+ }
+ /* invalidate items which refer to old strinfo_list items. */
+ rtp_stream_dialog->ui->streamTreeWidget->clear();
+ }
+}
+
+void RtpStreamDialog::tapDraw(rtpstream_tapinfo_t *tapinfo)
+{
+ RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
+ if (rtp_stream_dialog) {
+ rtp_stream_dialog->updateStreams();
+ }
+}
+
+void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd)
+{
+ if (!tapinfo) return;
+
+ RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
+ if (rtp_stream_dialog) {
+ cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd);
+ rtp_stream_dialog->need_redraw_ = true;
+ }
+}
+
+/* Operator == for rtpstream_id_t */
+bool operator==(rtpstream_id_t const& a, rtpstream_id_t const& b)
+{
+ return rtpstream_id_equal(&a, &b, RTPSTREAM_ID_EQUAL_SSRC);
+}
+
+void RtpStreamDialog::updateStreams()
+{
+ // string_list is reverse ordered, so we must add
+ // just first "to_insert_count" of streams
+ GList *cur_stream = g_list_first(tapinfo_.strinfo_list);
+ guint tap_len = g_list_length(tapinfo_.strinfo_list);
+ guint tree_len = static_cast<guint>(ui->streamTreeWidget->topLevelItemCount());
+ guint to_insert_count = tap_len - tree_len;
+
+ // Add any missing items
+ while (cur_stream && cur_stream->data && to_insert_count) {
+ rtpstream_info_t *stream_info = gxx_list_data(rtpstream_info_t*, cur_stream);
+ RtpStreamTreeWidgetItem *rsti = new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info);
+ cur_stream = gxx_list_next(cur_stream);
+ to_insert_count--;
+
+ // Check if item was selected last time. If so, select it
+ if (-1 != last_selected_.indexOf(stream_info->id)) {
+ rsti->setSelected(true);
+ }
+ }
+
+ // Recalculate values
+ QTreeWidgetItemIterator iter(ui->streamTreeWidget);
+ while (*iter) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
+ rsti->drawData();
+ ++iter;
+ }
+
+ // Resize columns
+ for (int i = 0; i < ui->streamTreeWidget->columnCount(); i++) {
+ ui->streamTreeWidget->resizeColumnToContents(i);
+ }
+
+ ui->streamTreeWidget->setSortingEnabled(true);
+
+ updateWidgets();
+
+ if (need_redraw_) {
+ emit packetsMarked();
+ need_redraw_ = false;
+ }
+}
+
+void RtpStreamDialog::updateWidgets()
+{
+ bool selected = ui->streamTreeWidget->selectedItems().count() > 0;
+
+ QString hint = "<small><i>";
+ hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount());
+
+ if (selected) {
+ int tot_packets = 0;
+ foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ if (rsti->streamInfo()) {
+ tot_packets += rsti->streamInfo()->packet_count;
+ }
+ }
+ hint += tr(", %1 selected, %2 total packets")
+ .arg(ui->streamTreeWidget->selectedItems().count())
+ .arg(tot_packets);
+ }
+
+ hint += ". Right-click for more options.";
+ hint += "</i></small>";
+ ui->hintLabel->setText(hint);
+
+ bool enable = selected && !file_closed_;
+ bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0;
+
+ find_reverse_button_->setEnabled(has_data);
+ prepare_button_->setEnabled(enable);
+ export_button_->setEnabled(enable);
+ copy_button_->setEnabled(has_data);
+ analyze_button_->setEnabled(enable);
+
+ ui->actionFindReverseNormal->setEnabled(enable);
+ ui->actionFindReversePair->setEnabled(has_data);
+ ui->actionFindReverseSingle->setEnabled(has_data);
+ ui->actionGoToSetup->setEnabled(enable);
+ ui->actionMarkPackets->setEnabled(enable);
+ ui->actionPrepareFilter->setEnabled(enable);
+ ui->actionExportAsRtpDump->setEnabled(enable);
+ ui->actionCopyAsCsv->setEnabled(has_data);
+ ui->actionCopyAsYaml->setEnabled(has_data);
+ ui->actionAnalyze->setEnabled(enable);
+
+#if defined(QT_MULTIMEDIA_LIB)
+ player_button_->setEnabled(enable);
+#endif
+
+ WiresharkDialog::updateWidgets();
+}
+
+QList<QVariant> RtpStreamDialog::streamRowData(int row) const
+{
+ QList<QVariant> row_data;
+
+ if (row >= ui->streamTreeWidget->topLevelItemCount()) {
+ return row_data;
+ }
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
+ if (row < 0) {
+ row_data << ui->streamTreeWidget->headerItem()->text(col);
+ } else {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
+ if (rsti) {
+ row_data << rsti->colData(col);
+ }
+ }
+ }
+
+ // Add additional columns to export
+ if (row < 0) {
+ row_data << QString("SSRC formatted");
+ row_data << QString("Lost percentage");
+ } else {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row));
+ if (rsti) {
+ row_data << rsti->colData(ssrc_fmt_col_);
+ row_data << rsti->colData(lost_perc_col_);
+ }
+ }
+ return row_data;
+}
+
+void RtpStreamDialog::freeLastSelected()
+{
+ /* Free old IDs */
+ for(int i=0; i<last_selected_.length(); i++) {
+ rtpstream_id_t id = last_selected_.at(i);
+ rtpstream_id_free(&id);
+ }
+ /* Clear list and reuse it */
+ last_selected_.clear();
+}
+
+void RtpStreamDialog::captureFileClosing()
+{
+ remove_tap_listener_rtpstream(&tapinfo_);
+
+ WiresharkDialog::captureFileClosing();
+}
+
+void RtpStreamDialog::captureFileClosed()
+{
+ ui->todCheckBox->setEnabled(false);
+ ui->displayFilterCheckBox->setEnabled(false);
+
+ WiresharkDialog::captureFileClosed();
+}
+
+void RtpStreamDialog::showStreamMenu(QPoint pos)
+{
+ ui->actionGoToSetup->setEnabled(!file_closed_);
+ ui->actionMarkPackets->setEnabled(!file_closed_);
+ ui->actionPrepareFilter->setEnabled(!file_closed_);
+ ui->actionExportAsRtpDump->setEnabled(!file_closed_);
+ ui->actionAnalyze->setEnabled(!file_closed_);
+ ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos));
+}
+
+void RtpStreamDialog::on_actionCopyAsCsv_triggered()
+{
+ QString csv;
+ QTextStream stream(&csv, QIODevice::Text);
+ for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QStringList rdsl;
+ foreach (QVariant v, streamRowData(row)) {
+ if (!v.isValid()) {
+ rdsl << "\"\"";
+ } else if (v.userType() == QMetaType::QString) {
+ rdsl << QString("\"%1\"").arg(v.toString());
+ } else {
+ rdsl << v.toString();
+ }
+ }
+ stream << rdsl.join(",") << '\n';
+ }
+ mainApp->clipboard()->setText(stream.readAll());
+}
+
+void RtpStreamDialog::on_actionCopyAsYaml_triggered()
+{
+ QString yaml;
+ QTextStream stream(&yaml, QIODevice::Text);
+ stream << "---" << '\n';
+ for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) {
+ stream << "-" << '\n';
+ foreach (QVariant v, streamRowData(row)) {
+ stream << " - " << v.toString() << '\n';
+ }
+ }
+ mainApp->clipboard()->setText(stream.readAll());
+}
+
+void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
+{
+ if (file_closed_ || ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ // XXX If the user selected multiple frames is this the one we actually want?
+ QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ rtpstream_info_t *stream_info = rsti->streamInfo();
+ if (stream_info) {
+ QString file_name;
+ QDir path(mainApp->openDialogInitialDir());
+ QString save_file = path.canonicalPath() + "/" + cap_file_.fileBaseName();
+ QString extension;
+ file_name = WiresharkFileDialog::getSaveFileName(this, mainApp->windowTitleString(tr("Save RTPDump As…")),
+ save_file, "RTPDump Format (*.rtp)", &extension);
+
+ if (file_name.length() > 0) {
+ gchar *dest_file = qstring_strdup(file_name);
+ gboolean save_ok = rtpstream_save(&tapinfo_, cap_file_.capFile(), stream_info, dest_file);
+ g_free(dest_file);
+ // else error dialog?
+ if (save_ok) {
+ mainApp->setLastOpenDirFromFilename(file_name);
+ }
+ }
+
+ }
+}
+
+// Search for reverse stream of every selected stream
+void RtpStreamDialog::on_actionFindReverseNormal_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ ui->streamTreeWidget->blockSignals(true);
+
+ // Traverse all items and if stream is selected, search reverse from
+ // current position till last item (NxN/2)
+ for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
+ RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
+ rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
+ if (fwd_stream && fwd_rsti->isSelected()) {
+ for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
+ RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
+ rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
+ if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
+ rev_rsti->setSelected(true);
+ break;
+ }
+ }
+ }
+ }
+ ui->streamTreeWidget->blockSignals(false);
+ updateWidgets();
+}
+
+// Select all pairs of forward/reverse streams
+void RtpStreamDialog::on_actionFindReversePair_triggered()
+{
+ ui->streamTreeWidget->blockSignals(true);
+ ui->streamTreeWidget->clearSelection();
+
+ // Traverse all items and search reverse from current position till last
+ // item (NxN/2)
+ for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
+ RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
+ rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
+ if (fwd_stream) {
+ for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
+ RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
+ rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
+ if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
+ fwd_rsti->setSelected(true);
+ rev_rsti->setSelected(true);
+ break;
+ }
+ }
+ }
+ }
+ ui->streamTreeWidget->blockSignals(false);
+ updateWidgets();
+}
+
+// Select all streams which don't have reverse stream
+void RtpStreamDialog::on_actionFindReverseSingle_triggered()
+{
+ ui->streamTreeWidget->blockSignals(true);
+ ui->streamTreeWidget->selectAll();
+
+ // Traverse all items and search reverse from current position till last
+ // item (NxN/2)
+ for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
+ RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
+ rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
+ if (fwd_stream) {
+ for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
+ RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
+ rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
+ if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
+ fwd_rsti->setSelected(false);
+ rev_rsti->setSelected(false);
+ break;
+ }
+ }
+ }
+ }
+ ui->streamTreeWidget->blockSignals(false);
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_actionGoToSetup_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+ // XXX If the user selected multiple frames is this the one we actually want?
+ QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ rtpstream_info_t *stream_info = rsti->streamInfo();
+ if (stream_info) {
+ emit goToPacket(stream_info->setup_frame_number);
+ }
+}
+
+void RtpStreamDialog::on_actionMarkPackets_triggered()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+ rtpstream_info_t *stream_a, *stream_b = NULL;
+
+ QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0];
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ stream_a = rsti->streamInfo();
+ if (ui->streamTreeWidget->selectedItems().count() > 1) {
+ ti = ui->streamTreeWidget->selectedItems()[1];
+ rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ stream_b = rsti->streamInfo();
+ }
+
+ if (stream_a == NULL && stream_b == NULL) return;
+
+ // XXX Mark the setup frame as well?
+ need_redraw_ = false;
+ rtpstream_mark(&tapinfo_, cap_file_.capFile(), stream_a, stream_b);
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_actionPrepareFilter_triggered()
+{
+ QVector<rtpstream_id_t *> ids = getSelectedRtpIds();
+ QString filter = make_filter_based_on_rtpstream_id(ids);
+ if (filter.length() > 0) {
+ remove_tap_listener_rtpstream(&tapinfo_);
+ emit updateFilter(filter);
+ }
+}
+
+void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
+{
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_buttonBox_helpRequested()
+{
+ mainApp->helpTopicAction(HELP_TELEPHONY_RTP_STREAMS_DIALOG);
+}
+
+void RtpStreamDialog::displayFilterCheckBoxToggled(bool checked)
+{
+ if (!cap_file_.isValid()) {
+ return;
+ }
+
+ tapinfo_.apply_display_filter = checked;
+
+ cap_file_.retapPackets();
+}
+
+void RtpStreamDialog::on_todCheckBox_toggled(bool checked)
+{
+ QTreeWidgetItemIterator iter(ui->streamTreeWidget);
+ while (*iter) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
+ rsti->setTOD(checked);
+ rsti->drawData();
+ ++iter;
+ }
+ ui->streamTreeWidget->resizeColumnToContents(start_time_col_);
+}
+
+void RtpStreamDialog::on_actionSelectAll_triggered()
+{
+ ui->streamTreeWidget->selectAll();
+}
+
+void RtpStreamDialog::on_actionSelectInvert_triggered()
+{
+ invertSelection();
+}
+
+void RtpStreamDialog::on_actionSelectNone_triggered()
+{
+ ui->streamTreeWidget->clearSelection();
+}
+
+QVector<rtpstream_id_t *>RtpStreamDialog::getSelectedRtpIds()
+{
+ // Gather up our selected streams...
+ QVector<rtpstream_id_t *> stream_ids;
+ foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
+ RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
+ rtpstream_info_t *selected_stream = rsti->streamInfo();
+ if (selected_stream) {
+ stream_ids << &(selected_stream->id);
+ }
+ }
+
+ return stream_ids;
+}
+
+void RtpStreamDialog::rtpPlayerReplace()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::rtpPlayerAdd()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::rtpPlayerRemove()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::rtpAnalysisReplace()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::rtpAnalysisAdd()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::rtpAnalysisRemove()
+{
+ if (ui->streamTreeWidget->selectedItems().count() < 1) return;
+
+ emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpIds());
+}
+
+void RtpStreamDialog::displayFilterSuccess(bool success)
+{
+ if (success && ui->displayFilterCheckBox->isChecked()) {
+ cap_file_.retapPackets();
+ }
+}
+
+void RtpStreamDialog::invertSelection()
+{
+ ui->streamTreeWidget->blockSignals(true);
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ ti->setSelected(!ti->isSelected());
+ }
+ ui->streamTreeWidget->blockSignals(false);
+ updateWidgets();
+}
+
+void RtpStreamDialog::on_actionAnalyze_triggered()
+{
+ RtpStreamDialog::rtpAnalysisAdd();
+}
+