2015-01-15 01:25:56 +00:00
|
|
|
/* rtp_stream_dialog.cpp
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "rtp_stream_dialog.h"
|
2015-06-25 16:17:03 +00:00
|
|
|
#include <ui_rtp_stream_dialog.h>
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
#include "file.h"
|
|
|
|
|
|
|
|
#include "epan/addr_resolv.h"
|
|
|
|
#include <epan/rtp_pt.h>
|
|
|
|
|
2015-10-04 17:10:29 +00:00
|
|
|
#include <wsutil/utf8_entities.h>
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
#include "qt_ui_utils.h"
|
2015-10-08 19:52:32 +00:00
|
|
|
#include "rtp_analysis_dialog.h"
|
2015-01-15 01:25:56 +00:00
|
|
|
#include "wireshark_application.h"
|
|
|
|
|
|
|
|
#include <QAction>
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QKeyEvent>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QTextStream>
|
|
|
|
#include <QTreeWidgetItem>
|
|
|
|
#include <QTreeWidgetItemIterator>
|
|
|
|
|
|
|
|
#include "tango_colors.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 payload_col_ = 5;
|
|
|
|
const int packets_col_ = 6;
|
|
|
|
const int lost_col_ = 7;
|
|
|
|
const int max_delta_col_ = 8;
|
|
|
|
const int max_jitter_col_ = 9;
|
|
|
|
const int mean_jitter_col_ = 10;
|
|
|
|
const int status_col_ = 11;
|
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
enum { rtp_stream_type_ = 1000 };
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
class RtpStreamTreeWidgetItem : public QTreeWidgetItem
|
|
|
|
{
|
|
|
|
public:
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem(QTreeWidget *tree, rtp_stream_info_t *stream_info) :
|
|
|
|
QTreeWidgetItem(tree, rtp_stream_type_),
|
|
|
|
stream_info_(stream_info)
|
|
|
|
{
|
2015-01-15 01:25:56 +00:00
|
|
|
drawData();
|
|
|
|
}
|
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
rtp_stream_info_t *streamInfo() const { return stream_info_; }
|
|
|
|
|
2015-01-15 01:25:56 +00:00
|
|
|
void drawData() {
|
2015-10-08 19:52:32 +00:00
|
|
|
if (!stream_info_) {
|
2015-01-15 01:25:56 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-10-08 19:52:32 +00:00
|
|
|
setText(src_addr_col_, address_to_display_qstring(&stream_info_->src_addr));
|
|
|
|
setText(src_port_col_, QString::number(stream_info_->src_port));
|
|
|
|
setText(dst_addr_col_, address_to_display_qstring(&stream_info_->dest_addr));
|
|
|
|
setText(dst_port_col_, QString::number(stream_info_->dest_port));
|
|
|
|
setText(ssrc_col_, QString("0x%1").arg(stream_info_->ssrc, 0, 16));
|
|
|
|
|
|
|
|
if (stream_info_->payload_type_name != NULL) {
|
|
|
|
setText(payload_col_, stream_info_->payload_type_name);
|
2015-01-15 01:25:56 +00:00
|
|
|
} else {
|
2015-10-08 19:52:32 +00:00
|
|
|
setText(payload_col_, val_ext_to_qstring(stream_info_->payload_type,
|
2015-01-15 01:25:56 +00:00
|
|
|
&rtp_payload_type_short_vals_ext,
|
|
|
|
"Unknown (%u)"));
|
|
|
|
}
|
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
setText(packets_col_, QString::number(stream_info_->packet_count));
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
guint32 expected;
|
|
|
|
double pct_loss;
|
2015-10-08 19:52:32 +00:00
|
|
|
expected = (stream_info_->rtp_stats.stop_seq_nr + stream_info_->rtp_stats.cycles*65536)
|
|
|
|
- stream_info_->rtp_stats.start_seq_nr + 1;
|
|
|
|
lost_ = expected - stream_info_->rtp_stats.total_nr;
|
2015-01-15 01:25:56 +00:00
|
|
|
if (expected) {
|
|
|
|
pct_loss = (double)(lost_*100.0)/(double)expected;
|
|
|
|
} else {
|
|
|
|
pct_loss = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
setText(lost_col_, QObject::tr("%1 (%L2%)").arg(lost_).arg(QString::number(pct_loss, 'f', 1)));
|
2015-10-08 19:52:32 +00:00
|
|
|
setText(max_delta_col_, QString::number(stream_info_->rtp_stats.max_delta, 'f', 3)); // This is RTP. Do we need nanoseconds?
|
|
|
|
setText(max_jitter_col_, QString::number(stream_info_->rtp_stats.max_jitter, 'f', 3));
|
|
|
|
setText(mean_jitter_col_, QString::number(stream_info_->rtp_stats.mean_jitter, 'f', 3));
|
2015-01-15 01:25:56 +00:00
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
if (stream_info_->problem) {
|
2015-01-15 01:25:56 +00:00
|
|
|
setText(status_col_, UTF8_BULLET);
|
|
|
|
setTextAlignment(status_col_, Qt::AlignCenter);
|
|
|
|
for (int i = 0; i < columnCount(); i++) {
|
|
|
|
setBackgroundColor(i, ws_css_warn_background);
|
|
|
|
setTextColor(i, ws_css_warn_text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return a QString, int, double, or invalid QVariant representing the raw column data.
|
|
|
|
QVariant colData(int col) const {
|
2015-10-08 19:52:32 +00:00
|
|
|
if (!stream_info_) {
|
2015-01-15 01:25:56 +00:00
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(col) {
|
|
|
|
case src_addr_col_:
|
|
|
|
case dst_addr_col_:
|
|
|
|
case payload_col_: // XXX Return numeric value?
|
|
|
|
return text(col);
|
|
|
|
case src_port_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->src_port;
|
2015-01-15 01:25:56 +00:00
|
|
|
case dst_port_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->dest_port;
|
2015-01-15 01:25:56 +00:00
|
|
|
case ssrc_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->ssrc;
|
2015-01-15 01:25:56 +00:00
|
|
|
case packets_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->packet_count;
|
2015-01-15 01:25:56 +00:00
|
|
|
case lost_col_:
|
|
|
|
return lost_;
|
|
|
|
case max_delta_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.max_delta;
|
2015-01-15 01:25:56 +00:00
|
|
|
case max_jitter_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.max_jitter;
|
2015-01-15 01:25:56 +00:00
|
|
|
case mean_jitter_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.mean_jitter;
|
2015-01-15 01:25:56 +00:00
|
|
|
case status_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->problem ? "Problem" : "";
|
2015-01-15 01:25:56 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator< (const QTreeWidgetItem &other) const
|
|
|
|
{
|
2015-10-08 19:52:32 +00:00
|
|
|
if (other.type() != rtp_stream_type_) return QTreeWidgetItem::operator <(other);
|
2015-01-15 01:25:56 +00:00
|
|
|
const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other);
|
|
|
|
|
|
|
|
switch (treeWidget()->sortColumn()) {
|
|
|
|
case src_addr_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return cmp_address(&(stream_info_->src_addr), &(other_rstwi.stream_info_->src_addr)) < 0;
|
2015-01-15 01:25:56 +00:00
|
|
|
case src_port_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->src_port < other_rstwi.stream_info_->src_port;
|
2015-01-15 01:25:56 +00:00
|
|
|
case dst_addr_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return cmp_address(&(stream_info_->dest_addr), &(other_rstwi.stream_info_->dest_addr)) < 0;
|
2015-01-15 01:25:56 +00:00
|
|
|
case dst_port_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->dest_port < other_rstwi.stream_info_->dest_port;
|
2015-01-15 01:25:56 +00:00
|
|
|
case ssrc_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->ssrc < other_rstwi.stream_info_->ssrc;
|
2015-01-15 01:25:56 +00:00
|
|
|
case payload_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->payload_type < other_rstwi.stream_info_->payload_type; // XXX Compare payload_type_name instead?
|
2015-01-15 01:25:56 +00:00
|
|
|
case packets_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->packet_count < other_rstwi.stream_info_->packet_count;
|
2015-01-15 01:25:56 +00:00
|
|
|
case lost_col_:
|
|
|
|
return lost_ < other_rstwi.lost_;
|
|
|
|
case max_delta_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.max_delta < other_rstwi.stream_info_->rtp_stats.max_delta;
|
2015-01-15 01:25:56 +00:00
|
|
|
case max_jitter_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.max_jitter < other_rstwi.stream_info_->rtp_stats.max_jitter;
|
2015-01-15 01:25:56 +00:00
|
|
|
case mean_jitter_col_:
|
2015-10-08 19:52:32 +00:00
|
|
|
return stream_info_->rtp_stats.mean_jitter < other_rstwi.stream_info_->rtp_stats.mean_jitter;
|
2015-01-15 01:25:56 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to string comparison
|
|
|
|
return QTreeWidgetItem::operator <(other);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-10-08 19:52:32 +00:00
|
|
|
rtp_stream_info_t *stream_info_;
|
2015-01-15 01:25:56 +00:00
|
|
|
guint32 lost_;
|
|
|
|
};
|
|
|
|
|
|
|
|
RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
|
|
|
|
WiresharkDialog(parent, cf),
|
|
|
|
ui(new Ui::RtpStreamDialog),
|
|
|
|
need_redraw_(false)
|
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
2016-02-28 18:23:20 +00:00
|
|
|
loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
|
2015-01-15 01:25:56 +00:00
|
|
|
setWindowSubtitle(tr("RTP Streams"));
|
|
|
|
ui->streamTreeWidget->installEventFilter(this);
|
|
|
|
|
|
|
|
ctx_menu_.addAction(ui->actionSelectNone);
|
|
|
|
ctx_menu_.addAction(ui->actionFindReverse);
|
|
|
|
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);
|
|
|
|
ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
2016-05-17 21:09:39 +00:00
|
|
|
ui->streamTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
|
2015-01-15 01:25:56 +00:00
|
|
|
connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
|
|
|
|
SLOT(showStreamMenu(QPoint)));
|
|
|
|
|
|
|
|
// Some GTK+ buttons have been left out intentionally in order to
|
|
|
|
// reduce clutter. Do you have a strong and informed opinion about
|
|
|
|
// this? Perhaps you should volunteer to maintain this code!
|
|
|
|
find_reverse_button_ = ui->buttonBox->addButton(ui->actionFindReverse->text(), QDialogButtonBox::ApplyRole);
|
|
|
|
find_reverse_button_->setToolTip(ui->actionFindReverse->toolTip());
|
|
|
|
prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ApplyRole);
|
|
|
|
prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
|
2015-07-31 17:34:43 +00:00
|
|
|
export_button_ = ui->buttonBox->addButton(tr("Export" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ApplyRole);
|
2015-01-15 01:25:56 +00:00
|
|
|
export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
|
|
|
|
copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ApplyRole);
|
|
|
|
analyze_button_ = ui->buttonBox->addButton(ui->actionAnalyze->text(), QDialogButtonBox::ApplyRole);
|
|
|
|
analyze_button_->setToolTip(ui->actionAnalyze->toolTip());
|
|
|
|
|
|
|
|
QMenu *copy_menu = new QMenu();
|
|
|
|
QAction *ca;
|
|
|
|
ca = copy_menu->addAction(tr("as CSV"));
|
|
|
|
ca->setToolTip(ui->actionCopyAsCsv->toolTip());
|
|
|
|
connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered()));
|
|
|
|
ca = copy_menu->addAction(tr("as YAML"));
|
|
|
|
ca->setToolTip(ui->actionCopyAsYaml->toolTip());
|
|
|
|
connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
|
|
|
|
copy_button_->setMenu(copy_menu);
|
|
|
|
|
|
|
|
/* Register the tap listener */
|
|
|
|
memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
|
2015-10-01 14:56:01 +00:00
|
|
|
tapinfo_.tap_reset = tapReset;
|
2015-01-15 01:25:56 +00:00
|
|
|
tapinfo_.tap_draw = tapDraw;
|
|
|
|
tapinfo_.tap_mark_packet = tapMarkPacket;
|
|
|
|
tapinfo_.tap_data = this;
|
|
|
|
tapinfo_.mode = TAP_ANALYSE;
|
|
|
|
|
2015-05-15 02:53:16 +00:00
|
|
|
register_tap_listener_rtp_stream(&tapinfo_, NULL);
|
2015-01-15 01:25:56 +00:00
|
|
|
/* Scan for RTP streams (redissect all packets) */
|
2015-05-15 02:53:16 +00:00
|
|
|
rtpstream_scan(&tapinfo_, cf.capFile(), NULL);
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
updateWidgets();
|
|
|
|
}
|
|
|
|
|
|
|
|
RtpStreamDialog::~RtpStreamDialog()
|
|
|
|
{
|
|
|
|
delete ui;
|
|
|
|
remove_tap_listener_rtp_stream(&tapinfo_);
|
|
|
|
}
|
|
|
|
|
2015-07-07 06:24:50 +00:00
|
|
|
bool RtpStreamDialog::eventFilter(QObject *, QEvent *event)
|
2015-01-15 01:25:56 +00:00
|
|
|
{
|
|
|
|
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:
|
|
|
|
on_actionFindReverse_triggered();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_A:
|
|
|
|
// XXX "Shift+Ctrl+A" is a fairly standard shortcut for "select none".
|
|
|
|
// However, the main window uses this for displaying the profile dialog.
|
|
|
|
// if (keyEvent.modifiers() == (Qt::ControlModifier | Qt::ShiftModifier))
|
|
|
|
// on_actionSelectNone_triggered();
|
|
|
|
// return true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-01 14:56:01 +00:00
|
|
|
void RtpStreamDialog::tapReset(rtpstream_tapinfo_t *tapinfo)
|
|
|
|
{
|
|
|
|
RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
|
|
|
|
if (rtp_stream_dialog) {
|
|
|
|
/* invalidate items which refer to old strinfo_list items. */
|
|
|
|
rtp_stream_dialog->ui->streamTreeWidget->clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 23:49:31 +00:00
|
|
|
void RtpStreamDialog::tapDraw(rtpstream_tapinfo_t *tapinfo)
|
2015-01-15 01:25:56 +00:00
|
|
|
{
|
2015-02-02 17:01:29 +00:00
|
|
|
RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
|
2015-01-15 01:25:56 +00:00
|
|
|
if (rtp_stream_dialog) {
|
|
|
|
rtp_stream_dialog->updateStreams();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd)
|
|
|
|
{
|
|
|
|
if (!tapinfo) return;
|
|
|
|
|
2015-02-02 17:01:29 +00:00
|
|
|
RtpStreamDialog *rtp_stream_dialog = dynamic_cast<RtpStreamDialog *>((RtpStreamDialog *)tapinfo->tap_data);
|
2015-01-15 01:25:56 +00:00
|
|
|
if (rtp_stream_dialog) {
|
|
|
|
cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd);
|
|
|
|
rtp_stream_dialog->need_redraw_ = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::updateStreams()
|
|
|
|
{
|
|
|
|
GList *cur_stream = g_list_nth(tapinfo_.strinfo_list, ui->streamTreeWidget->topLevelItemCount());
|
|
|
|
|
|
|
|
// Add any missing items
|
|
|
|
while (cur_stream && cur_stream->data) {
|
|
|
|
rtp_stream_info_t *stream_info = (rtp_stream_info_t*) cur_stream->data;
|
|
|
|
new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info);
|
|
|
|
cur_stream = g_list_next(cur_stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()) {
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
|
|
|
|
if (rsti->streamInfo()) {
|
|
|
|
tot_packets += rsti->streamInfo()->packet_count;
|
2015-01-15 01:25:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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(enable);
|
|
|
|
prepare_button_->setEnabled(enable);
|
|
|
|
export_button_->setEnabled(enable);
|
|
|
|
copy_button_->setEnabled(has_data);
|
2015-10-08 19:52:32 +00:00
|
|
|
analyze_button_->setEnabled(selected);
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
ui->actionFindReverse->setEnabled(enable);
|
|
|
|
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);
|
2015-10-08 19:52:32 +00:00
|
|
|
ui->actionAnalyze->setEnabled(selected);
|
2015-09-25 09:05:45 +00:00
|
|
|
|
|
|
|
WiresharkDialog::updateWidgets();
|
2015-01-15 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return row_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::captureFileClosing()
|
|
|
|
{
|
|
|
|
remove_tap_listener_rtp_stream(&tapinfo_);
|
|
|
|
WiresharkDialog::captureFileClosing();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::showStreamMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_actionAnalyze_triggered()
|
|
|
|
{
|
2015-10-08 19:52:32 +00:00
|
|
|
rtp_stream_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();
|
|
|
|
}
|
2015-01-15 01:25:56 +00:00
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
if (stream_a == NULL && stream_b == NULL) return;
|
|
|
|
|
|
|
|
RtpAnalysisDialog rtp_analysis_dialog(*this, cap_file_, stream_a, stream_b);
|
|
|
|
rtp_analysis_dialog.exec();
|
2015-01-15 01:25:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ((int) v.type() == (int) QMetaType::QString) {
|
|
|
|
rdsl << QString("\"%1\"").arg(v.toString());
|
|
|
|
} else {
|
|
|
|
rdsl << v.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream << rdsl.join(",") << endl;
|
|
|
|
}
|
|
|
|
wsApp->clipboard()->setText(stream.readAll());
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_actionCopyAsYaml_triggered()
|
|
|
|
{
|
|
|
|
QString yaml;
|
|
|
|
QTextStream stream(&yaml, QIODevice::Text);
|
|
|
|
stream << "---" << endl;
|
|
|
|
for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) {
|
|
|
|
stream << "-" << endl;
|
|
|
|
foreach (QVariant v, streamRowData(row)) {
|
|
|
|
stream << " - " << v.toString() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wsApp->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];
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
|
|
|
|
rtp_stream_info_t *stream_info = rsti->streamInfo();
|
2015-01-15 01:25:56 +00:00
|
|
|
if (stream_info) {
|
|
|
|
QString file_name;
|
|
|
|
QDir path(wsApp->lastOpenDir());
|
|
|
|
QString save_file = path.canonicalPath() + "/" + cap_file_.fileTitle();
|
|
|
|
QString extension;
|
|
|
|
file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save RTPDump As" UTF8_HORIZONTAL_ELLIPSIS)),
|
|
|
|
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) {
|
|
|
|
path = QDir(file_name);
|
|
|
|
wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_actionFindReverse_triggered()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
|
|
|
|
// Gather up our selected streams...
|
|
|
|
QList<rtp_stream_info_t *> selected_streams;
|
|
|
|
foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
|
|
|
|
rtp_stream_info_t *stream_info = rsti->streamInfo();
|
2015-01-15 01:25:56 +00:00
|
|
|
if (stream_info) {
|
|
|
|
selected_streams << stream_info;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ...and compare them to our unselected streams.
|
|
|
|
QTreeWidgetItemIterator iter(ui->streamTreeWidget, QTreeWidgetItemIterator::Unselected);
|
|
|
|
while (*iter) {
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
|
|
|
|
rtp_stream_info_t *stream_info = rsti->streamInfo();
|
|
|
|
if (stream_info) {
|
2015-01-15 01:25:56 +00:00
|
|
|
foreach (rtp_stream_info_t *fwd_stream, selected_streams) {
|
2015-10-08 19:52:32 +00:00
|
|
|
if (rtp_stream_info_is_reverse(fwd_stream, stream_info)) {
|
2015-01-15 01:25:56 +00:00
|
|
|
(*iter)->setSelected(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
|
|
|
|
rtp_stream_info_t *stream_info = rsti->streamInfo();
|
2015-01-15 01:25:56 +00:00
|
|
|
if (stream_info) {
|
|
|
|
emit goToPacket(stream_info->setup_frame_number);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_actionMarkPackets_triggered()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
rtp_stream_info_t *stream_a, *stream_b = NULL;
|
|
|
|
|
2015-10-08 19:52:32 +00:00
|
|
|
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();
|
|
|
|
}
|
2015-01-15 01:25:56 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
|
|
|
|
// Gather up our selected streams...
|
|
|
|
QStringList stream_filters;
|
|
|
|
foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
|
2015-10-08 19:52:32 +00:00
|
|
|
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
|
|
|
|
rtp_stream_info_t *stream_info = rsti->streamInfo();
|
2015-01-15 01:25:56 +00:00
|
|
|
if (stream_info) {
|
|
|
|
QString ip_proto = stream_info->src_addr.type == AT_IPv6 ? "ipv6" : "ip";
|
|
|
|
stream_filters << QString("(%1.src==%2 && udp.srcport==%3 && %1.dst==%4 && udp.dstport==%5 && rtp.ssrc==0x%6)")
|
|
|
|
.arg(ip_proto) // %1
|
|
|
|
.arg(address_to_qstring(&stream_info->src_addr)) // %2
|
|
|
|
.arg(stream_info->src_port) // %3
|
|
|
|
.arg(address_to_qstring(&stream_info->dest_addr)) // %4
|
|
|
|
.arg(stream_info->dest_port) // %5
|
|
|
|
.arg(stream_info->ssrc, 0, 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (stream_filters.length() > 0) {
|
|
|
|
QString filter = stream_filters.join(" || ");
|
|
|
|
remove_tap_listener_rtp_stream(&tapinfo_);
|
|
|
|
emit updateFilter(filter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_actionSelectNone_triggered()
|
|
|
|
{
|
|
|
|
ui->streamTreeWidget->clearSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
|
|
|
|
{
|
|
|
|
updateWidgets();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_buttonBox_clicked(QAbstractButton *button)
|
|
|
|
{
|
|
|
|
if (button == prepare_button_) {
|
|
|
|
on_actionPrepareFilter_triggered();
|
|
|
|
} else if (button == export_button_) {
|
|
|
|
on_actionExportAsRtpDump_triggered();
|
|
|
|
} else if (button == analyze_button_) {
|
2015-10-08 19:52:32 +00:00
|
|
|
on_actionAnalyze_triggered();
|
2015-01-15 01:25:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpStreamDialog::on_buttonBox_helpRequested()
|
|
|
|
{
|
|
|
|
wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Editor modelines
|
|
|
|
*
|
|
|
|
* Local Variables:
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* tab-width: 8
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
* End:
|
|
|
|
*
|
|
|
|
* ex: set shiftwidth=4 tabstop=8 expandtab:
|
|
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
|
|
*/
|