/* rtp_analysis_dialog.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * 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_analysis_dialog.h" #include #include "file.h" #include "frame_tvbuff.h" #include "epan/epan_dissect.h" #include "epan/rtp_pt.h" #include "epan/dfilter/dfilter.h" #include "epan/dissectors/packet-rtp.h" #include "ui/help_url.h" #include #include #include #include #include #include #include #include "color_utils.h" #include "qt_ui_utils.h" #include "rtp_player_dialog.h" #include "stock_icon.h" #include "wireshark_application.h" /* * @file RTP stream analysis dialog * * Displays forward and reverse RTP streams and graphs each stream */ // To do: // - Progress bar for tapping and saving. // - Add a refresh button and/or action. // - Fixup output file names. // - Add a graph title and legend when saving? enum { packet_col_, sequence_col_, delta_col_, jitter_col_, skew_col_, bandwidth_col_, marker_col_, status_col_ }; static const QRgb color_cn_ = 0xbfbfff; static const QRgb color_rtp_warn_ = 0xffdbbf; static const QRgb color_pt_event_ = 0xefffff; enum { rtp_analysis_type_ = 1000 }; class RtpAnalysisTreeWidgetItem : public QTreeWidgetItem { public: RtpAnalysisTreeWidgetItem(QTreeWidget *tree, tap_rtp_stat_t *statinfo, packet_info *pinfo, const struct _rtp_info *rtpinfo) : QTreeWidgetItem(tree, rtp_analysis_type_) { frame_num_ = pinfo->num; sequence_num_ = rtpinfo->info_seq_num; pkt_len_ = pinfo->fd->pkt_len; flags_ = statinfo->flags; if (flags_ & STAT_FLAG_FIRST) { delta_ = 0.0; jitter_ = 0.0; skew_ = 0.0; } else { delta_ = statinfo->delta; jitter_ = statinfo->jitter; skew_ = statinfo->skew; } bandwidth_ = statinfo->bandwidth; marker_ = rtpinfo->info_marker_set ? true : false; ok_ = false; QColor bg_color = QColor(); QString status; if (statinfo->pt == PT_CN) { status = "Comfort noise (PT=13, RFC 3389)"; bg_color = color_cn_; } else if (statinfo->pt == PT_CN_OLD) { status = "Comfort noise (PT=19, reserved)"; bg_color = color_cn_; } else if (statinfo->flags & STAT_FLAG_WRONG_SEQ) { status = "Wrong sequence number"; bg_color = ColorUtils::expert_color_error; } else if (statinfo->flags & STAT_FLAG_DUP_PKT) { status = "Suspected duplicate (MAC address) only delta time calculated"; bg_color = color_rtp_warn_; } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) { status = QString("Payload changed to PT=%1").arg(statinfo->pt); if (statinfo->flags & STAT_FLAG_PT_T_EVENT) { status.append(" telephone/event"); } bg_color = color_rtp_warn_; } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) { status = "Incorrect timestamp"; /* color = COLOR_WARNING; */ bg_color = color_rtp_warn_; } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE) && !(statinfo->flags & STAT_FLAG_FIRST) && !(statinfo->flags & STAT_FLAG_PT_CN) && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN) && !(statinfo->flags & STAT_FLAG_MARKER)) { status = "Marker missing?"; bg_color = color_rtp_warn_; } else if (statinfo->flags & STAT_FLAG_PT_T_EVENT) { status = QString("PT=%1 telephone/event").arg(statinfo->pt); /* XXX add color? */ bg_color = color_pt_event_; } else { if (statinfo->flags & STAT_FLAG_MARKER) { bg_color = color_rtp_warn_; } } if (status.isEmpty()) { ok_ = true; status = UTF8_CHECK_MARK; } setText(packet_col_, QString::number(frame_num_)); setText(sequence_col_, QString::number(sequence_num_)); setText(delta_col_, QString::number(delta_, 'f', 2)); setText(jitter_col_, QString::number(jitter_, 'f', 2)); setText(skew_col_, QString::number(skew_, 'f', 2)); setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2)); if (marker_) { setText(marker_col_, UTF8_BULLET); } setText(status_col_, status); setTextAlignment(packet_col_, Qt::AlignRight); setTextAlignment(sequence_col_, Qt::AlignRight); setTextAlignment(delta_col_, Qt::AlignRight); setTextAlignment(jitter_col_, Qt::AlignRight); setTextAlignment(skew_col_, Qt::AlignRight); setTextAlignment(bandwidth_col_, Qt::AlignRight); setTextAlignment(marker_col_, Qt::AlignCenter); if (bg_color.isValid()) { for (int col = 0; col < columnCount(); col++) { setBackground(col, bg_color); setForeground(col, ColorUtils::expert_color_foreground); } } } guint32 frameNum() { return frame_num_; } bool frameStatus() { return ok_; } QList rowData() { QString marker_str; QString status_str = ok_ ? "OK" : text(status_col_); if (marker_) marker_str = "SET"; return QList() << frame_num_ << sequence_num_ << delta_ << jitter_ << skew_ << bandwidth_ << marker_str << status_str; } bool operator< (const QTreeWidgetItem &other) const { if (other.type() != rtp_analysis_type_) return QTreeWidgetItem::operator< (other); const RtpAnalysisTreeWidgetItem *other_row = static_cast(&other); switch (treeWidget()->sortColumn()) { case (packet_col_): return frame_num_ < other_row->frame_num_; break; case (sequence_col_): return sequence_num_ < other_row->sequence_num_; break; case (delta_col_): return delta_ < other_row->delta_; break; case (jitter_col_): return jitter_ < other_row->jitter_; break; case (skew_col_): return skew_ < other_row->skew_; break; case (bandwidth_col_): return bandwidth_ < other_row->bandwidth_; break; default: break; } // Fall back to string comparison return QTreeWidgetItem::operator <(other); } private: guint32 frame_num_; guint32 sequence_num_; guint32 pkt_len_; guint32 flags_; double delta_; double jitter_; double skew_; double bandwidth_; bool marker_; bool ok_; }; enum { fwd_jitter_graph_, fwd_diff_graph_, fwd_delta_graph_, rev_jitter_graph_, rev_diff_graph_, rev_delta_graph_, num_graphs_ }; RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf, struct _rtp_stream_info *stream_fwd, struct _rtp_stream_info *stream_rev) : WiresharkDialog(parent, cf), ui(new Ui::RtpAnalysisDialog), port_src_fwd_(0), port_dst_fwd_(0), ssrc_fwd_(0), packet_count_fwd_(0), setup_frame_number_fwd_(0), port_src_rev_(0), port_dst_rev_(0), ssrc_rev_(0), packet_count_rev_(0), setup_frame_number_rev_(0), num_streams_(0), save_payload_error_(TAP_RTP_NO_ERROR) { ui->setupUi(this); loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5); setWindowSubtitle(tr("RTP Stream Analysis")); ui->progressFrame->hide(); player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox); stream_ctx_menu_.addAction(ui->actionGoToPacket); stream_ctx_menu_.addAction(ui->actionNextProblem); stream_ctx_menu_.addSeparator(); stream_ctx_menu_.addAction(ui->actionSaveAudio); stream_ctx_menu_.addAction(ui->actionSaveForwardAudio); stream_ctx_menu_.addAction(ui->actionSaveReverseAudio); stream_ctx_menu_.addSeparator(); stream_ctx_menu_.addAction(ui->actionSaveCsv); stream_ctx_menu_.addAction(ui->actionSaveForwardCsv); stream_ctx_menu_.addAction(ui->actionSaveReverseCsv); stream_ctx_menu_.addSeparator(); stream_ctx_menu_.addAction(ui->actionSaveGraph); ui->forwardTreeWidget->installEventFilter(this); ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->forwardTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder); connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showStreamMenu(QPoint))); ui->reverseTreeWidget->installEventFilter(this); ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->reverseTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder); connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showStreamMenu(QPoint))); connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*))); graph_ctx_menu_.addAction(ui->actionSaveGraph); QStringList header_labels; for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) { header_labels << ui->forwardTreeWidget->headerItem()->text(i); } ui->reverseTreeWidget->setHeaderLabels(header_labels); memset(&src_fwd_, 0, sizeof(address)); memset(&dst_fwd_, 0, sizeof(address)); memset(&src_rev_, 0, sizeof(address)); memset(&dst_rev_, 0, sizeof(address)); nstime_set_zero(&start_rel_time_fwd_); nstime_set_zero(&start_rel_time_rev_); QList graph_cbs = QList() << ui->fJitterCheckBox << ui->fDiffCheckBox << ui->fDeltaCheckBox << ui->rJitterCheckBox << ui->rDiffCheckBox << ui->rDeltaCheckBox; for (int i = 0; i < num_graphs_; i++) { QCPGraph *graph = ui->streamGraph->addGraph(); graph->setPen(QPen(ColorUtils::graphColor(i))); graph->setName(graph_cbs[i]->text()); graphs_ << graph; graph_cbs[i]->setChecked(true); graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text)); } ui->streamGraph->xAxis->setLabel("Arrival Time"); ui->streamGraph->yAxis->setLabel("Value (ms)"); // We keep our temp files open for the lifetime of the dialog. The GTK+ // UI opens and closes at various points. QString tempname = QString("%1/wireshark_rtp_f").arg(QDir::tempPath()); fwd_tempfile_ = new QTemporaryFile(tempname, this); fwd_tempfile_->open(); tempname = QString("%1/wireshark_rtp_r").arg(QDir::tempPath()); rev_tempfile_ = new QTemporaryFile(tempname, this); rev_tempfile_->open(); if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) { err_str_ = tr("Unable to save RTP data."); ui->actionSaveAudio->setEnabled(false); ui->actionSaveForwardAudio->setEnabled(false); ui->actionSaveReverseAudio->setEnabled(false); } QMenu *save_menu = new QMenu(); save_menu->addAction(ui->actionSaveAudio); save_menu->addAction(ui->actionSaveForwardAudio); save_menu->addAction(ui->actionSaveReverseAudio); save_menu->addSeparator(); save_menu->addAction(ui->actionSaveCsv); save_menu->addAction(ui->actionSaveForwardCsv); save_menu->addAction(ui->actionSaveReverseCsv); save_menu->addSeparator(); save_menu->addAction(ui->actionSaveGraph); ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu); if (stream_fwd) { // XXX What if stream_fwd == 0 && stream_rev != 0? copy_address(&src_fwd_, &(stream_fwd->src_addr)); port_src_fwd_ = stream_fwd->src_port; copy_address(&dst_fwd_, &(stream_fwd->dest_addr)); port_dst_fwd_ = stream_fwd->dest_port; ssrc_fwd_ = stream_fwd->ssrc; packet_count_fwd_ = stream_fwd->packet_count; setup_frame_number_fwd_ = stream_fwd->setup_frame_number; nstime_copy(&start_rel_time_fwd_, &stream_fwd->start_rel_time); num_streams_++; if (stream_rev) { copy_address(&src_rev_, &(stream_rev->src_addr)); port_src_rev_ = stream_rev->src_port; copy_address(&dst_rev_, &(stream_rev->dest_addr)); port_dst_rev_ = stream_rev->dest_port; ssrc_rev_ = stream_rev->ssrc; packet_count_rev_ = stream_rev->packet_count; setup_frame_number_rev_ = stream_rev->setup_frame_number; nstime_copy(&start_rel_time_rev_, &stream_rev->start_rel_time); num_streams_++; } } else { findStreams(); } if (err_str_.isEmpty() && num_streams_ < 1) { err_str_ = tr("No streams found."); } registerTapListener("rtp", this, NULL, 0, tapReset, tapPacket, tapDraw); cap_file_.retapPackets(); removeTapListeners(); connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateWidgets())); connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(updateWidgets())); connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(updateWidgets())); connect(&cap_file_, SIGNAL(captureFileClosing()), this, SLOT(updateWidgets())); updateWidgets(); updateStatistics(); } RtpAnalysisDialog::~RtpAnalysisDialog() { delete ui; // remove_tap_listener_rtp_stream(&tapinfo_); delete fwd_tempfile_; delete rev_tempfile_; } void RtpAnalysisDialog::updateWidgets() { bool enable_tab = false; QString hint = err_str_; if (hint.isEmpty()) { enable_tab = true; hint = tr("%1 streams found.").arg(num_streams_); } else if (save_payload_error_ != TAP_RTP_NO_ERROR) { /* We cannot save the payload but can still display the widget or save CSV data */ enable_tab = true; } bool enable_nav = false; if (!file_closed_ && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget && ui->forwardTreeWidget->selectedItems().length() > 0) || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget && ui->reverseTreeWidget->selectedItems().length() > 0))) { enable_nav = true; } ui->actionGoToPacket->setEnabled(enable_nav); ui->actionNextProblem->setEnabled(enable_nav); if (enable_nav) { hint.append(tr(" G: Go to packet, N: Next problem packet")); } bool enable_save_fwd_audio = fwd_tempfile_->isOpen() && (save_payload_error_ == TAP_RTP_NO_ERROR); bool enable_save_rev_audio = rev_tempfile_->isOpen() && (save_payload_error_ == TAP_RTP_NO_ERROR); ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio); ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio); ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio); bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0; bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0; ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv); ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv); ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv); #if defined(QT_MULTIMEDIA_LIB) player_button_->setEnabled(num_streams_ > 0); #else player_button_->setEnabled(false); player_button_->setText(tr("No Audio")); #endif ui->tabWidget->setEnabled(enable_tab); hint.prepend(""); hint.append(""); ui->hintLabel->setText(hint); WiresharkDialog::updateWidgets(); } void RtpAnalysisDialog::on_actionGoToPacket_triggered() { if (file_closed_) return; QTreeWidget *cur_tree = qobject_cast(ui->tabWidget->currentWidget()); if (!cur_tree || cur_tree->selectedItems().length() < 1) return; QTreeWidgetItem *ti = cur_tree->selectedItems()[0]; if (ti->type() != rtp_analysis_type_) return; RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast((RtpAnalysisTreeWidgetItem *)ti); emit goToPacket(ra_ti->frameNum()); } void RtpAnalysisDialog::on_actionNextProblem_triggered() { QTreeWidget *cur_tree = qobject_cast(ui->tabWidget->currentWidget()); if (!cur_tree || cur_tree->topLevelItemCount() < 2) return; // Choose convenience over correctness. if (cur_tree->selectedItems().length() < 1) { cur_tree->setCurrentItem(cur_tree->topLevelItem(0)); } QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0]; if (sel_ti->type() != rtp_analysis_type_) return; QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti); while (test_ti != sel_ti) { if (!test_ti) test_ti = cur_tree->topLevelItem(0); RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast((RtpAnalysisTreeWidgetItem *)test_ti); if (!ra_ti->frameStatus()) { cur_tree->setCurrentItem(ra_ti); break; } test_ti = cur_tree->itemBelow(test_ti); } } void RtpAnalysisDialog::on_fJitterCheckBox_toggled(bool checked) { ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_fDiffCheckBox_toggled(bool checked) { ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_fDeltaCheckBox_toggled(bool checked) { ui->streamGraph->graph(fwd_delta_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_rJitterCheckBox_toggled(bool checked) { ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_rDiffCheckBox_toggled(bool checked) { ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_rDeltaCheckBox_toggled(bool checked) { ui->streamGraph->graph(rev_delta_graph_)->setVisible(checked); updateGraph(); } void RtpAnalysisDialog::on_actionSaveAudio_triggered() { saveAudio(dir_both_); } void RtpAnalysisDialog::on_actionSaveForwardAudio_triggered() { saveAudio(dir_forward_); } void RtpAnalysisDialog::on_actionSaveReverseAudio_triggered() { saveAudio(dir_reverse_); } void RtpAnalysisDialog::on_actionSaveCsv_triggered() { saveCsv(dir_both_); } void RtpAnalysisDialog::on_actionSaveForwardCsv_triggered() { saveCsv(dir_forward_); } void RtpAnalysisDialog::on_actionSaveReverseCsv_triggered() { saveCsv(dir_reverse_); } void RtpAnalysisDialog::on_actionSaveGraph_triggered() { ui->tabWidget->setCurrentWidget(ui->graphTab); QString file_name, extension; QDir path(wsApp->lastOpenDir()); QString pdf_filter = tr("Portable Document Format (*.pdf)"); QString png_filter = tr("Portable Network Graphics (*.png)"); QString bmp_filter = tr("Windows Bitmap (*.bmp)"); // Gaze upon my beautiful graph with lossy artifacts! QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)"); QString filter = QString("%1;;%2;;%3;;%4") .arg(pdf_filter) .arg(png_filter) .arg(bmp_filter) .arg(jpeg_filter); QString save_file = path.canonicalPath(); if (!file_closed_) { save_file += QString("/%1").arg(cap_file_.fileTitle()); } file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)), save_file, filter, &extension); if (!file_name.isEmpty()) { bool save_ok = false; // http://www.qcustomplot.com/index.php/support/forum/63 // ui->streamGraph->legend->setVisible(true); if (extension.compare(pdf_filter) == 0) { save_ok = ui->streamGraph->savePdf(file_name); } else if (extension.compare(png_filter) == 0) { save_ok = ui->streamGraph->savePng(file_name); } else if (extension.compare(bmp_filter) == 0) { save_ok = ui->streamGraph->saveBmp(file_name); } else if (extension.compare(jpeg_filter) == 0) { save_ok = ui->streamGraph->saveJpg(file_name); } // ui->streamGraph->legend->setVisible(false); // else error dialog? if (save_ok) { path = QDir(file_name); wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData()); } } } void RtpAnalysisDialog::on_buttonBox_clicked(QAbstractButton *button) { if (button == player_button_) { showPlayer(); } } void RtpAnalysisDialog::on_buttonBox_helpRequested() { wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG); } void RtpAnalysisDialog::tapReset(void *tapinfo_ptr) { RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast((RtpAnalysisDialog*)tapinfo_ptr); if (!rtp_analysis_dialog) return; rtp_analysis_dialog->resetStatistics(); } gboolean RtpAnalysisDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr) { RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast((RtpAnalysisDialog*)tapinfo_ptr); if (!rtp_analysis_dialog) return FALSE; const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr; if (!rtpinfo) return FALSE; /* we ignore packets that are not displayed */ if (pinfo->fd->flags.passed_dfilter == 0) return FALSE; /* also ignore RTP Version != 2 */ else if (rtpinfo->info_version != 2) return FALSE; /* is it the forward direction? */ else if (rtp_analysis_dialog->ssrc_fwd_ == rtpinfo->info_sync_src && (cmp_address(&(rtp_analysis_dialog->src_fwd_), &(pinfo->src)) == 0) && (rtp_analysis_dialog->port_src_fwd_ == pinfo->srcport) && (cmp_address(&(rtp_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0) && (rtp_analysis_dialog->port_dst_fwd_ == pinfo->destport)) { rtp_analysis_dialog->addPacket(true, pinfo, rtpinfo); } /* is it the reversed direction? */ else if (rtp_analysis_dialog->ssrc_rev_ == rtpinfo->info_sync_src && (cmp_address(&(rtp_analysis_dialog->src_rev_), &(pinfo->src)) == 0) && (rtp_analysis_dialog->port_src_rev_ == pinfo->srcport) && (cmp_address(&(rtp_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0) && (rtp_analysis_dialog->port_dst_rev_ == pinfo->destport)) { rtp_analysis_dialog->addPacket(false, pinfo, rtpinfo); } return FALSE; } void RtpAnalysisDialog::tapDraw(void *tapinfo_ptr) { RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast((RtpAnalysisDialog*)tapinfo_ptr); if (!rtp_analysis_dialog) return; rtp_analysis_dialog->updateStatistics(); } void RtpAnalysisDialog::resetStatistics() { memset(&fwd_statinfo_, 0, sizeof(tap_rtp_stat_t)); memset(&rev_statinfo_, 0, sizeof(tap_rtp_stat_t)); fwd_statinfo_.first_packet = TRUE; rev_statinfo_.first_packet = TRUE; fwd_statinfo_.reg_pt = PT_UNDEFINED; rev_statinfo_.reg_pt = PT_UNDEFINED; ui->forwardTreeWidget->clear(); ui->reverseTreeWidget->clear(); for (int i = 0; i < ui->streamGraph->graphCount(); i++) { ui->streamGraph->graph(i)->clearData(); } fwd_time_vals_.clear(); fwd_jitter_vals_.clear(); fwd_diff_vals_.clear(); fwd_delta_vals_.clear(); rev_time_vals_.clear(); rev_jitter_vals_.clear(); rev_diff_vals_.clear(); rev_delta_vals_.clear(); fwd_tempfile_->resize(0); rev_tempfile_->resize(0); } void RtpAnalysisDialog::addPacket(bool forward, packet_info *pinfo, const _rtp_info *rtpinfo) { /* add this RTP for future listening using the RTP Player*/ // add_rtp_packet(rtpinfo, pinfo); if (forward) { rtp_packet_analyse(&fwd_statinfo_, pinfo, rtpinfo); new RtpAnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo, rtpinfo); fwd_time_vals_.append(fwd_statinfo_.time / 1000); fwd_jitter_vals_.append(fwd_statinfo_.jitter); fwd_diff_vals_.append(fwd_statinfo_.diff); fwd_delta_vals_.append(fwd_statinfo_.delta); savePayload(fwd_tempfile_, &fwd_statinfo_, pinfo, rtpinfo); } else { rtp_packet_analyse(&rev_statinfo_, pinfo, rtpinfo); new RtpAnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo, rtpinfo); rev_time_vals_.append(rev_statinfo_.time / 1000); rev_jitter_vals_.append(rev_statinfo_.jitter); rev_diff_vals_.append(rev_statinfo_.diff); rev_delta_vals_.append(rev_statinfo_.delta); savePayload(rev_tempfile_, &rev_statinfo_, pinfo, rtpinfo); } } // rtp_analysis.c:rtp_packet_save_payload const unsigned int max_silence_ticks_ = 1000000; const guint8 silence_pcmu_ = 0xff; const guint8 silence_pcma_ = 0x55; void RtpAnalysisDialog::savePayload(QTemporaryFile *tmpfile, tap_rtp_stat_t *statinfo, packet_info *pinfo, const _rtp_info *rtpinfo) { /* Is this the first packet we got in this direction? */ // if (statinfo->flags & STAT_FLAG_FIRST) { // if (saveinfo->fp == NULL) { // saveinfo->saved = FALSE; // saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR; // } else { // saveinfo->saved = TRUE; // } // } /* Save the voice information */ /* If there was already an error, we quit */ if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return; /* Quit if the captured length and packet length aren't equal or * if the RTP dissector thinks there is some information missing */ if ((pinfo->fd->pkt_len != pinfo->fd->cap_len) && (!rtpinfo->info_all_data_present)) { tmpfile->close(); err_str_ = tr("Can't save in a file: Wrong length of captured packets."); save_payload_error_ = TAP_RTP_WRONG_LENGTH; return; } /* If padding bit is set but the padding count is bigger * then the whole RTP data - error with padding count */ if ((rtpinfo->info_padding_set != FALSE) && (rtpinfo->info_padding_count > rtpinfo->info_payload_len)) { tmpfile->close(); err_str_ = tr("Can't save in a file: RTP data with padding."); save_payload_error_ = TAP_RTP_PADDING_ERROR; return; } /* Do we need to insert some silence? */ if ((rtpinfo->info_marker_set) && !(statinfo->flags & STAT_FLAG_FIRST) && !(statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) && (statinfo->delta_timestamp > (rtpinfo->info_payload_len - rtpinfo->info_padding_count))) { /* the amount of silence should be the difference between * the last timestamp and the current one minus x * x should equal the amount of information in the last frame * XXX not done yet */ for (unsigned int i = 0; (i < (statinfo->delta_timestamp - rtpinfo->info_payload_len - rtpinfo->info_padding_count)) && (i < max_silence_ticks_); i++) { guint8 tmp; size_t nchars; switch (statinfo->reg_pt) { case PT_PCMU: tmp = silence_pcmu_; break; case PT_PCMA: tmp = silence_pcma_; break; default: tmp = 0; break; } nchars = tmpfile->write((char *)&tmp, 1); if (nchars != 1) { /* Write error or short write */ tmpfile->close(); err_str_ = tr("Can't save in a file: File I/O problem."); save_payload_error_ = TAP_RTP_FILE_IO_ERROR; return; } } } if ((rtpinfo->info_payload_type == PT_CN) || (rtpinfo->info_payload_type == PT_CN_OLD)) { } else { /* All other payloads */ const char *data; size_t nchars; if (!rtpinfo->info_all_data_present) { /* Not all the data was captured. */ tmpfile->close(); err_str_ = tr("Can't save in a file: Not all data in all packets was captured."); save_payload_error_ = TAP_RTP_WRONG_LENGTH; return; } /* We put the pointer at the beginning of the RTP * payload, that is, at the beginning of the RTP data * plus the offset of the payload from the beginning * of the RTP data */ data = (const char *) rtpinfo->info_data + rtpinfo->info_payload_offset; nchars = tmpfile->write(data, rtpinfo->info_payload_len - rtpinfo->info_padding_count); if (nchars != (rtpinfo->info_payload_len - rtpinfo->info_padding_count)) { /* Write error or short write */ err_str_ = tr("Can't save in a file: File I/O problem."); save_payload_error_ = TAP_RTP_FILE_IO_ERROR; tmpfile->close(); return; } return; } return; } void RtpAnalysisDialog::updateStatistics() { unsigned int f_clock_rate = fwd_statinfo_.clock_rate; unsigned int r_clock_rate = rev_statinfo_.clock_rate; unsigned int f_expected = (fwd_statinfo_.stop_seq_nr + fwd_statinfo_.cycles*65536) - fwd_statinfo_.start_seq_nr + 1; unsigned int r_expected = (rev_statinfo_.stop_seq_nr + rev_statinfo_.cycles*65536) - rev_statinfo_.start_seq_nr + 1; unsigned int f_total_nr = fwd_statinfo_.total_nr; unsigned int r_total_nr = rev_statinfo_.total_nr; int f_lost = f_expected - f_total_nr; int r_lost = r_expected - r_total_nr; double f_sumt = fwd_statinfo_.sumt; double f_sumTS = fwd_statinfo_.sumTS; double f_sumt2 = fwd_statinfo_.sumt2; double f_sumtTS = fwd_statinfo_.sumtTS; double r_sumt = rev_statinfo_.sumt; double r_sumTS = rev_statinfo_.sumTS; double r_sumt2 = rev_statinfo_.sumt2; double r_sumtTS = rev_statinfo_.sumtTS; double f_perc, r_perc; double f_clock_drift = 1.0; double r_clock_drift = 1.0; double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; double r_duration = rev_statinfo_.time - rev_statinfo_.start_time; if (f_clock_rate == 0) { f_clock_rate = 1; } if (r_clock_rate == 0) { r_clock_rate = 1; } if (f_expected) { f_perc = (double)(f_lost*100)/(double)f_expected; } else { f_perc = 0; } if (r_expected) { r_perc = (double)(r_lost*100)/(double)r_expected; } else { r_perc = 0; } if ((f_total_nr >0) && (f_sumt2 > 0)) { f_clock_drift = (f_total_nr * f_sumtTS - f_sumt * f_sumTS) / (f_total_nr * f_sumt2 - f_sumt * f_sumt); } if ((r_total_nr >0) && (r_sumt2 > 0)) { r_clock_drift = (r_total_nr * r_sumtTS - r_sumt * r_sumTS) / (r_total_nr * r_sumt2 - r_sumt * r_sumt); } QString stats_tables = "\n"; stats_tables += QString("

%1:%2 " UTF8_LEFT_RIGHT_ARROW) .arg(address_to_qstring(&src_fwd_, true)) .arg(port_src_fwd_); stats_tables += QString("
%1:%2

\n") .arg(address_to_qstring(&dst_fwd_, true)) .arg(port_dst_fwd_); stats_tables += "

Forward

\n"; stats_tables += "

\n"; stats_tables += QString("") .arg(int_to_qstring(ssrc_fwd_, 8, 16)); stats_tables += QString("") .arg(fwd_statinfo_.max_delta, 0, 'f', 2) .arg(fwd_statinfo_.max_nr); stats_tables += QString("") .arg(fwd_statinfo_.max_jitter, 0, 'f', 2); stats_tables += QString("") .arg(fwd_statinfo_.mean_jitter, 0, 'f', 2); stats_tables += QString("") .arg(fwd_statinfo_.max_skew, 0, 'f', 2); stats_tables += QString("") .arg(f_total_nr); stats_tables += QString("") .arg(f_expected); stats_tables += QString("") .arg(f_lost).arg(f_perc, 0, 'f', 2); stats_tables += QString("") .arg(fwd_statinfo_.sequence); stats_tables += QString("") .arg(f_duration / 1000.0, 0, 'f', 2); stats_tables += QString("") .arg(f_duration * (f_clock_drift - 1.0), 0, 'f', 0); stats_tables += QString("") // XXX Terminology? .arg(f_clock_drift * f_clock_rate, 0, 'f', 0).arg(100.0 * (f_clock_drift - 1.0), 0, 'f', 2); stats_tables += "
SSRC%1
Max Delta%1 ms @ %2
Max Jitter%1 ms
Mean Jitter%1 ms
Max Skew%1 ms
RTP Packets%1
Expected%1
Lost%1 (%2 %)
Seq Errs%1
Duration%1 s
Clock Drift%1 ms
Freq Drift%1 Hz (%2 %)

\n"; stats_tables += "

Reverse

\n"; stats_tables += "

\n"; stats_tables += QString("") .arg(int_to_qstring(ssrc_fwd_, 8, 16)); stats_tables += QString("") .arg(rev_statinfo_.max_delta, 0, 'f', 2) .arg(rev_statinfo_.max_nr); stats_tables += QString("") .arg(rev_statinfo_.max_jitter, 0, 'f', 2); stats_tables += QString("") .arg(rev_statinfo_.mean_jitter, 0, 'f', 2); stats_tables += QString("") .arg(rev_statinfo_.max_skew, 0, 'f', 2); stats_tables += QString("") .arg(r_total_nr); stats_tables += QString("") .arg(r_expected); stats_tables += QString("") .arg(r_lost).arg(r_perc, 0, 'f', 2); stats_tables += QString("") .arg(rev_statinfo_.sequence); stats_tables += QString("") .arg(r_duration / 1000.0, 0, 'f', 2); stats_tables += QString("") .arg(r_duration * (r_clock_drift - 1.0), 0, 'f', 0); stats_tables += QString("") // XXX Terminology? .arg(r_clock_drift * r_clock_rate, 0, 'f', 0).arg(100.0 * (r_clock_drift - 1.0), 0, 'f', 2); stats_tables += "
SSRC%1
Max Delta%1 ms @ %2
Max Jitter%1 ms
Mean Jitter%1 ms
Max Skew%1 ms
RTP Packets%1
Expected%1
Lost%1 (%2 %)
Seq Errs%1
Duration%1 s
Clock Drift%1 ms
Freq Drift%1 Hz (%2 %)

\n"; ui->statisticsLabel->setText(stats_tables); for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) { ui->forwardTreeWidget->resizeColumnToContents(col); ui->reverseTreeWidget->resizeColumnToContents(col); } graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_); graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_); graphs_[fwd_delta_graph_]->setData(fwd_time_vals_, fwd_delta_vals_); graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_); graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_); graphs_[rev_delta_graph_]->setData(rev_time_vals_, rev_delta_vals_); updateGraph(); updateWidgets(); } void RtpAnalysisDialog::updateGraph() { for (int i = 0; i < ui->streamGraph->graphCount(); i++) { if (ui->streamGraph->graph(i)->visible()) { ui->streamGraph->graph(i)->rescaleAxes(i > 0); } } ui->streamGraph->replot(); } void RtpAnalysisDialog::showPlayer() { #ifdef QT_MULTIMEDIA_LIB if (num_streams_ < 1) return; RtpPlayerDialog rtp_player_dialog(*this, cap_file_); rtp_stream_info_t stream_info; // XXX We might want to create an "rtp_stream_id_t" struct with only // addresses, ports & SSRC. memset(&stream_info, 0, sizeof(stream_info)); copy_address(&(stream_info.src_addr), &src_fwd_); stream_info.src_port = port_src_fwd_; copy_address(&(stream_info.dest_addr), &dst_fwd_); stream_info.dest_port = port_dst_fwd_; stream_info.ssrc = ssrc_fwd_; stream_info.packet_count = packet_count_fwd_; stream_info.setup_frame_number = setup_frame_number_fwd_; nstime_copy(&stream_info.start_rel_time, &start_rel_time_fwd_); rtp_player_dialog.addRtpStream(&stream_info); if (num_streams_ > 1) { copy_address(&(stream_info.src_addr), &src_rev_); stream_info.src_port = port_src_rev_; copy_address(&(stream_info.dest_addr), &dst_rev_); stream_info.dest_port = port_dst_rev_; stream_info.ssrc = ssrc_rev_; stream_info.packet_count = packet_count_rev_; stream_info.setup_frame_number = setup_frame_number_rev_; rtp_player_dialog.addRtpStream(&stream_info); nstime_copy(&stream_info.start_rel_time, &start_rel_time_rev_); } connect(&rtp_player_dialog, SIGNAL(goToPacket(int)), this, SIGNAL(goToPacket(int))); rtp_player_dialog.exec(); #endif // QT_MULTIMEDIA_LIB } // rtp_analysis.c:copy_file enum { save_audio_none_, save_audio_au_, save_audio_raw_ }; void RtpAnalysisDialog::saveAudio(RtpAnalysisDialog::StreamDirection direction) { if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return; QString caption; switch (direction) { case dir_forward_: caption = tr("Save forward stream audio"); break; case dir_reverse_: caption = tr("Save reverse stream audio"); break; case dir_both_: default: caption = tr("Save audio"); break; } QString ext_filter = tr("Sun Audio (*.au)"); if (direction != dir_both_) { ext_filter.append(tr(";;Raw (*.raw)")); } QString sel_filter; QString file_path = QFileDialog::getSaveFileName( this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"), ext_filter, &sel_filter); if (file_path.isEmpty()) return; int save_format = save_audio_none_; if (file_path.endsWith(".au")) { save_format = save_audio_au_; } else if (file_path.endsWith(".raw")) { save_format = save_audio_raw_; } if (save_format == save_audio_none_) { QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format")); return; } if (save_format == save_audio_au_) { if ((((direction == dir_forward_) || (direction == dir_both_)) && (fwd_statinfo_.pt != PT_PCMU) && (fwd_statinfo_.pt != PT_PCMA)) || (((direction == dir_reverse_) || (direction == dir_both_)) && (rev_statinfo_.pt != PT_PCMU) && (rev_statinfo_.pt != PT_PCMA))) { QMessageBox::warning(this, tr("Warning"), tr("Can't save in a file: saving in au format supported only for alaw/ulaw streams")); return; } } QFile save_file(file_path); gint16 sample; gchar pd[4]; gboolean stop_flag = FALSE; size_t nchars; save_file.open(QIODevice::WriteOnly); fwd_tempfile_->seek(0); rev_tempfile_->seek(0); if (save_file.error() != QFile::NoError) { QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName())); return; } ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName())); ui->progressFrame->showProgress(true, true, &stop_flag); if (save_format == save_audio_au_) { /* au format */ /* First we write the .au header. XXX Hope this is endian independent */ /* the magic word 0x2e736e64 == .snd */ phton32(pd, 0x2e736e64); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; /* header offset == 24 bytes */ phton32(pd, 24); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; /* total length; it is permitted to set this to 0xffffffff */ phton32(pd, 0xffffffff); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; /* encoding format == 16-bit linear PCM */ phton32(pd, 3); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; /* sample rate == 8000 Hz */ phton32(pd, 8000); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; /* channels == 1 */ phton32(pd, 1); nchars = save_file.write((const char *)pd, 4); if (nchars != 4) goto copy_file_err; switch (direction) { /* Only forward direction */ case dir_forward_: { char f_rawvalue; while (fwd_tempfile_->getChar(&f_rawvalue)) { if (stop_flag) { break; } ui->progressFrame->setValue(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size()); if (fwd_statinfo_.pt == PT_PCMU) { sample = ulaw2linear((unsigned char)f_rawvalue); phton16(pd, sample); } else if (fwd_statinfo_.pt == PT_PCMA) { sample = alaw2linear((unsigned char)f_rawvalue); phton16(pd, sample); } else { goto copy_file_err; } nchars = save_file.write((const char *)pd, 2); if (nchars < 2) { goto copy_file_err; } } break; } /* Only reverse direction */ case dir_reverse_: { char r_rawvalue; while (rev_tempfile_->getChar(&r_rawvalue)) { if (stop_flag) { break; } ui->progressFrame->setValue(rev_tempfile_->pos() * 100 / rev_tempfile_->size()); if (rev_statinfo_.pt == PT_PCMU) { sample = ulaw2linear((unsigned char)r_rawvalue); phton16(pd, sample); } else if (rev_statinfo_.pt == PT_PCMA) { sample = alaw2linear((unsigned char)r_rawvalue); phton16(pd, sample); } else { goto copy_file_err; } nchars = save_file.write((const char *)pd, 2); if (nchars < 2) { goto copy_file_err; } } break; } /* Both directions */ case dir_both_: { char f_rawvalue, r_rawvalue; guint32 f_write_silence = 0; guint32 r_write_silence = 0; /* since conversation in one way can start later than in the other one, * we have to write some silence information for one channel */ if (fwd_statinfo_.start_time > rev_statinfo_.start_time) { f_write_silence = (guint32) ((fwd_statinfo_.start_time - rev_statinfo_.start_time) * (8000/1000)); } else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) { r_write_silence = (guint32) ((rev_statinfo_.start_time - fwd_statinfo_.start_time) * (8000/1000)); } for (;;) { if (stop_flag) { break; } int fwd_pct = (fwd_tempfile_->size() > 0 ? fwd_tempfile_->pos() * 100 / fwd_tempfile_->size() : 0); int rev_pct = (rev_tempfile_->size() > 0 ? rev_tempfile_->pos() * 100 / rev_tempfile_->size() : 0); ui->progressFrame->setValue(qMin(fwd_pct, rev_pct)); if (f_write_silence > 0) { rev_tempfile_->getChar(&r_rawvalue); switch (fwd_statinfo_.reg_pt) { case PT_PCMU: f_rawvalue = silence_pcmu_; break; case PT_PCMA: f_rawvalue = silence_pcma_; break; default: f_rawvalue = 0; break; } f_write_silence--; } else if (r_write_silence > 0) { fwd_tempfile_->getChar(&f_rawvalue); switch (rev_statinfo_.reg_pt) { case PT_PCMU: r_rawvalue = silence_pcmu_; break; case PT_PCMA: r_rawvalue = silence_pcma_; break; default: r_rawvalue = 0; break; } r_write_silence--; } else { fwd_tempfile_->getChar(&f_rawvalue); rev_tempfile_->getChar(&r_rawvalue); } if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd()) break; if ((fwd_statinfo_.pt == PT_PCMU) && (rev_statinfo_.pt == PT_PCMU)) { sample = (ulaw2linear((unsigned char)r_rawvalue) + ulaw2linear((unsigned char)f_rawvalue)) / 2; phton16(pd, sample); } else if ((fwd_statinfo_.pt == PT_PCMA) && (rev_statinfo_.pt == PT_PCMA)) { sample = (alaw2linear((unsigned char)r_rawvalue) + alaw2linear((unsigned char)f_rawvalue)) / 2; phton16(pd, sample); } else { goto copy_file_err; } nchars = save_file.write((const char *)pd, 2); if (nchars < 2) { goto copy_file_err; } } } } } else if (save_format == save_audio_raw_) { /* raw format */ QFile *tempfile; int progress_pct; switch (direction) { /* Only forward direction */ case dir_forward_: { progress_pct = fwd_tempfile_->pos() * 100 / fwd_tempfile_->size(); tempfile = fwd_tempfile_; break; } /* only reversed direction */ case dir_reverse_: { progress_pct = rev_tempfile_->pos() * 100 / rev_tempfile_->size(); tempfile = rev_tempfile_; break; } default: { goto copy_file_err; } } int chunk_size = 65536; /* XXX how do you just copy the file? */ while (chunk_size > 0) { if (stop_flag) break; QByteArray bytes = tempfile->read(chunk_size); ui->progressFrame->setValue(progress_pct); if (!save_file.write(bytes)) { goto copy_file_err; } chunk_size = bytes.length(); } } copy_file_err: ui->progressFrame->hide(); updateWidgets(); return; } // XXX The GTK+ UI saves the length and timestamp. void RtpAnalysisDialog::saveCsv(RtpAnalysisDialog::StreamDirection direction) { QString caption; switch (direction) { case dir_forward_: caption = tr("Save forward stream CSV"); break; case dir_reverse_: caption = tr("Save reverse stream CSV"); break; case dir_both_: default: caption = tr("Save CSV"); break; } QString file_path = QFileDialog::getSaveFileName( this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"), tr("Comma-separated values (*.csv)")); if (file_path.isEmpty()) return; QFile save_file(file_path); save_file.open(QFile::WriteOnly); if (direction == dir_forward_ || direction == dir_both_) { save_file.write("Forward\n"); for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) { QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row); if (ti->type() != rtp_analysis_type_) continue; RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast((RtpAnalysisTreeWidgetItem *)ti); QStringList values; foreach (QVariant v, ra_ti->rowData()) { if (!v.isValid()) { values << "\"\""; } else if ((int) v.type() == (int) QMetaType::QString) { values << QString("\"%1\"").arg(v.toString()); } else { values << v.toString(); } } save_file.write(values.join(",").toUtf8()); save_file.write("\n"); } } if (direction == dir_both_) { save_file.write("\n"); } if (direction == dir_reverse_ || direction == dir_both_) { save_file.write("Reverse\n"); for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) { QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row); if (ti->type() != rtp_analysis_type_) continue; RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast((RtpAnalysisTreeWidgetItem *)ti); QStringList values; foreach (QVariant v, ra_ti->rowData()) { if (!v.isValid()) { values << "\"\""; } else if ((int) v.type() == (int) QMetaType::QString) { values << QString("\"%1\"").arg(v.toString()); } else { values << v.toString(); } } save_file.write(values.join(",").toUtf8()); save_file.write("\n"); } } } bool RtpAnalysisDialog::eventFilter(QObject *, QEvent *event) { if (event->type() != QEvent::KeyPress) return false; QKeyEvent *kevt = static_cast(event); switch(kevt->key()) { case Qt::Key_G: on_actionGoToPacket_triggered(); return true; case Qt::Key_N: on_actionNextProblem_triggered(); return true; default: break; } return false; } void RtpAnalysisDialog::graphClicked(QMouseEvent *event) { updateWidgets(); if (event->button() == Qt::RightButton) { graph_ctx_menu_.exec(event->globalPos()); } } void RtpAnalysisDialog::findStreams() { const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)"; dfilter_t *sfcode; gchar *err_msg; /* Try to get the hfid for "rtp.ssrc". */ int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc"); if (hfid_rtp_ssrc == -1) { err_str_ = tr("There is no \"rtp.ssrc\" field in this version of Wireshark."); updateWidgets(); return; } /* Try to compile the filter. */ if (!dfilter_compile(filter_text, &sfcode, &err_msg)) { err_str_ = QString(err_msg); g_free(err_msg); updateWidgets(); return; } if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close(); frame_data *fdata = cap_file_.capFile()->current_frame; if (!cf_read_record(cap_file_.capFile(), fdata)) close(); epan_dissect_t edt; epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE); epan_dissect_prime_dfilter(&edt, sfcode); epan_dissect_prime_hfid(&edt, hfid_rtp_ssrc); epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->phdr, frame_tvbuff_new_buffer(fdata, &cap_file_.capFile()->buf), fdata, NULL); /* * Packet must be an RTPv2 packet with an SSRC; we use the filter to * check. */ if (!dfilter_apply_edt(sfcode, &edt)) { epan_dissect_cleanup(&edt); dfilter_free(sfcode); err_str_ = tr("Please select an RTPv2 packet with an SSRC value"); updateWidgets(); return; } dfilter_free(sfcode); /* OK, it is an RTP frame. Let's get the IP and port values */ copy_address(&(src_fwd_), &(edt.pi.src)); copy_address(&(dst_fwd_), &(edt.pi.dst)); port_src_fwd_ = edt.pi.srcport; port_dst_fwd_ = edt.pi.destport; /* assume the inverse ip/port combination for the reverse direction */ copy_address(&(src_rev_), &(edt.pi.dst)); copy_address(&(dst_rev_), &(edt.pi.src)); port_src_rev_ = edt.pi.destport; port_dst_rev_ = edt.pi.srcport; /* now we need the SSRC value of the current frame */ GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc); if (gp == NULL || gp->len == 0) { /* XXX - should not happen, as the filter includes rtp.ssrc */ epan_dissect_cleanup(&edt); err_str_ = tr("SSRC value not found."); updateWidgets(); return; } ssrc_fwd_ = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value); /* Register the tap listener */ memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t)); tapinfo_.tap_data = this; tapinfo_.mode = TAP_ANALYSE; // register_tap_listener_rtp_stream(&tapinfo_, NULL); /* Scan for RTP streams (redissect all packets) */ rtpstream_scan(&tapinfo_, cap_file_.capFile(), NULL); for (GList *strinfo_list = g_list_first(tapinfo_.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) { rtp_stream_info_t * strinfo = (rtp_stream_info_t*)(strinfo_list->data); if (addresses_equal(&(strinfo->src_addr), &(src_fwd_)) && (strinfo->src_port == port_src_fwd_) && (addresses_equal(&(strinfo->dest_addr), &(dst_fwd_))) && (strinfo->dest_port == port_dst_fwd_)) { packet_count_fwd_ = strinfo->packet_count; setup_frame_number_fwd_ = strinfo->setup_frame_number; nstime_copy(&start_rel_time_fwd_, &strinfo->start_rel_time); num_streams_++; } if (addresses_equal(&(strinfo->src_addr), &(src_rev_)) && (strinfo->src_port == port_src_rev_) && (addresses_equal(&(strinfo->dest_addr), &(dst_rev_))) && (strinfo->dest_port == port_dst_rev_)) { packet_count_rev_ = strinfo->packet_count; setup_frame_number_rev_ = strinfo->setup_frame_number; nstime_copy(&start_rel_time_rev_, &strinfo->start_rel_time); num_streams_++; if (ssrc_rev_ == 0) { ssrc_rev_ = strinfo->ssrc; } } } } void RtpAnalysisDialog::showStreamMenu(QPoint pos) { QTreeWidget *cur_tree = qobject_cast(ui->tabWidget->currentWidget()); if (!cur_tree) return; updateWidgets(); stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos)); } /* * Editor modelines * * Local Variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */