wireshark/ui/qt/rtp_stream_dialog.cpp

1022 lines
36 KiB
C++

/* 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 "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();
}
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<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);
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_:
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.
*/
return calc1.lost_num < calc2.lost_num;
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(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 *>)));
/* Scan for RTP streams (redissect all packets) */
rtpstream_scan(&tapinfo_, cf.capFile(), NULL);
updateWidgets();
}
RtpStreamDialog::~RtpStreamDialog()
{
std::lock_guard<std::mutex> 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<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);
while (*iter) {
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*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 *>((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->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<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::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<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();
}