/* rtp_stream_dialog.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "rtp_stream_dialog.h" #include #include "file.h" #include "epan/addr_resolv.h" #include #include #include #include "rtp_analysis_dialog.h" #include "main_application.h" #include "ui/qt/widgets/wireshark_file_dialog.h" #include #include #include #include #include #include #include #include #include /* * @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(); } rtpstream_info_calculate(stream_info_, &calc); switch(col) { case src_addr_col_: return text(col); case src_port_col_: return calc.src_port; case dst_addr_col_: return text(col); case dst_port_col_: return calc.dst_port; case ssrc_col_: return calc.ssrc; case start_time_col_: return calc.start_time_ms; case duration_col_: return calc.duration_ms; case payload_col_: return text(col); case packets_col_: return calc.packet_count; case lost_col_: return calc.lost_num; case min_delta_col_: return calc.min_delta; case mean_delta_col_: return calc.mean_delta; case max_delta_col_: return calc.max_delta; case min_jitter_col_: return calc.min_jitter; case mean_jitter_col_: return calc.mean_jitter; case max_jitter_col_: return calc.max_jitter; case status_col_: return calc.problem ? "Problem" : ""; case ssrc_fmt_col_: return QString("0x%1").arg(calc.ssrc, 0, 16); case lost_perc_col_: return QString::number(calc.lost_perc, 'f', prefs.gui_decimal_places1); default: break; } return QVariant(); } bool operator< (const QTreeWidgetItem &other) const { rtpstream_info_calc_t calc1; rtpstream_info_calc_t calc2; if (other.type() != rtp_stream_type_) return QTreeWidgetItem::operator <(other); const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast(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); return calc1.start_time_ms < calc2.start_time_ms; case duration_col_: rtpstream_info_calculate(stream_info_, &calc1); rtpstream_info_calculate(other_rstwi.stream_info_, &calc2); return calc1.duration_ms < calc2.duration_ms; 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_: return lost_ < other_rstwi.lost_; 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_; guint32 lost_; gboolean tod_; }; RtpStreamDialog *RtpStreamDialog::pinstance_{nullptr}; std::mutex RtpStreamDialog::mutex_; RtpStreamDialog *RtpStreamDialog::openRtpStreamDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list) { std::lock_guard 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(this, SIGNAL(updateFilter(QString, bool)), &parent, SLOT(filterPackets(QString, bool))); connect(&parent, SIGNAL(displayFilterSuccess(bool)), this, SLOT(displayFilterSuccess(bool))); connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector)), &parent, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector))); connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector)), &parent, SLOT(rtpPlayerDialogAddRtpStreams(QVector))); connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector)), &parent, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector))); connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector)), &parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector))); connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector)), &parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector))); connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector)), &parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector))); /* Scan for RTP streams (redissect all packets) */ rtpstream_scan(&tapinfo_, cf.capFile(), NULL); updateWidgets(); } RtpStreamDialog::~RtpStreamDialog() { std::lock_guard lock(mutex_); freeLastSelected(); delete ui; 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(*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 stream_ids) { std::lock_guard lock(mutex_); foreach(rtpstream_id_t *id, stream_ids) { setRtpStreamSelection(id, true); } } void RtpStreamDialog::deselectRtpStream(QVector stream_ids) { std::lock_guard 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(*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 *)tapinfo->tap_data); if (rtp_stream_dialog) { rtp_stream_dialog->freeLastSelected(); /* Copy currently selected rtpstream_ids */ QTreeWidgetItemIterator iter(rtp_stream_dialog->ui->streamTreeWidget); while (*iter) { RtpStreamTreeWidgetItem *rsti = static_cast(*iter); rtpstream_info_t *stream_info = rsti->streamInfo(); if ((*iter)->isSelected()) { rtpstream_id_t *i = (rtpstream_id_t *)g_malloc0(sizeof(rtpstream_id_t)); rtpstream_id_copy(&stream_info->id, i); rtp_stream_dialog->last_selected_.append(*i); } ++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 *)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 *)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(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(*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 = ""; hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount()); if (selected) { int tot_packets = 0; foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) { RtpStreamTreeWidgetItem *rsti = static_cast(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 += ""; 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 RtpStreamDialog::streamRowData(int row) const { QList 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(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(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; itodCheckBox->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(ti); rtpstream_info_t *stream_info = rsti->streamInfo(); if (stream_info) { QString file_name; QDir path(mainApp->lastOpenDir()); 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 (*.rtpdump)", &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(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(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(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(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(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(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(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(ti); stream_a = rsti->streamInfo(); if (ui->streamTreeWidget->selectedItems().count() > 1) { ti = ui->streamTreeWidget->selectedItems()[1]; rsti = static_cast(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 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::on_displayFilterCheckBox_toggled(bool checked _U_) { 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(*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(); } QVectorRtpStreamDialog::getSelectedRtpIds() { // Gather up our selected streams... QVector stream_ids; foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) { RtpStreamTreeWidgetItem *rsti = static_cast(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(); }