summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--epan/dissectors/Makefile.am1
-rw-r--r--epan/dissectors/packet-ieee80211-radio.c47
-rw-r--r--epan/dissectors/packet-ieee80211-radio.h57
-rw-r--r--ui/qt/CMakeLists.txt2
-rw-r--r--ui/qt/Makefile.am2
-rw-r--r--ui/qt/main_window.cpp12
-rw-r--r--ui/qt/main_window.h5
-rw-r--r--ui/qt/main_window.ui37
-rw-r--r--ui/qt/main_window_slots.cpp20
-rw-r--r--ui/qt/packet_list_model.cpp4
-rw-r--r--ui/qt/packet_list_model.h2
-rw-r--r--ui/qt/wireless_timeline.cpp643
-rw-r--r--ui/qt/wireless_timeline.h122
13 files changed, 928 insertions, 26 deletions
diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am
index f2bb82a1d4..b5bc8ab8a6 100644
--- a/epan/dissectors/Makefile.am
+++ b/epan/dissectors/Makefile.am
@@ -1599,6 +1599,7 @@ DISSECTOR_INCLUDES = \
packet-idmp.h \
packet-idp.h \
packet-ieee80211.h \
+ packet-ieee80211-radio.h \
packet-ieee80211-radiotap-iter.h \
packet-ieee80211-radiotap-defs.h \
packet-ieee802154.h \
diff --git a/epan/dissectors/packet-ieee80211-radio.c b/epan/dissectors/packet-ieee80211-radio.c
index ca91f5abaa..dadabec481 100644
--- a/epan/dissectors/packet-ieee80211-radio.c
+++ b/epan/dissectors/packet-ieee80211-radio.c
@@ -32,8 +32,10 @@
#include <wiretap/wtap.h>
#include <epan/prefs.h>
#include <epan/proto_data.h>
+#include <epan/tap.h>
#include "packet-ieee80211.h"
+#include "packet-ieee80211-radio.h"
#include "math.h"
void proto_register_ieee80211_radio(void);
@@ -104,6 +106,8 @@ static expert_field ei_wlan_radio_assumed_no_stbc = EI_INIT;
static expert_field ei_wlan_radio_assumed_no_extension_streams = EI_INIT;
static expert_field ei_wlan_radio_assumed_bcc_fec = EI_INIT;
+static int wlan_radio_timeline_tap = -1;
+
/* Settings */
static gboolean wlan_radio_always_short_preamble = FALSE;
static gboolean wlan_radio_tsf_at_end = TRUE;
@@ -382,26 +386,6 @@ static gint ett_wlan_radio_11ac_user = -1;
static gint ett_wlan_radio_duration = -1;
static gint ett_wlan_radio_aggregate = -1;
-struct aggregate {
- guint phy;
- union ieee_802_11_phy_info phy_info;
- gint8 rssi; /* sometimes only available on the last frame */
- guint duration; /* total duration of data in microseconds (without preamble) */
-};
-
-struct wlan_radio {
- struct aggregate *aggregate; /* if this frame is part of an aggregate, point to it, otherwise NULL */
- guint prior_aggregate_data; /* length of all prior data in this aggregate
- used for calculating duration of this subframe */
- guint64 start_tsf;
- guint64 end_tsf;
-
- guint64 ifs; /* inter frame space in microseconds */
-
- guint16 nav;
- gint8 rssi;
-};
-
/* previous frame details, for aggregate detection */
struct previous_frame_info {
gboolean has_tsf_timestamp;
@@ -457,6 +441,8 @@ static void adjust_agg_tsf(gpointer data, gpointer user_data)
wlan_radio_info->start_tsf += (*ppdu_start);
wlan_radio_info->end_tsf += (*ppdu_start);
+ if (wlan_radio_info->prior_aggregate_data == 0)
+ wlan_radio_info->ifs += (*ppdu_start);
}
/*
@@ -1091,22 +1077,23 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree,
}
if (phdr->tsf_timestamp == 0xFFFFFFFFFFFFFFFF) {
/* QCA aggregate, we don't know tsf yet */
- wlan_radio_info->start_tsf = prior_duration;
- wlan_radio_info->end_tsf = prior_duration + duration;
+ wlan_radio_info->start_tsf = prior_duration + (current_aggregate ? agg_preamble : 0);
+ wlan_radio_info->end_tsf = prior_duration + duration + (current_aggregate ? agg_preamble : 0);
if (agg_tracker_list == NULL) {
agg_tracker_list = wmem_list_new(NULL);
}
wmem_list_append(agg_tracker_list, wlan_radio_info);
} else if (current_aggregate && wlan_radio_tsf_at_end && phdr->tsf_timestamp != 0xFFFFFFFFFFFFFFFF) {
/* QCA aggregate, last frame */
- guint64 ppdu_start = phdr->tsf_timestamp - current_aggregate->duration;
wlan_radio_info->start_tsf = phdr->tsf_timestamp - duration;
wlan_radio_info->end_tsf = phdr->tsf_timestamp;
/* fix up the tsfs for the prior MPDUs */
if (agg_tracker_list != NULL) {
+ guint64 ppdu_start = phdr->tsf_timestamp - (prior_duration + duration + agg_preamble);
wmem_list_foreach(agg_tracker_list, adjust_agg_tsf, &ppdu_start);
wmem_destroy_list(agg_tracker_list);
- }
+ agg_tracker_list = NULL;
+ };
} else if (wlan_radio_tsf_at_end) {
wlan_radio_info->start_tsf = phdr->tsf_timestamp - duration - preamble;
wlan_radio_info->end_tsf = phdr->tsf_timestamp;
@@ -1164,7 +1151,7 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree,
}
}
if (wlan_radio_info->ifs) {
- p_item = proto_tree_add_uint64(d_tree, hf_wlan_radio_ifs, tvb, 0, 0, wlan_radio_info->ifs);
+ p_item = proto_tree_add_int64(d_tree, hf_wlan_radio_ifs, tvb, 0, 0, wlan_radio_info->ifs);
PROTO_ITEM_SET_GENERATED(p_item);
/* TODO: warnings on unusual IFS values (too small or negative) */
}
@@ -1178,6 +1165,12 @@ dissect_wlan_radio_phdr (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree,
}
}
} /* if (have_data_rate) */
+
+ tap_queue_packet(wlan_radio_timeline_tap, pinfo, wlan_radio_info);
+
+ if (!pinfo->fd->flags.visited) {
+ previous_frame.radio_info = wlan_radio_info;
+ }
}
/*
@@ -1401,7 +1394,7 @@ void proto_register_ieee80211_radio(void)
"MPDU is part of an A-MPDU", HFILL }},
{&hf_wlan_radio_ifs,
- {"IFS", "wlan_radio.ifs", FT_UINT64, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0,
+ {"IFS", "wlan_radio.ifs", FT_INT64, BASE_DEC|BASE_UNIT_STRING, &units_microseconds, 0,
"Inter Frame Space before this frame in microseconds, calculated from PHY data", HFILL }},
{&hf_wlan_radio_start_tsf,
@@ -1483,6 +1476,8 @@ void proto_reg_handoff_ieee80211_radio(void)
wlan_radio_handle);
ieee80211_handle = find_dissector_add_dependency("wlan", proto_wlan_radio);
ieee80211_noqos_handle = find_dissector_add_dependency("wlan_noqos", proto_wlan_radio);
+
+ wlan_radio_timeline_tap = register_tap("wlan_radio_timeline");
}
/*
diff --git a/epan/dissectors/packet-ieee80211-radio.h b/epan/dissectors/packet-ieee80211-radio.h
new file mode 100644
index 0000000000..83c0e23147
--- /dev/null
+++ b/epan/dissectors/packet-ieee80211-radio.h
@@ -0,0 +1,57 @@
+/* packet-ieee80211-radio.h
+ * Routines for pseudo 802.11 header dissection and radio packet timing calculation
+ *
+ * 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.
+ */
+
+#ifndef __WLAN_RADIO_H__
+#define __WLAN_RADIO_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct aggregate {
+ guint phy;
+ union ieee_802_11_phy_info phy_info;
+ gint8 rssi; /* sometimes only available on the last frame */
+ guint duration; /* total duration of data in microseconds (without preamble) */
+};
+
+struct wlan_radio {
+ struct aggregate *aggregate; /* if this frame is part of an aggregate, point to it, otherwise NULL */
+ guint prior_aggregate_data; /* length of all prior data in this aggregate
+ used for calculating duration of this subframe */
+ guint64 start_tsf;
+ guint64 end_tsf;
+
+ gint64 ifs; /* inter frame space in microseconds */
+
+ guint16 nav;
+ gint8 rssi;
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __WLAN_RADIO_H__ */
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>&amp;Full Screen</string>
</property>
</action>
+ <action name="actionWirelessTimelineZoomIn">
+ <property name="text">
+ <string>&amp;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:
+ */