diff options
Diffstat (limited to 'ui/qt')
-rw-r--r-- | ui/qt/CMakeLists.txt | 2 | ||||
-rw-r--r-- | ui/qt/Makefile.am | 2 | ||||
-rw-r--r-- | ui/qt/main_window.cpp | 12 | ||||
-rw-r--r-- | ui/qt/main_window.h | 5 | ||||
-rw-r--r-- | ui/qt/main_window.ui | 37 | ||||
-rw-r--r-- | ui/qt/main_window_slots.cpp | 20 | ||||
-rw-r--r-- | ui/qt/packet_list_model.cpp | 4 | ||||
-rw-r--r-- | ui/qt/packet_list_model.h | 2 | ||||
-rw-r--r-- | ui/qt/wireless_timeline.cpp | 643 | ||||
-rw-r--r-- | ui/qt/wireless_timeline.h | 122 |
10 files changed, 849 insertions, 0 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index ca3d67f530..9addc44d43 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -170,6 +170,7 @@ set(WIRESHARK_QT_HEADERS voip_calls_dialog.h voip_calls_info_model.h wireless_frame.h + wireless_timeline.h wireshark_application.h wireshark_dialog.h wlan_statistics_dialog.h @@ -344,6 +345,7 @@ set(WIRESHARK_QT_SRC voip_calls_dialog.cpp voip_calls_info_model.cpp wireless_frame.cpp + wireless_timeline.cpp wireshark_application.cpp wireshark_dialog.cpp ${WIRESHARK_CUSTOM_QT_SRCS} diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am index 7f52918fcf..ee9c82a6f1 100644 --- a/ui/qt/Makefile.am +++ b/ui/qt/Makefile.am @@ -302,6 +302,7 @@ MOC_HDRS = \ voip_calls_dialog.h \ voip_calls_info_model.h \ wireless_frame.h \ + wireless_timeline.h \ wireshark_application.h \ wireshark_dialog.h \ wlan_statistics_dialog.h @@ -589,6 +590,7 @@ WIRESHARK_QT_SRC = \ voip_calls_dialog.cpp \ voip_calls_info_model.cpp \ wireless_frame.cpp \ + wireless_timeline.cpp \ wireshark_application.cpp \ wireshark_dialog.cpp diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp index d9036cb3e9..543dd1c9da 100644 --- a/ui/qt/main_window.cpp +++ b/ui/qt/main_window.cpp @@ -71,6 +71,7 @@ DIAG_ON(frame-larger-than=) #include "import_text_dialog.h" #include "interface_toolbar.h" #include "packet_list.h" +#include "wireless_timeline.h" #include "proto_tree.h" #include "simple_dialog.h" #include "stock_icon.h" @@ -338,6 +339,7 @@ MainWindow::MainWindow(QWidget *parent) : cur_layout_(QVector<unsigned>()), df_combo_box_(NULL), packet_list_(NULL), + wireless_timeline_(NULL), proto_tree_(NULL), previous_focus_(NULL), file_set_dialog_(NULL), @@ -526,6 +528,7 @@ MainWindow::MainWindow(QWidget *parent) : empty_pane_.setObjectName("emptyPane"); packet_list_ = new PacketList(&master_split_); + wireless_timeline_ = new WirelessTimeline(&master_split_, packet_list_); proto_tree_ = new ProtoTree(&master_split_); proto_tree_->installEventFilter(this); @@ -1985,6 +1988,10 @@ void MainWindow::initMainToolbarIcons() main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out")); main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original")); main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns")); + + main_ui_->actionWirelessTimelineZoomIn->setIcon(StockIcon("zoom-in")); + main_ui_->actionWirelessTimelineZoomOut->setIcon(StockIcon("zoom-out")); + main_ui_->actionWirelessTimelineZoomFullOut->setIcon(StockIcon("zoom-original")); } void MainWindow::initShowHideMainWidgets() @@ -2415,6 +2422,11 @@ void MainWindow::setForCapturedPackets(bool have_captured_packets) main_ui_->actionViewNormalSize->setEnabled(have_captured_packets); main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets); + bool wireless_timeline_visible = (wireless_timeline_ ? !wireless_timeline_->isHidden() : FALSE); + main_ui_->actionWirelessTimelineZoomIn->setEnabled(wireless_timeline_visible); + main_ui_->actionWirelessTimelineZoomOut->setEnabled(wireless_timeline_visible); + main_ui_->actionWirelessTimelineZoomFullOut->setEnabled(wireless_timeline_visible); + main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets); main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets); main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets); diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index b6c41ea1c1..18df63840f 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -66,6 +66,7 @@ class FileSetDialog; class FunnelStatistics; class MainWelcome; class PacketList; +class WirelessTimeline; class ProtoTree; class WirelessFrame; @@ -152,6 +153,7 @@ private: // XXX - packet_list_, proto_tree_, and byte_view_tab_ should // probably be full-on values instead of pointers. PacketList *packet_list_; + WirelessTimeline *wireless_timeline_; ProtoTree *proto_tree_; QWidget *previous_focus_; FileSetDialog *file_set_dialog_; @@ -456,6 +458,9 @@ private slots: void on_actionViewZoomIn_triggered(); void on_actionViewZoomOut_triggered(); void on_actionViewNormalSize_triggered(); + void on_actionWirelessTimelineZoomIn_triggered(); + void on_actionWirelessTimelineZoomOut_triggered(); + void on_actionWirelessTimelineZoomFullOut_triggered(); void on_actionViewColorizePacketList_triggered(bool checked); void on_actionViewColoringRules_triggered(); void colorizeConversation(bool create_rule = false); diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui index e38896a5e9..7c359249c5 100644 --- a/ui/qt/main_window.ui +++ b/ui/qt/main_window.ui @@ -738,6 +738,10 @@ <addaction name="actionViewZoomOut"/> <addaction name="actionViewNormalSize"/> <addaction name="actionViewResizeColumns"/> + <addaction name="separator"/> + <addaction name="actionWirelessTimelineZoomIn"/> + <addaction name="actionWirelessTimelineZoomOut"/> + <addaction name="actionWirelessTimelineZoomFullOut"/> </widget> <widget class="MainStatusBar" name="statusBar"/> <widget class="QToolBar" name="displayFilterToolBar"> @@ -2982,6 +2986,39 @@ <string>&Full Screen</string> </property> </action> + <action name="actionWirelessTimelineZoomIn"> + <property name="text"> + <string>&Zoom In</string> + </property> + <property name="toolTip"> + <string>Zoom in on the wireless timeline</string> + </property> + <property name="shortcut"> + <string>Ctrl+I</string> + </property> + </action> + <action name="actionWirelessTimelineZoomOut"> + <property name="text"> + <string>Zoom Out</string> + </property> + <property name="toolTip"> + <string>Zoom out on the wireless timeline</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionWirelessTimelineZoomFullOut"> + <property name="text"> + <string>Full capture</string> + </property> + <property name="toolTip"> + <string>Fully zoom out on the wireless timeline</string> + </property> + <property name="shortcut"> + <string>Ctrl+U</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index 82f9cc7543..aafee8bcca 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -165,6 +165,7 @@ DIAG_ON(frame-larger-than=) #include "voip_calls_dialog.h" #include "wireshark_application.h" #include "wlan_statistics_dialog.h" +#include "wireless_timeline.h" #include <QClipboard> #include <QFileInfo> @@ -786,6 +787,7 @@ void MainWindow::captureFileReadStarted(const QString &action) { main_ui_->statusBar->pushFileStatus(msg, msgtip); main_ui_->mainStack->setCurrentWidget(&master_split_); main_ui_->actionAnalyzeReloadLuaPlugins->setEnabled(false); + wireless_timeline_->captureFileReadStarted(capture_file_.capFile()); WiresharkApplication::processEvents(); } @@ -806,6 +808,9 @@ void MainWindow::captureFileReadFinished() { /* Update the appropriate parts of the main window. */ updateForUnsavedChanges(); + /* enable wireless timeline if capture allows it */ + wireless_timeline_->captureFileReadFinished(); + /* Enable menu items that make sense if you have some captured packets. */ setForCapturedPackets(true); @@ -2462,6 +2467,21 @@ void MainWindow::on_actionViewNormalSize_triggered() zoomText(); } +void MainWindow::on_actionWirelessTimelineZoomIn_triggered() +{ + wireless_timeline_->zoomIn(); +} + +void MainWindow::on_actionWirelessTimelineZoomOut_triggered() +{ + wireless_timeline_->zoomOut(); +} + +void MainWindow::on_actionWirelessTimelineZoomFullOut_triggered() +{ + wireless_timeline_->zoomFullOut(); +} + void MainWindow::on_actionViewColorizePacketList_triggered(bool checked) { recent.packet_list_colorize = checked; packet_list_enable_color(checked); diff --git a/ui/qt/packet_list_model.cpp b/ui/qt/packet_list_model.cpp index 4c8012ea91..a46b6f568d 100644 --- a/ui/qt/packet_list_model.cpp +++ b/ui/qt/packet_list_model.cpp @@ -615,6 +615,7 @@ void PacketListModel::dissectIdle(bool reset) idle_dissection_timer_->restart(); + int first = idle_dissection_row_; while (idle_dissection_timer_->elapsed() < idle_dissection_interval_ && idle_dissection_row_ < physical_rows_.count()) { ensureRowColorized(idle_dissection_row_); @@ -627,6 +628,9 @@ void PacketListModel::dissectIdle(bool reset) } else { idle_dissection_timer_->invalidate(); } + + // report colorization progress + bgColorizationProgress(first+1, idle_dissection_row_+1); } // XXX Pass in cinfo from packet_list_append so that we can fill in diff --git a/ui/qt/packet_list_model.h b/ui/qt/packet_list_model.h index 40360f5552..3af6c60042 100644 --- a/ui/qt/packet_list_model.h +++ b/ui/qt/packet_list_model.h @@ -87,6 +87,8 @@ signals: void updateProgressStatus(int value); void popProgressStatus(); + void bgColorizationProgress(int first, int last); + public slots: void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); void flushVisibleRows(); diff --git a/ui/qt/wireless_timeline.cpp b/ui/qt/wireless_timeline.cpp new file mode 100644 index 0000000000..7fb583528c --- /dev/null +++ b/ui/qt/wireless_timeline.cpp @@ -0,0 +1,643 @@ +/* wireless_timeline.cpp + * GUI to show an 802.11 wireless timeline of packets + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * Copyright 2012 Parc Inc and Samsung Electronics + * Copyright 2015, 2016 & 2017 Cisco Inc + * + * 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 "wireless_timeline.h" + +#include <epan/packet.h> +#include <epan/prefs.h> +#include <epan/proto_data.h> +#include <epan/packet_info.h> +#include <epan/column-utils.h> +#include <epan/tap.h> + +#include <cmath> + +#include "globals.h" +#include "color_utils.h" +#include "../../log.h" +#include <epan/dissectors/packet-ieee80211-radio.h> + +#include <epan/color_filters.h> +#include "frame_tvbuff.h" + +#include "color_utils.h" +#include "qt_ui_utils.h" +#include "wireshark_application.h" + +#ifdef Q_OS_WIN +#include "wsutil/file_util.h" +#include <QSysInfo> +#endif + +#include <QPaintEvent> +#include <QPainter> +#include <QGraphicsScene> +#include <QToolTip> + +#include "packet_list.h" +#include "packet_list_model.h" + +#include "ui/main_statusbar.h" + +const float fraction = 0.8F; +const float base = 0.1F; + +class pcolor : public QColor +{ +public: + inline pcolor(float red, float green, float blue) : QColor( + (int) (255*(red * fraction + base)), + (int) (255*(green * fraction + base)), + (int) (255*(blue * fraction + base)) ) { } +}; + +static void reset_rgb(float rgb[TIMELINE_HEIGHT][3]) +{ + int i; + for(i = 0; i < TIMELINE_HEIGHT; i++) + rgb[i][0] = rgb[i][1] = rgb[i][2] = 1.0; +} + +static void render_pixels(QPainter &p, gint x, gint width, float rgb[TIMELINE_HEIGHT][3], float ratio) +{ + int previous = 0, i; + for(i = 1; i <= TIMELINE_HEIGHT; i++) { + if (i != TIMELINE_HEIGHT && + rgb[previous][0] == rgb[i][0] && + rgb[previous][1] == rgb[i][1] && + rgb[previous][2] == rgb[i][2]) + continue; + if (rgb[previous][0] != 1.0 || rgb[previous][1] != 1.0 || rgb[previous][2] != 1.0) { + p.fillRect(QRectF(x/ratio, previous, width/ratio, i-previous), pcolor(rgb[previous][0],rgb[previous][1],rgb[previous][2])); + } + previous = i; + } + reset_rgb(rgb); +} + +static void render_rectangle(QPainter &p, gint x, gint width, guint height, int dfilter, float r, float g, float b, float ratio) +{ + p.fillRect(QRectF(x/ratio, TIMELINE_HEIGHT/2-height, width/ratio, dfilter ? height * 2 : height), pcolor(r,g,b)); +} + +static void accumulate_rgb(float rgb[TIMELINE_HEIGHT][3], int height, int dfilter, float width, float red, float green, float blue) +{ + int i; + for(i = TIMELINE_HEIGHT/2-height; i < (TIMELINE_HEIGHT/2 + (dfilter ? height : 0)); i++) { + rgb[i][0] = rgb[i][0] - width + width * red; + rgb[i][1] = rgb[i][1] - width + width * green; + rgb[i][2] = rgb[i][2] - width + width * blue; + } +} + + +void WirelessTimeline::mousePressEvent(QMouseEvent *event) +{ + start_x = last_x = event->localPos().x(); +} + + +void WirelessTimeline::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() == Qt::NoButton) + return; + + qreal offset = event->localPos().x() - last_x; + last_x = event->localPos().x(); + + qreal shift = ((qreal) (end_tsf - start_tsf))/width() * offset; + start_tsf -= shift; + end_tsf -= shift; + clip_tsf(); + + // TODO: scroll by moving pixels and redraw only exposed area + // render(p, ...) + // then update full widget only on release. + update(); +} + + +void WirelessTimeline::mouseReleaseEvent(QMouseEvent *event) +{ + qreal offset = event->localPos().x() - start_x; + + /* if this was a drag, ignore it */ + if (std::abs(offset) > 3) + return; + + /* this was a click */ + guint num = find_packet(event->localPos().x()); + if (num == 0) + return; + + frame_data *fdata = frame_data_sequence_find(cfile.frames, num); + if (!fdata->flags.passed_dfilter && fdata->prev_dis_num > 0) + num = fdata->prev_dis_num; + + cf_goto_frame(&cfile, num); +} + + +void WirelessTimeline::clip_tsf() +{ + // did we go past the start of the file? + if (start_tsf < first->start_tsf) { + // align the start of the file at the left edge + guint64 shift = first->start_tsf - start_tsf; + start_tsf += shift; + end_tsf += shift; + } + if (end_tsf > last->end_tsf) { + guint64 shift = end_tsf - last->end_tsf; + start_tsf -= shift; + end_tsf -= shift; + } +} + + +void WirelessTimeline::packetSelectionChanged() +{ + if (isHidden()) + return; + + if (cfile.current_frame) { + struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num); + + guint left_margin = 0.9 * start_tsf + 0.1 * end_tsf; + guint right_margin = 0.1 * start_tsf + 0.9 * end_tsf; + guint half_window = (end_tsf - start_tsf)/2; + + if (wr) { + // are we to the left of the left margin? + if (wr->start_tsf < left_margin) { + // scroll the left edge back to the left margin + guint64 offset = left_margin - wr->start_tsf; + if (offset < half_window) { + // small movement; keep packet to margin + start_tsf -= offset; + end_tsf -= offset; + } else { + // large movement; move packet to center of window + guint64 center = (wr->start_tsf + wr->end_tsf)/2; + start_tsf = center - half_window; + end_tsf = center + half_window; + } + } else if (wr->end_tsf > right_margin) { + guint64 offset = wr->end_tsf - right_margin; + if (offset < half_window) { + start_tsf += offset; + end_tsf += offset; + } else { + guint64 center = (wr->start_tsf + wr->end_tsf)/2; + start_tsf = center - half_window; + end_tsf = center + half_window; + } + } + clip_tsf(); + + update(); + } + } +} + + +/* given an x position find which packet that corresponds to. + * if it's inter frame space the subsequent packet is returned */ +guint +WirelessTimeline::find_packet(qreal x_position) +{ + guint64 x_time = start_tsf + (x_position/width() * (end_tsf - start_tsf)); + + return find_packet_tsf(x_time); +} + +void WirelessTimeline::captureFileReadStarted(capture_file *cf) +{ + capfile = cf; + hide(); + // TODO: hide or grey the toolbar controls +} + +void WirelessTimeline::captureFileReadFinished() +{ + /* All frames must be included in packet list */ + if (cfile.count == 0 || g_hash_table_size(radio_packet_list) != cfile.count) + return; + + /* check that all frames have start and end tsf time and are reasonable time order. + * packet timing reference seems to be off a little on some generators, which + * causes frequent IFS values in the range 0 to -30. Some generators emit excessive + * data when an FCS error happens, and this results in the duration calculation for + * the error frame being excessively long. This can cause larger negative IFS values + * (-30 to -1000) for the subsequent frame. Ignore these cases, as they don't seem + * to impact the GUI too badly. If the TSF reference point is set wrong (TSF at + * start of frame when it is at the end) then larger negative offsets are often + * seen. Don't display the timeline in these cases. + */ + /* TODO: update GUI to handle captures with occasional frames missing TSF data */ + /* TODO: indicate error message to the user */ + for (guint32 n = 1; n < cfile.count; n++) { + struct wlan_radio *w = get_wlan_radio(n); + if (w->start_tsf == 0 || w->end_tsf == 0) { + statusbar_push_temporary_msg("Packet number %u does not include TSF timestamp, not showing timeline.", n); + return; + } + if (w->ifs < -15000) { + statusbar_push_temporary_msg("Packet number %u has large negative jump in TSF, not showing timeline. Perhaps TSF reference point is set wrong?", n); + return; + } + } + + first = get_wlan_radio(1); + last = get_wlan_radio(cfile.count); + + start_tsf = first->start_tsf; + end_tsf = last->end_tsf; + + /* TODO: only reset the zoom level if the file is changed, not on redissection */ + zoom_level = 0; + + show(); + packetSelectionChanged(); + // TODO: show or ungrey the toolbar controls + update(); +} + +void WirelessTimeline::appInitialized() +{ + register_tap_listener("wlan_radio_timeline", this, NULL, TL_REQUIRES_NOTHING, tap_timeline_reset, tap_timeline_packet, NULL/*tap_draw_cb tap_draw*/); +} + +void WirelessTimeline::resizeEvent(QResizeEvent*) +{ + // TODO adjust scrollbar +} + + +// Calculate the x position on the GUI from the timestamp +int WirelessTimeline::position(guint64 tsf, float ratio) +{ + int position = -100; + + if (tsf != 0xffffffffffffffff) { + position = ((double) tsf - start_tsf)*width()*ratio/(end_tsf-start_tsf); + } + return position; +} + + +WirelessTimeline::WirelessTimeline(QWidget *parent, PacketList *packet_list) : QWidget(parent) +{ + setHidden(true); + this->packet_list = packet_list; + connect(packet_list->packetListModel(), SIGNAL(bgColorizationProgress(int,int)), + this, SLOT(bgColorizationProgress(int,int))); + connect(packet_list, SIGNAL(packetSelectionChanged()), + this, SLOT(packetSelectionChanged())); + connect(wsApp, SIGNAL(appInitialized()), + this, SLOT(appInitialized())); + zoom_level = 1.0; + setFixedHeight(TIMELINE_HEIGHT); + first_packet = 1; + setMouseTracking(true); + + radio_packet_list = NULL; +} + +void WirelessTimeline::tap_timeline_reset(void* tapdata) +{ + WirelessTimeline* timeline = (WirelessTimeline*)tapdata; + + if (timeline->radio_packet_list != NULL) + { + g_hash_table_destroy(timeline->radio_packet_list); + } + timeline->hide(); + + timeline->radio_packet_list = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +gboolean WirelessTimeline::tap_timeline_packet(void *tapdata, packet_info* pinfo, epan_dissect_t* edt _U_, const void *data) +{ + WirelessTimeline* timeline = (WirelessTimeline*)tapdata; + struct wlan_radio *wlan_radio_info = (struct wlan_radio *)data; + + /* Save the radio information in our own (GUI) hashtable */ + g_hash_table_insert(timeline->radio_packet_list, GUINT_TO_POINTER(pinfo->num), wlan_radio_info); + return FALSE; +} + +struct wlan_radio* WirelessTimeline::get_wlan_radio(guint32 packet_num) +{ + return (struct wlan_radio*)g_hash_table_lookup(radio_packet_list, GUINT_TO_POINTER(packet_num)); +} + +void WirelessTimeline::doToolTip(struct wlan_radio *wr, QPoint pos, int x) +{ + if (x < position(wr->start_tsf, 1.0)) { + QToolTip::showText(pos, QString("inter frame space %1 us").arg(wr->ifs)); + } else { + QToolTip::showText(pos, QString("total duration %1 us\nNAV %2 us").arg(wr->end_tsf-wr->start_tsf) + .arg(wr->nav)); + } +} + + +bool WirelessTimeline::event(QEvent *event) +{ + if (event->type() == QEvent::ToolTip) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + guint packet = find_packet(helpEvent->pos().x()); + if (packet) { + doToolTip(get_wlan_radio(packet), helpEvent->globalPos(), helpEvent->x()); + } else { + QToolTip::hideText(); + event->ignore(); + } + return true; + } + return QWidget::event(event); +} + + +void WirelessTimeline::bgColorizationProgress(int first, int last) +{ + if (isHidden()) return; + + struct wlan_radio *first_wr = get_wlan_radio(first); + + struct wlan_radio *last_wr = get_wlan_radio(last-1); + + int x = position(first_wr->start_tsf, 1); + int x_end = position(last_wr->end_tsf, 1); + + update(x, 0, x_end-x+1, height()); +} + + +guint64 WirelessTimeline::current_frame_center() +{ + if (cfile.current_frame == NULL) + return 0; + + struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num); + return (wr->start_tsf + wr->end_tsf) /2; +} + + +void WirelessTimeline::zoom() +{ + guint64 center = current_frame_center(); + int x_position = position(center, 1.0); + + /* adjust the zoom around the selected packet */ + float fraction = ((float) x_position)/width(); + guint64 file_range = last->end_tsf - first->start_tsf; + guint64 span = pow(file_range, 1.0-zoom_level/25.0); + start_tsf = center - span*fraction; + end_tsf = center + span*(1.0-fraction); + + /* if we go out of range for the whole file, clamp it */ + if (start_tsf < first->start_tsf) { + end_tsf += first->start_tsf - start_tsf; + start_tsf = first->start_tsf; + } else if (end_tsf > last->end_tsf) { + start_tsf -= end_tsf - last->end_tsf; + end_tsf = last->end_tsf; + } + + update(); +} + +void WirelessTimeline::zoomIn() +{ + zoom_level++; + zoom(); +} + +void WirelessTimeline::zoomOut() +{ + zoom_level--; + if (zoom_level < 0) zoom_level = 0; + zoom(); +} + +void WirelessTimeline::zoomFullOut() +{ + zoom_level = 0; + zoom(); +} + +int WirelessTimeline::find_packet_tsf(guint64 tsf) +{ + if (cfile.count < 1) + return 0; + + if (cfile.count < 2) + return 1; + + guint32 min_count = 1; + guint32 max_count = cfile.count-1; + + guint64 min_tsf = get_wlan_radio(min_count)->end_tsf; + guint64 max_tsf = get_wlan_radio(max_count)->end_tsf; + + for(;;) { + if (tsf >= max_tsf) + return max_count+1; + + if (tsf < min_tsf) + return min_count; + + guint32 middle = (min_count + max_count)/2; + if (middle == min_count) + return middle+1; + + guint64 middle_tsf = get_wlan_radio(middle)->end_tsf; + + if (tsf >= middle_tsf) { + min_count = middle; + min_tsf = middle_tsf; + } else { + max_count = middle; + max_tsf = middle_tsf; + } + }; +} + +void +WirelessTimeline::paintEvent(QPaintEvent *qpe) +{ + QPainter p(this); + + // painting is done in device pixels in the x axis, get the ratio here + float ratio = p.device()->devicePixelRatio(); + + unsigned int packet; + double zoom; + int last_x=-1; + int left = qpe->rect().left()*ratio; + int right = qpe->rect().right()*ratio; + float rgb[TIMELINE_HEIGHT][3]; + reset_rgb(rgb); + + zoom = ((double) width())/(end_tsf - start_tsf) * ratio; + + /* background is light grey */ + p.fillRect(0, 0, width(), TIMELINE_HEIGHT, QColor(240,240,240)); + + /* background of packets visible in packet_list is white */ + int top = packet_list->indexAt(QPoint(0,0)).row(); + int bottom = packet_list->indexAt(QPoint(0,packet_list->viewport()->height())).row(); + PacketListModel *model = packet_list->packetListModel(); + int x1 = top == -1 ? 0 : position(get_wlan_radio(model->getRowFdata(top)->num)->start_tsf, ratio); + int x2 = bottom == -1 ? width() : position(get_wlan_radio(model->getRowFdata(bottom)->num)->end_tsf, ratio); + p.fillRect(QRectF(x1/ratio, 0, (x2-x1+1)/ratio, TIMELINE_HEIGHT), Qt::white); + + /* background of current packet is blue */ + if (cfile.current_frame) { + struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num); + if (wr) { + x1 = position(wr->start_tsf, ratio); + x2 = position(wr->end_tsf, ratio); + p.fillRect(QRectF(x1/ratio, 0, (x2-x1+1)/ratio, TIMELINE_HEIGHT), Qt::blue); + } + } + + QGraphicsScene qs; + for(packet = find_packet_tsf(start_tsf + left/zoom - 40000); packet <= cfile.count; packet++) { + frame_data *fdata = frame_data_sequence_find(cfile.frames, packet); + struct wlan_radio *ri = get_wlan_radio(fdata->num); + float x, width, red,green,blue; + gint8 rssi = ri->aggregate ? ri->aggregate->rssi : ri->rssi; + guint height = (rssi+100)/2; + gint end_nav; + + if (ri == NULL) continue; + + /* leave a margin above the packets so the selected packet can be seen */ + if (height > TIMELINE_HEIGHT/2-6) + height = TIMELINE_HEIGHT/2-6; + + /* ensure shortest packets are clearly visible */ + if (height < 2) + height = 2; + + /* skip frames we don't have start and end data for */ + /* TODO: show something, so it's clear a frame is missing */ + if (ri->start_tsf == 0 || ri->end_tsf == 0) + continue; + + x = ((gint64) (ri->start_tsf - start_tsf))*zoom; + /* is there a previous anti-aliased pixel to output */ + if (last_x >= 0 && ((int) x) != last_x) { + /* write it out now */ + render_pixels(p, last_x, 1, rgb, ratio); + last_x = -1; + } + + /* does this packet start past the right edge of the window? */ + if (x >= right) { + break; + } + + width = (ri->end_tsf - ri->start_tsf)*zoom; + if (width < 0) { + continue; + } + + /* is this packet completely to the left of the displayed area? */ + // TODO clip NAV line properly if we are displaying it + if ((x + width) < left) + continue; + + /* remember the first displayed packet */ + if (first_packet < 0) + first_packet = packet; + + if (fdata->color_filter) { + const color_t *c = &((color_filter_t *) fdata->color_filter)->fg_color; + red = c->red / 65535.0; + green = c->green / 65535.0; + blue = c->blue / 65535.0; + } else { + red = green = blue = 0.0; + } + + /* record NAV field at higher magnifications */ + end_nav = x + width + ri->nav*zoom; + if (zoom >= 0.01 && ri->nav && end_nav > 0) { + gint y = 2*(packet % (TIMELINE_HEIGHT/2)); + qs.addLine(QLineF((x+width)/ratio, y, end_nav/ratio, y), QPen(pcolor(red,green,blue))); + } + + /* does this rectangle fit within one pixel? */ + if (((int) x) == ((int) (x+width))) { + /* accumulate it for later rendering together + * with all other sub pixels that fall within this + * pixel */ + last_x = x; + accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, width, red, green, blue); + } else { + /* it spans more than 1 pixel. + * first accumulate the part that does fit */ + float partial = ((int) x) + 1 - x; + accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, partial, red, green, blue); + /* and render it */ + render_pixels(p, (int) x, 1, rgb, ratio); + last_x = -1; + x += partial; + width -= partial; + /* are there any whole pixels of width left to draw? */ + if (width > 1.0) { + render_rectangle(p, x, width, height, fdata->flags.passed_dfilter, red, green, blue, ratio); + x += (int) width; + width -= (int) width; + } + /* is there a partial pixel left */ + if (width > 0.0) { + last_x = x; + accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, width, red, green, blue); + } + } + } + + // draw the NAV lines last, so they appear on top of the packets + qs.render(&p, rect(), rect()); +} + + +/* + * 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: + */ diff --git a/ui/qt/wireless_timeline.h b/ui/qt/wireless_timeline.h new file mode 100644 index 0000000000..628524e4b4 --- /dev/null +++ b/ui/qt/wireless_timeline.h @@ -0,0 +1,122 @@ +/* wireless_timeline.h + * GUI to show an 802.11 wireless timeline of packets + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * Copyright 2012 Parc Inc and Samsung Electronics + * Copyright 2015, 2016 & 2017 Cisco Inc + * + * 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 <QScrollArea> + +#ifndef WIRELESSTIMELINE_H +#define WIRELESSTIMELINE_H + +#include <stdio.h> + +#include <config.h> + +#include <glib.h> + +#include "file.h" + +#include "ui/ui_util.h" + +#include <epan/prefs.h> +#include <epan/plugin_if.h> +#include <epan/timestamp.h> + +#include <epan/dissectors/packet-ieee80211-radio.h> + +#include <QScrollArea> + +#include "cfile.h" + +/* pixels height for rendered timeline */ +#define TIMELINE_HEIGHT 64 + +class WirelessTimeline; +class PacketList; + +class WirelessTimeline : public QWidget +{ + Q_OBJECT + +public: + explicit WirelessTimeline(QWidget *parent, PacketList *packet_list); + void captureFileReadStarted(capture_file *cf); + void captureFileReadFinished(); + void zoomIn(); + void zoomOut(); + void zoomFullOut(); + +protected: + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); + void mousePressEvent (QMouseEvent *event); + void mouseMoveEvent (QMouseEvent *event); + void mouseReleaseEvent (QMouseEvent *event); + bool event(QEvent *event); + +public slots: + void bgColorizationProgress(int first, int last); + void packetSelectionChanged(); + void appInitialized(); + +protected: + static void tap_timeline_reset(void* tapdata); + static gboolean tap_timeline_packet(void *tapdata, packet_info* pinfo, epan_dissect_t* edt, const void *data); + + struct wlan_radio* get_wlan_radio(guint32 packet_num); + + void clip_tsf(); + guint64 current_frame_center(); + int position(guint64 tsf, float ratio); + int find_packet_tsf(guint64 tsf); + void doToolTip(struct wlan_radio *wr, QPoint pos, int x); + void zoom(); + int zoom_level; + qreal start_x, last_x; + PacketList *packet_list; + guint find_packet(qreal x); + float rgb[TIMELINE_HEIGHT][3]; + + guint64 start_tsf; + guint64 end_tsf; + int first_packet; /* first packet displayed */ + struct wlan_radio *first, *last; + capture_file *capfile; + + GHashTable* radio_packet_list; +}; + +#endif // WIRELESS_TIMELINE_H + +/* + * 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: + */ |