1 /* wlan_statistics_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "wlan_statistics_dialog.h"
11 
12 #include <epan/packet.h>
13 #include <epan/strutil.h>
14 #include <epan/tap.h>
15 
16 #include <epan/dissectors/packet-ieee80211.h>
17 
18 #include <QElapsedTimer>
19 #include <QTreeWidget>
20 #include <QTreeWidgetItem>
21 
22 #include <ui/qt/models/percent_bar_delegate.h>
23 #include <ui/qt/utils/qt_ui_utils.h>
24 #include "wireshark_application.h"
25 
26 // To do:
27 // - Add the name resolution checkbox
28 // - Add the "Only show defined networks" checkbox
29 
30 enum {
31     col_bssid_,
32     col_channel_,
33     col_ssid_,
34     col_pct_packets_,
35     col_pct_retry_,
36     col_retry_packets_,
37     col_beacons_,
38     col_data_packets_,
39     col_probe_reqs_,
40     col_probe_resps_,
41     col_auths_,
42     col_deauths_,
43     col_others_,
44     col_protection_
45 };
46 
47 enum {
48     wlan_network_row_type_ = 1000,
49     wlan_station_row_type_
50 };
51 
52 class WlanStationTreeWidgetItem : public QTreeWidgetItem
53 {
54 public:
WlanStationTreeWidgetItem(const address * addr)55     WlanStationTreeWidgetItem(const address *addr) :
56         QTreeWidgetItem (wlan_station_row_type_),
57         packets_(0),
58         retry_(0),
59         sent_(0),
60         received_(0),
61         probe_req_(0),
62         probe_resp_(0),
63         auth_(0),
64         deauth_(0),
65         other_(0)
66     {
67         copy_address(&addr_, addr);
68         setText(col_bssid_, address_to_qstring(&addr_));
69     }
isMatch(const address * addr)70     bool isMatch(const address *addr) {
71         return addresses_equal(&addr_, addr);
72     }
update(const wlan_hdr_t * wlan_hdr)73     void update(const wlan_hdr_t *wlan_hdr) {
74         bool is_sender = addresses_equal(&addr_, &wlan_hdr->src);
75 
76         if (wlan_hdr->stats.fc_retry != 0) {
77             retry_++;
78         }
79 
80         // XXX Should we count received probes and auths? This is what the
81         // GTK+ UI does, but it seems odd.
82         switch (wlan_hdr->type) {
83         case MGT_PROBE_REQ:
84             probe_req_++;
85             break;
86         case MGT_PROBE_RESP:
87             probe_resp_++;
88             break;
89         case MGT_BEACON:
90             // Skip
91             break;
92         case MGT_AUTHENTICATION:
93             auth_++;
94             break;
95         case MGT_DEAUTHENTICATION:
96             deauth_++;
97             break;
98         case DATA:
99         case DATA_CF_ACK:
100         case DATA_CF_POLL:
101         case DATA_CF_ACK_POLL:
102         case DATA_QOS_DATA:
103         case DATA_QOS_DATA_CF_ACK:
104         case DATA_QOS_DATA_CF_POLL:
105         case DATA_QOS_DATA_CF_ACK_POLL:
106             if (is_sender) {
107                 sent_++;
108             } else {
109                 received_++;
110             }
111             break;
112         default:
113             other_++;
114             break;
115         }
116         if (wlan_hdr->type != MGT_BEACON) packets_++;
117     }
draw(address * bssid,int num_packets)118     void draw(address *bssid, int num_packets) {
119         if (packets_ && num_packets > 0) {
120             setData(col_pct_packets_, Qt::UserRole, QVariant::fromValue<double>(packets_ * 100.0 / num_packets));
121             setData(col_pct_retry_, Qt::UserRole, QVariant::fromValue<double>(retry_ * 100.0 / packets_));
122         } else {
123             setData(col_pct_packets_, Qt::UserRole, QVariant::fromValue<double>(0));
124             setData(col_pct_retry_, Qt::UserRole, QVariant::fromValue<double>(0));
125         }
126         setText(col_beacons_, QString::number(sent_));
127         setText(col_data_packets_, QString::number(received_));
128         setText(col_retry_packets_, QString::number(retry_));
129         setText(col_probe_reqs_, QString::number(probe_req_));
130         setText(col_probe_resps_, QString::number(probe_resp_));
131         setText(col_auths_, QString::number(auth_));
132         setText(col_deauths_, QString::number(deauth_));
133         setText(col_others_, QString::number(other_));
134 
135         if (!is_broadcast_bssid(bssid) && addresses_data_equal(&addr_, bssid)) {
136             setText(col_protection_, QObject::tr("Base station"));
137         }
138     }
operator <(const QTreeWidgetItem & other) const139     bool operator< (const QTreeWidgetItem &other) const
140     {
141         if (other.type() != wlan_station_row_type_) return QTreeWidgetItem::operator< (other);
142         const WlanStationTreeWidgetItem *other_row = static_cast<const WlanStationTreeWidgetItem *>(&other);
143 
144         switch (treeWidget()->sortColumn()) {
145         case col_bssid_:
146             return cmp_address(&addr_, &other_row->addr_) < 0;
147         case col_pct_packets_:
148             return packets_ < other_row->packets_;
149         case col_beacons_:
150             return sent_ < other_row->sent_;
151         case col_data_packets_:
152             return received_ < other_row->received_;
153         case col_probe_reqs_:
154             return probe_req_ < other_row->probe_req_;
155         case col_probe_resps_:
156             return probe_resp_ < other_row->probe_resp_;
157         case col_auths_:
158             return auth_ < other_row->auth_;
159         case col_deauths_:
160             return deauth_ < other_row->deauth_;
161         case col_others_:
162             return other_ < other_row->other_;
163         case col_retry_packets_:
164         case col_pct_retry_:
165             return retry_ < other_row->retry_;
166         default:
167             break;
168         }
169 
170         return QTreeWidgetItem::operator< (other);
171     }
rowData()172     QList<QVariant> rowData() {
173         return QList<QVariant>()
174                 << address_to_qstring(&addr_)
175                 << data(col_pct_packets_, Qt::UserRole).toDouble()
176                 << data(col_pct_retry_, Qt::UserRole).toDouble() << retry_
177                 << sent_ << received_ << probe_req_ << probe_resp_
178                 << auth_ << deauth_ << other_ << text(col_protection_);
179     }
filterExpression()180     const QString filterExpression() {
181         QString filter_expr = QString("wlan.addr==%1")
182                 .arg(address_to_qstring(&addr_));
183         return filter_expr;
184     }
185 
186 private:
187     address addr_;
188     int packets_;
189     int retry_;
190     int sent_;
191     int received_;
192     int probe_req_;
193     int probe_resp_;
194     int auth_;
195     int deauth_;
196     int other_;
197 
198 };
199 
200 class WlanNetworkTreeWidgetItem : public QTreeWidgetItem
201 {
202 public:
WlanNetworkTreeWidgetItem(QTreeWidget * parent,const wlan_hdr_t * wlan_hdr)203     WlanNetworkTreeWidgetItem(QTreeWidget *parent, const wlan_hdr_t *wlan_hdr) :
204         QTreeWidgetItem (parent, wlan_network_row_type_),
205         beacon_(0),
206         data_packet_(0),
207         retry_packet_(0),
208         probe_req_(0),
209         probe_resp_(0),
210         auth_(0),
211         deauth_(0),
212         other_(0),
213         packets_(0)
214     {
215         updateBssid(wlan_hdr);
216         channel_ = wlan_hdr->stats.channel;
217         ssid_ = QByteArray::fromRawData((const char *)wlan_hdr->stats.ssid, wlan_hdr->stats.ssid_len);
218         QString ssid_text;
219 
220         if (wlan_hdr->stats.ssid_len == 0) {
221             ssid_text = QObject::tr("<Broadcast>");
222         } else if (wlan_hdr->stats.ssid_len == 1 && wlan_hdr->stats.ssid[0] == 0) {
223             ssid_text = QObject::tr("<Hidden>");
224         } else {
225             gchar *str = format_text(NULL, wlan_hdr->stats.ssid, wlan_hdr->stats.ssid_len);
226             ssid_text = str;
227             wmem_free(NULL, str);
228         }
229 
230         setText(col_ssid_, ssid_text);
231     }
232 
isMatch(const wlan_hdr_t * wlan_hdr)233     bool isMatch(const wlan_hdr_t *wlan_hdr) {
234         bool is_bssid_match = false;
235         bool is_ssid_match = false;
236         bool update_bssid = false;
237         bool update_ssid = false;
238         // We want (but might not have) a unicast BSSID and a named SSID. Try
239         // to match the current packet and update our information if possible.
240 
241         if (addresses_equal(&bssid_, &wlan_hdr->bssid)) {
242             is_bssid_match = true;
243         }
244 
245         if ((wlan_hdr->stats.ssid_len > 0) && (wlan_hdr->stats.ssid[0] != 0)) {
246             QByteArray hdr_ssid = QByteArray::fromRawData((const char *)wlan_hdr->stats.ssid, wlan_hdr->stats.ssid_len);
247             if (ssid_ == hdr_ssid) {
248                 is_ssid_match = true;
249             }
250         }
251 
252         if (is_bssid_match && is_ssid_match) return true;
253 
254         // Probe requests.
255         if (wlan_hdr->type == MGT_PROBE_REQ) {
256             // Probes with visible SSIDs. Unicast or broadcast.
257             if (is_ssid_match) {
258                 if (is_broadcast_ && !is_broadcast_bssid(&wlan_hdr->bssid)) {
259                     update_bssid = true;
260                 }
261             // Probes with hidden SSIDs. Unicast.
262             } else if ((wlan_hdr->stats.ssid_len == 1) && (wlan_hdr->stats.ssid[0] == 0)) {
263                 if (!is_broadcast_ && addresses_equal(&bssid_, &wlan_hdr->bssid)) {
264                     is_bssid_match = true;
265                     update_ssid = true;
266                 }
267             // Probes with no SSID. Broadcast.
268             } else if (ssid_.isEmpty() && wlan_hdr->stats.ssid_len < 1) {
269                 if (is_broadcast_ && is_broadcast_bssid(&wlan_hdr->bssid)) {
270                     return true;
271                 }
272             }
273         // Non-probe requests (responses, beacons, etc)
274         } else {
275             if (is_ssid_match) {
276                 if (is_broadcast_ && !is_broadcast_bssid(&wlan_hdr->bssid)) {
277                     update_bssid = true;
278                 }
279             } else if (wlan_hdr->stats.ssid_len < 1) {
280                 // No SSID.
281                 is_ssid_match = true;
282             }
283             if (is_bssid_match) {
284                 if ((ssid_.isEmpty() || ssid_[0] == '\0') && (wlan_hdr->stats.ssid_len > 0) && (wlan_hdr->stats.ssid[0] != 0)) {
285                     update_ssid = true;
286                 }
287             }
288         }
289 
290         if (update_bssid) {
291             updateBssid(wlan_hdr);
292             is_bssid_match = true;
293         }
294 
295         if (update_ssid) {
296             gchar* str;
297             ssid_ = QByteArray::fromRawData((const char *)wlan_hdr->stats.ssid, wlan_hdr->stats.ssid_len);
298             str = format_text(NULL, wlan_hdr->stats.ssid, wlan_hdr->stats.ssid_len);
299             setText(col_ssid_, str);
300             wmem_free(NULL, str);
301             is_ssid_match = true;
302         }
303 
304         return is_bssid_match && is_ssid_match;
305     }
306 
update(const wlan_hdr_t * wlan_hdr)307     void update(const wlan_hdr_t *wlan_hdr) {
308         if (channel_ == 0 && wlan_hdr->stats.channel != 0) {
309             channel_ = wlan_hdr->stats.channel;
310         }
311         if (text(col_protection_).isEmpty() && wlan_hdr->stats.protection[0] != 0) {
312             setText(col_protection_, wlan_hdr->stats.protection);
313         }
314         if (wlan_hdr->stats.fc_retry != 0) {
315             retry_packet_++;
316         }
317 
318         switch (wlan_hdr->type) {
319         case MGT_PROBE_REQ:
320             probe_req_++;
321             break;
322         case MGT_PROBE_RESP:
323             probe_resp_++;
324             break;
325         case MGT_BEACON:
326             beacon_++;
327             break;
328         case MGT_AUTHENTICATION:
329             auth_++;
330             break;
331         case MGT_DEAUTHENTICATION:
332             deauth_++;
333             break;
334         case DATA:
335         case DATA_CF_ACK:
336         case DATA_CF_POLL:
337         case DATA_CF_ACK_POLL:
338         case DATA_QOS_DATA:
339         case DATA_QOS_DATA_CF_ACK:
340         case DATA_QOS_DATA_CF_POLL:
341         case DATA_QOS_DATA_CF_ACK_POLL:
342             data_packet_++;
343             break;
344         default:
345             other_++;
346             break;
347         }
348         packets_++;
349 
350         WlanStationTreeWidgetItem* sender_ws_ti = NULL;
351         WlanStationTreeWidgetItem* receiver_ws_ti = NULL;
352         foreach (QTreeWidgetItem *cur_ti, stations_) {
353             WlanStationTreeWidgetItem *cur_ws_ti = dynamic_cast<WlanStationTreeWidgetItem *>(cur_ti);
354             if (cur_ws_ti && (cur_ws_ti->isMatch(&wlan_hdr->src))) sender_ws_ti = cur_ws_ti;
355             if (cur_ws_ti && (cur_ws_ti->isMatch(&wlan_hdr->dst))) receiver_ws_ti = cur_ws_ti;
356             if (sender_ws_ti && receiver_ws_ti) break;
357         }
358         if (!sender_ws_ti) {
359             sender_ws_ti = new WlanStationTreeWidgetItem(&wlan_hdr->src);
360             stations_ << sender_ws_ti;
361         }
362         if (!receiver_ws_ti) {
363             receiver_ws_ti = new WlanStationTreeWidgetItem(&wlan_hdr->dst);
364             stations_ << receiver_ws_ti;
365         }
366         sender_ws_ti->update(wlan_hdr);
367         receiver_ws_ti->update(wlan_hdr);
368     }
369 
draw(int num_packets)370     void draw(int num_packets) {
371         if (channel_ > 0) setText(col_channel_, QString::number(channel_));
372         setData(col_pct_packets_, Qt::UserRole, QVariant::fromValue<double>(packets_ * 100.0 / num_packets));
373         setData(col_pct_retry_, Qt::UserRole, QVariant::fromValue<double>(retry_packet_ * 100.0 / packets_));
374         setText(col_retry_packets_, QString::number(retry_packet_));
375         setText(col_beacons_, QString::number(beacon_));
376         setText(col_data_packets_, QString::number(data_packet_));
377         setText(col_probe_reqs_, QString::number(probe_req_));
378         setText(col_probe_resps_, QString::number(probe_resp_));
379         setText(col_auths_, QString::number(auth_));
380         setText(col_deauths_, QString::number(deauth_));
381         setText(col_others_, QString::number(other_));
382     }
383 
addStations()384     void addStations() {
385         foreach (QTreeWidgetItem *cur_ti, stations_) {
386             WlanStationTreeWidgetItem *cur_ws_ti = dynamic_cast<WlanStationTreeWidgetItem *>(cur_ti);
387             cur_ws_ti->draw(&bssid_, packets_ - beacon_);
388             for (int col = 0; col < treeWidget()->columnCount(); col++) {
389                 cur_ws_ti->setTextAlignment(col, treeWidget()->headerItem()->textAlignment(col));
390             }
391         }
392 
393         addChildren(stations_);
394         stations_.clear();
395     }
396 
operator <(const QTreeWidgetItem & other) const397     bool operator< (const QTreeWidgetItem &other) const
398     {
399         if (other.type() != wlan_network_row_type_) return QTreeWidgetItem::operator< (other);
400         const WlanNetworkTreeWidgetItem *other_row = static_cast<const WlanNetworkTreeWidgetItem *>(&other);
401 
402         switch (treeWidget()->sortColumn()) {
403         case col_bssid_:
404             return cmp_address(&bssid_, &other_row->bssid_) < 0;
405         case col_channel_:
406             return channel_ < other_row->channel_;
407         case col_ssid_:
408             return ssid_ < other_row->ssid_;
409         case col_pct_packets_:
410             return packets_ < other_row->packets_;
411         case col_beacons_:
412             return beacon_ < other_row->beacon_;
413         case col_data_packets_:
414             return data_packet_ < other_row->data_packet_;
415         case col_probe_reqs_:
416             return probe_req_ < other_row->probe_req_;
417         case col_probe_resps_:
418             return probe_resp_ < other_row->probe_resp_;
419         case col_auths_:
420             return auth_ < other_row->auth_;
421         case col_deauths_:
422             return deauth_ < other_row->deauth_;
423         case col_others_:
424             return other_ < other_row->other_;
425         case col_protection_:
426         default:
427             break;
428         }
429 
430         return QTreeWidgetItem::operator< (other);
431     }
rowData()432     QList<QVariant> rowData() {
433         return QList<QVariant>()
434                 << address_to_qstring(&bssid_) << channel_ << text(col_ssid_)
435                 << data(col_pct_packets_, Qt::UserRole).toDouble()
436                 << data(col_pct_retry_, Qt::UserRole).toDouble()
437                 << retry_packet_ << beacon_  << data_packet_ << probe_req_
438                 << probe_resp_ << auth_ << deauth_ << other_
439                 << text(col_protection_);
440     }
441 
filterExpression()442     const QString filterExpression() {
443         QString filter_expr = QString("(wlan.bssid==%1")
444                 .arg(address_to_qstring(&bssid_));
445         if (!ssid_.isEmpty() && ssid_[0] != '\0') {
446             filter_expr += QString(" || wlan.ssid==\"%1\"")
447                     .arg(ssid_.constData());
448         }
449         filter_expr += ")";
450         return filter_expr;
451     }
452 
453 private:
454     address bssid_;
455     bool is_broadcast_;
456     int channel_;
457     QByteArray ssid_;
458     int beacon_;
459     int data_packet_;
460     int retry_packet_;
461     int probe_req_;
462     int probe_resp_;
463     int auth_;
464     int deauth_;
465     int other_;
466     int packets_;
467 
468     // Adding items one at a time is slow. Gather up the stations in a list
469     // and add them all at once later.
470     QList<QTreeWidgetItem *>stations_;
471 
updateBssid(const wlan_hdr_t * wlan_hdr)472     void updateBssid(const wlan_hdr_t *wlan_hdr) {
473         copy_address(&bssid_, &wlan_hdr->bssid);
474         is_broadcast_ = is_broadcast_bssid(&bssid_);
475         setText(col_bssid_, address_to_qstring(&bssid_));
476     }
477 };
478 
479 static const QString network_col_0_title_ = QObject::tr("BSSID");
480 static const QString network_col_6_title_ = QObject::tr("Beacons");
481 static const QString network_col_7_title_ = QObject::tr("Data Pkts");
482 static const QString network_col_13_title_ = QObject::tr("Protection");
483 
484 static const QString node_col_0_title_ = QObject::tr("Address");
485 static const QString node_col_4_title_ = QObject::tr("Pkts Sent");
486 static const QString node_col_5_title_ = QObject::tr("Pkts Received");
487 static const QString node_col_11_title_ = QObject::tr("Comment");
488 
WlanStatisticsDialog(QWidget & parent,CaptureFile & cf,const char * filter)489 WlanStatisticsDialog::WlanStatisticsDialog(QWidget &parent, CaptureFile &cf, const char *filter) :
490     TapParameterDialog(parent, cf, HELP_STATS_WLAN_TRAFFIC_DIALOG),
491     packet_count_(0),
492     cur_network_(0),
493     add_station_timer_(0)
494 {
495     setWindowSubtitle(tr("Wireless LAN Statistics"));
496     loadGeometry(parent.width() * 4 / 5, parent.height() * 3 / 4, "WlanStatisticsDialog");
497 
498     QStringList header_labels = QStringList()
499             << "" << tr("Channel") << tr("SSID") << tr("Percent Packets") << tr("Percent Retry")
500             << tr("Retry") << "" << "" << tr("Probe Reqs") << tr("Probe Resp") << tr("Auths")
501             << tr("Deauths") << tr("Other");
502     statsTreeWidget()->setHeaderLabels(header_labels);
503     updateHeaderLabels();
504     packets_delegate_ = new PercentBarDelegate();
505     statsTreeWidget()->setItemDelegateForColumn(col_pct_packets_, packets_delegate_);
506     retry_delegate_ = new PercentBarDelegate();
507     statsTreeWidget()->setItemDelegateForColumn(col_pct_retry_, retry_delegate_);
508     statsTreeWidget()->sortByColumn(col_bssid_, Qt::AscendingOrder);
509 
510     // resizeColumnToContents doesn't work well here, so set sizes manually.
511     int one_em = fontMetrics().height();
512     for (int col = 0; col < statsTreeWidget()->columnCount() - 1; col++) {
513         switch (col) {
514         case col_bssid_:
515             statsTreeWidget()->setColumnWidth(col, one_em * 11);
516             break;
517         case col_ssid_:
518             statsTreeWidget()->setColumnWidth(col, one_em * 8);
519             break;
520         case col_pct_packets_:
521         case col_pct_retry_:
522         case col_protection_:
523             statsTreeWidget()->setColumnWidth(col, one_em * 6);
524             break;
525         default:
526             // The rest are numeric
527             statsTreeWidget()->setColumnWidth(col, one_em * 4);
528             statsTreeWidget()->headerItem()->setTextAlignment(col, Qt::AlignRight);
529             break;
530         }
531     }
532 
533     addFilterActions();
534 
535     if (filter) {
536         setDisplayFilter(filter);
537     }
538 
539     add_station_timer_ = new QElapsedTimer();
540 
541     connect(statsTreeWidget(), SIGNAL(itemSelectionChanged()),
542             this, SLOT(updateHeaderLabels()));
543 
544     // Set handler for when display filter string is changed.
545     connect(this, SIGNAL(updateFilter(QString)),
546             this, SLOT(filterUpdated(QString)));
547 }
548 
~WlanStatisticsDialog()549 WlanStatisticsDialog::~WlanStatisticsDialog()
550 {
551     delete packets_delegate_;
552     delete retry_delegate_;
553     delete add_station_timer_;
554 }
555 
tapReset(void * ws_dlg_ptr)556 void WlanStatisticsDialog::tapReset(void *ws_dlg_ptr)
557 {
558     WlanStatisticsDialog *ws_dlg = static_cast<WlanStatisticsDialog *>(ws_dlg_ptr);
559     if (!ws_dlg) return;
560 
561     ws_dlg->statsTreeWidget()->clear();
562     ws_dlg->packet_count_ = 0;
563 }
564 
tapPacket(void * ws_dlg_ptr,_packet_info *,epan_dissect *,const void * wlan_hdr_ptr)565 tap_packet_status WlanStatisticsDialog::tapPacket(void *ws_dlg_ptr, _packet_info *, epan_dissect *, const void *wlan_hdr_ptr)
566 {
567     WlanStatisticsDialog *ws_dlg = static_cast<WlanStatisticsDialog *>(ws_dlg_ptr);
568     const wlan_hdr_t *wlan_hdr  = (const wlan_hdr_t *)wlan_hdr_ptr;
569     if (!ws_dlg || !wlan_hdr) return TAP_PACKET_DONT_REDRAW;
570 
571     guint16 frame_type = wlan_hdr->type & 0xff0;
572     if (!((frame_type == 0x0) || (frame_type == 0x20) || (frame_type == 0x30))
573         || ((frame_type == 0x20) && DATA_FRAME_IS_NULL(wlan_hdr->type))) {
574         /* Not a management or non null data or extension frame; let's skip it */
575         return TAP_PACKET_DONT_REDRAW;
576     }
577 
578     ws_dlg->packet_count_++;
579 
580     // XXX This is very slow for large numbers of networks. We might be
581     // able to store networks in a cache keyed on BSSID+SSID instead.
582     WlanNetworkTreeWidgetItem *wn_ti = NULL;
583     for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
584         QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
585         if (ti->type() != wlan_network_row_type_) continue;
586         WlanNetworkTreeWidgetItem *cur_wn_ti = static_cast<WlanNetworkTreeWidgetItem*>(ti);
587 
588         if (cur_wn_ti->isMatch(wlan_hdr)) {
589             wn_ti = cur_wn_ti;
590             break;
591         }
592     }
593 
594     if (!wn_ti) {
595         wn_ti = new WlanNetworkTreeWidgetItem(ws_dlg->statsTreeWidget(), wlan_hdr);
596         for (int col = 0; col < ws_dlg->statsTreeWidget()->columnCount(); col++) {
597             wn_ti->setTextAlignment(col, ws_dlg->statsTreeWidget()->headerItem()->textAlignment(col));
598         }
599     }
600 
601     wn_ti->update(wlan_hdr);
602     return TAP_PACKET_REDRAW;
603 }
604 
tapDraw(void * ws_dlg_ptr)605 void WlanStatisticsDialog::tapDraw(void *ws_dlg_ptr)
606 {
607     WlanStatisticsDialog *ws_dlg = static_cast<WlanStatisticsDialog *>(ws_dlg_ptr);
608     if (!ws_dlg) return;
609 
610     for (int i = 0; i < ws_dlg->statsTreeWidget()->topLevelItemCount(); i++) {
611         QTreeWidgetItem *ti = ws_dlg->statsTreeWidget()->topLevelItem(i);
612         if (ti->type() != wlan_network_row_type_) continue;
613 
614         WlanNetworkTreeWidgetItem *wn_ti = static_cast<WlanNetworkTreeWidgetItem*>(ti);
615         wn_ti->draw(ws_dlg->packet_count_);
616     }
617 }
618 
filterExpression()619 const QString WlanStatisticsDialog::filterExpression()
620 {
621     QString filter_expr;
622     if (statsTreeWidget()->selectedItems().count() > 0) {
623         QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
624 
625         if (ti->type() == wlan_network_row_type_) {
626             WlanNetworkTreeWidgetItem *wn_ti = static_cast<WlanNetworkTreeWidgetItem*>(ti);
627             filter_expr = wn_ti->filterExpression();
628         } else if (ti->type() == wlan_station_row_type_) {
629             WlanStationTreeWidgetItem *ws_ti = static_cast<WlanStationTreeWidgetItem*>(ti);
630             filter_expr = ws_ti->filterExpression();
631         }
632     }
633     return filter_expr;
634 }
635 
fillTree()636 void WlanStatisticsDialog::fillTree()
637 {
638     if (!registerTapListener("wlan",
639                              this,
640                              displayFilter_.toLatin1().data(),
641                              TL_REQUIRES_NOTHING,
642                              tapReset,
643                              tapPacket,
644                              tapDraw)) {
645         reject();
646         return;
647     }
648 
649     statsTreeWidget()->setSortingEnabled(false);
650     cap_file_.retapPackets();
651     tapDraw(this);
652     removeTapListeners();
653     statsTreeWidget()->setSortingEnabled(true);
654 
655     // Don't freeze if we have a large number of stations.
656     cur_network_ = 0;
657     QTimer::singleShot(0, this, SLOT(addStationTreeItems()));
658 }
659 
660 static const int add_station_interval_ = 5; // ms
addStationTreeItems()661 void WlanStatisticsDialog::addStationTreeItems()
662 {
663     add_station_timer_->start();
664     while (add_station_timer_->elapsed() < add_station_interval_ && cur_network_ < statsTreeWidget()->topLevelItemCount()) {
665         QTreeWidgetItem *ti = statsTreeWidget()->topLevelItem(cur_network_);
666         if (ti->type() != wlan_network_row_type_) continue;
667 
668         WlanNetworkTreeWidgetItem *wn_ti = static_cast<WlanNetworkTreeWidgetItem*>(ti);
669         wn_ti->addStations();
670         ++cur_network_;
671     }
672 
673     if (cur_network_ < statsTreeWidget()->topLevelItemCount()) {
674         QTimer::singleShot(0, this, SLOT(addStationTreeItems()));
675     }
676 }
677 
updateHeaderLabels()678 void WlanStatisticsDialog::updateHeaderLabels()
679 {
680     if (statsTreeWidget()->selectedItems().count() > 0 && statsTreeWidget()->selectedItems()[0]->type() == wlan_station_row_type_) {
681         statsTreeWidget()->headerItem()->setText(col_bssid_, node_col_0_title_);
682         statsTreeWidget()->headerItem()->setText(col_beacons_, node_col_4_title_);
683         statsTreeWidget()->headerItem()->setText(col_data_packets_, node_col_5_title_);
684         statsTreeWidget()->headerItem()->setText(col_protection_, node_col_11_title_);
685     } else {
686         statsTreeWidget()->headerItem()->setText(col_bssid_, network_col_0_title_);
687         statsTreeWidget()->headerItem()->setText(col_beacons_, network_col_6_title_);
688         statsTreeWidget()->headerItem()->setText(col_data_packets_, network_col_7_title_);
689         statsTreeWidget()->headerItem()->setText(col_protection_, network_col_13_title_);
690     }
691 }
692 
captureFileClosing()693 void WlanStatisticsDialog::captureFileClosing()
694 {
695     remove_tap_listener(this);
696 
697     WiresharkDialog::captureFileClosing();
698 }
699 
700 // Store filter from signal.
filterUpdated(QString filter)701 void WlanStatisticsDialog::filterUpdated(QString filter)
702 {
703     displayFilter_ = filter;
704 }
705 
706 // This is how an item is represented for exporting.
treeItemData(QTreeWidgetItem * it) const707 QList<QVariant> WlanStatisticsDialog::treeItemData(QTreeWidgetItem *it) const
708 {
709     // Cast up to our type.
710     WlanNetworkTreeWidgetItem *nit = dynamic_cast<WlanNetworkTreeWidgetItem*>(it);
711     if (nit) {
712         return nit->rowData();
713     }
714     // TODO: not going to cast to WlanStationTreeWidgetItem* and do the same as
715     // some of the columns are different...
716 
717     return QList<QVariant>();
718 }
719 
720 // Stat command + args
721 
722 static void
wlan_statistics_init(const char * args,void *)723 wlan_statistics_init(const char *args, void*) {
724     QStringList args_l = QString(args).split(',');
725     QByteArray filter;
726     if (args_l.length() > 2) {
727         filter = QStringList(args_l.mid(2)).join(",").toUtf8();
728     }
729     wsApp->emitStatCommandSignal("WlanStatistics", filter.constData(), NULL);
730 }
731 
732 static stat_tap_ui wlan_statistics_ui = {
733     REGISTER_STAT_GROUP_GENERIC,
734     NULL,
735     "wlan,stat",
736     wlan_statistics_init,
737     0,
738     NULL
739 };
740 
741 extern "C" {
742 
743 void register_tap_listener_qt_wlan_statistics(void);
744 
745 void
register_tap_listener_qt_wlan_statistics(void)746 register_tap_listener_qt_wlan_statistics(void)
747 {
748     register_stat_tap_ui(&wlan_statistics_ui, NULL);
749 }
750 
751 }
752