1 /* bluetooth_devices_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 "bluetooth_devices_dialog.h"
11 #include <ui_bluetooth_devices_dialog.h>
12 
13 #include "bluetooth_device_dialog.h"
14 
15 #include <ui/qt/utils/color_utils.h>
16 
17 #include "epan/epan.h"
18 #include "epan/addr_resolv.h"
19 #include "epan/to_str.h"
20 #include "epan/epan_dissect.h"
21 #include "epan/prefs.h"
22 #include "epan/dissectors/packet-bluetooth.h"
23 #include "epan/dissectors/packet-bthci_evt.h"
24 
25 #include <ui/qt/utils/variant_pointer.h>
26 
27 #include "ui/simple_dialog.h"
28 #include "ui/qt/widgets/wireshark_file_dialog.h"
29 
30 #include <QClipboard>
31 #include <QContextMenuEvent>
32 #include <QPushButton>
33 #include <QTreeWidget>
34 
35 static const int column_number_bd_addr = 0;
36 static const int column_number_bd_addr_oui = 1;
37 static const int column_number_name = 2;
38 static const int column_number_lmp_version = 3;
39 static const int column_number_lmp_subversion = 4;
40 static const int column_number_manufacturer = 5;
41 static const int column_number_hci_version = 6;
42 static const int column_number_hci_revision = 7;
43 static const int column_number_is_local_adapter = 8;
44 
45 
46 static tap_packet_status
bluetooth_device_tap_packet(void * tapinfo_ptr,packet_info * pinfo,epan_dissect_t * edt,const void * data)47 bluetooth_device_tap_packet(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *edt, const void* data)
48 {
49     bluetooth_devices_tapinfo_t *tapinfo = (bluetooth_devices_tapinfo_t *) tapinfo_ptr;
50 
51     if (tapinfo->tap_packet)
52         tapinfo->tap_packet(tapinfo, pinfo, edt, data);
53 
54     return TAP_PACKET_REDRAW;
55 }
56 
57 static void
bluetooth_device_tap_reset(void * tapinfo_ptr)58 bluetooth_device_tap_reset(void *tapinfo_ptr)
59 {
60     bluetooth_devices_tapinfo_t *tapinfo = (bluetooth_devices_tapinfo_t *) tapinfo_ptr;
61 
62     if (tapinfo->tap_reset)
63         tapinfo->tap_reset(tapinfo);
64 }
65 
BluetoothDevicesDialog(QWidget & parent,CaptureFile & cf,PacketList * packet_list)66 BluetoothDevicesDialog::BluetoothDevicesDialog(QWidget &parent, CaptureFile &cf, PacketList *packet_list) :
67     WiresharkDialog(parent, cf),
68     ui(new Ui::BluetoothDevicesDialog)
69 {
70     ui->setupUi(this);
71     loadGeometry(parent.width() * 4 / 5, parent.height() * 2 / 3);
72 
73     packet_list_ = packet_list;
74 
75     connect(ui->tableTreeWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(tableContextMenu(const QPoint &)));
76     connect(ui->tableTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(tableItemDoubleClicked(QTreeWidgetItem *, int)));
77     connect(ui->interfaceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(interfaceCurrentIndexChanged(int)));
78     connect(ui->showInformationStepsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(showInformationStepsChanged(int)));
79 
80     ui->tableTreeWidget->sortByColumn(column_number_bd_addr, Qt::AscendingOrder);
81 
82     ui->tableTreeWidget->setStyleSheet("QTreeView::item:hover{background-color:lightyellow; color:black;}");
83 
84     context_menu_.addActions(QList<QAction *>() << ui->actionMark_Unmark_Cell);
85     context_menu_.addActions(QList<QAction *>() << ui->actionMark_Unmark_Row);
86     context_menu_.addActions(QList<QAction *>() << ui->actionCopy_Cell);
87     context_menu_.addActions(QList<QAction *>() << ui->actionCopy_Rows);
88     context_menu_.addActions(QList<QAction *>() << ui->actionCopy_All);
89     context_menu_.addActions(QList<QAction *>() << ui->actionSave_as_image);
90 
91     tapinfo_.tap_packet = tapPacket;
92     tapinfo_.tap_reset  = tapReset;
93     tapinfo_.ui = this;
94 
95     registerTapListener("bluetooth.device", &tapinfo_, NULL,
96                         0,
97                         bluetooth_device_tap_reset,
98                         bluetooth_device_tap_packet,
99                         NULL
100                         );
101     ui->hintLabel->setText(ui->hintLabel->text().arg(0));
102 
103     cap_file_.retapPackets();
104 }
105 
106 
~BluetoothDevicesDialog()107 BluetoothDevicesDialog::~BluetoothDevicesDialog()
108 {
109     delete ui;
110 }
111 
112 
captureFileClosed()113 void BluetoothDevicesDialog::captureFileClosed()
114 {
115     ui->interfaceComboBox->setEnabled(FALSE);
116     ui->showInformationStepsCheckBox->setEnabled(FALSE);
117 
118     WiresharkDialog::captureFileClosed();
119 }
120 
121 
changeEvent(QEvent * event)122 void BluetoothDevicesDialog::changeEvent(QEvent *event)
123 {
124     if (0 != event)
125     {
126         switch (event->type())
127         {
128         case QEvent::LanguageChange:
129             ui->retranslateUi(this);
130             break;
131         default:
132             break;
133         }
134     }
135     QDialog::changeEvent(event);
136 }
137 
138 
keyPressEvent(QKeyEvent * event)139 void BluetoothDevicesDialog::keyPressEvent(QKeyEvent *event)
140 {
141 /* NOTE: Do nothing*, but in real it "takes focus" from button_box so allow user
142  * to use Enter button to jump to frame from tree widget */
143 /* * - reimplement shortcuts from contex menu */
144 
145    if (event->modifiers() & Qt::ControlModifier && event->key()== Qt::Key_M)
146         on_actionMark_Unmark_Row_triggered();
147 }
148 
149 
tableContextMenu(const QPoint & pos)150 void BluetoothDevicesDialog::tableContextMenu(const QPoint &pos)
151 {
152     context_menu_.exec(ui->tableTreeWidget->viewport()->mapToGlobal(pos));
153 }
154 
tableItemDoubleClicked(QTreeWidgetItem * item,int)155 void BluetoothDevicesDialog::tableItemDoubleClicked(QTreeWidgetItem *item, int)
156 {
157     bluetooth_item_data_t            *item_data;
158     BluetoothDeviceDialog  *bluetooth_device_dialog;
159 
160     item_data = VariantPointer<bluetooth_item_data_t>::asPtr(item->data(0, Qt::UserRole));
161     bluetooth_device_dialog = new BluetoothDeviceDialog(*this, cap_file_, item->text(column_number_bd_addr), item->text(column_number_name), item_data->interface_id, item_data->adapter_id, !item->text(column_number_is_local_adapter).isEmpty());
162     connect(bluetooth_device_dialog, SIGNAL(goToPacket(int)),
163             packet_list_, SLOT(goToPacket(int)));
164     bluetooth_device_dialog->show();
165 }
166 
167 
on_actionMark_Unmark_Cell_triggered()168 void BluetoothDevicesDialog::on_actionMark_Unmark_Cell_triggered()
169 {
170     QBrush fg;
171     QBrush bg;
172 
173     if (ui->tableTreeWidget->currentItem()->background(ui->tableTreeWidget->currentColumn()) == QBrush(ColorUtils::fromColorT(&prefs.gui_marked_bg))) {
174         fg = QBrush();
175         bg = QBrush();
176     } else {
177         fg = QBrush(ColorUtils::fromColorT(&prefs.gui_marked_fg));
178         bg = QBrush(ColorUtils::fromColorT(&prefs.gui_marked_bg));
179     }
180 
181     ui->tableTreeWidget->currentItem()->setForeground(ui->tableTreeWidget->currentColumn(), fg);
182     ui->tableTreeWidget->currentItem()->setBackground(ui->tableTreeWidget->currentColumn(), bg);
183 }
184 
185 
on_actionMark_Unmark_Row_triggered()186 void BluetoothDevicesDialog::on_actionMark_Unmark_Row_triggered()
187 {
188     QBrush fg;
189     QBrush bg;
190     bool   is_marked = TRUE;
191 
192     for (int i = 0; i < ui->tableTreeWidget->columnCount(); i += 1) {
193         if (ui->tableTreeWidget->currentItem()->background(i) != QBrush(ColorUtils::fromColorT(&prefs.gui_marked_bg)))
194             is_marked = FALSE;
195     }
196 
197     if (is_marked) {
198         fg = QBrush();
199         bg = QBrush();
200     } else {
201         fg = QBrush(ColorUtils::fromColorT(&prefs.gui_marked_fg));
202         bg = QBrush(ColorUtils::fromColorT(&prefs.gui_marked_bg));
203     }
204 
205     for (int i = 0; i < ui->tableTreeWidget->columnCount(); i += 1) {
206         ui->tableTreeWidget->currentItem()->setForeground(i, fg);
207         ui->tableTreeWidget->currentItem()->setBackground(i, bg);
208     }
209 }
210 
211 
on_actionCopy_Cell_triggered()212 void BluetoothDevicesDialog::on_actionCopy_Cell_triggered()
213 {
214     QClipboard             *clipboard = QApplication::clipboard();
215     QString                 copy;
216 
217     copy = QString(ui->tableTreeWidget->currentItem()->text(ui->tableTreeWidget->currentColumn()));
218 
219     clipboard->setText(copy);
220 }
221 
222 
on_actionCopy_Rows_triggered()223 void BluetoothDevicesDialog::on_actionCopy_Rows_triggered()
224 {
225     QClipboard                         *clipboard = QApplication::clipboard();
226     QString                             copy;
227     QList<QTreeWidgetItem *>            items;
228     QList<QTreeWidgetItem *>::iterator  i_item;
229 
230     items =  ui->tableTreeWidget->selectedItems();
231 
232     for (i_item = items.begin(); i_item != items.end(); ++i_item) {
233         copy += QString("%1  %2  %3  %4  %5  %6  %7  %8  %9\n")
234                 .arg((*i_item)->text(column_number_bd_addr), -20)
235                 .arg((*i_item)->text(column_number_bd_addr_oui), -20)
236                 .arg((*i_item)->text(column_number_name), -30)
237                 .arg((*i_item)->text(column_number_lmp_version), -20)
238                 .arg((*i_item)->text(column_number_lmp_subversion), -20)
239                 .arg((*i_item)->text(column_number_manufacturer), -30)
240                 .arg((*i_item)->text(column_number_hci_version), -20)
241                 .arg((*i_item)->text(column_number_hci_revision), -20)
242                 .arg((*i_item)->text(column_number_is_local_adapter), -20);
243     }
244 
245     clipboard->setText(copy);
246 }
247 
tapReset(void * tapinfo_ptr)248 void BluetoothDevicesDialog::tapReset(void *tapinfo_ptr)
249 {
250     bluetooth_devices_tapinfo_t *tapinfo = (bluetooth_devices_tapinfo_t *) tapinfo_ptr;
251     BluetoothDevicesDialog  *bluetooth_devices_dialog = static_cast<BluetoothDevicesDialog *>(tapinfo->ui);
252 
253     bluetooth_devices_dialog->ui->tableTreeWidget->clear();
254 }
255 
tapPacket(void * tapinfo_ptr,packet_info * pinfo,epan_dissect_t *,const void * data)256 tap_packet_status BluetoothDevicesDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *data)
257 {
258     bluetooth_devices_tapinfo_t  *tapinfo    = static_cast<bluetooth_devices_tapinfo_t *>(tapinfo_ptr);
259     BluetoothDevicesDialog       *dialog     = static_cast<BluetoothDevicesDialog *>(tapinfo->ui);
260     bluetooth_device_tap_t       *tap_device = static_cast<bluetooth_device_tap_t *>(const_cast<void *>(data));
261     QString                       bd_addr;
262     QString                       bd_addr_oui;
263     const gchar                  *manuf;
264     QTreeWidgetItem              *item = NULL;
265 
266     if (dialog->file_closed_)
267         return TAP_PACKET_DONT_REDRAW;
268 
269     if (pinfo->rec->rec_type != REC_TYPE_PACKET)
270         return TAP_PACKET_DONT_REDRAW;
271 
272     if (pinfo->rec->presence_flags & WTAP_HAS_INTERFACE_ID) {
273         gchar       *interface;
274         const char  *interface_name;
275 
276         interface_name = epan_get_interface_name(pinfo->epan, pinfo->rec->rec_header.packet_header.interface_id);
277         interface = wmem_strdup_printf(pinfo->pool, "%u: %s", pinfo->rec->rec_header.packet_header.interface_id, interface_name);
278 
279         if (dialog->ui->interfaceComboBox->findText(interface) == -1)
280             dialog->ui->interfaceComboBox->addItem(interface);
281 
282         if (interface && dialog->ui->interfaceComboBox->currentIndex() > 0) {
283             if (dialog->ui->interfaceComboBox->currentText() != interface)
284             return TAP_PACKET_REDRAW;
285         }
286     }
287 
288     if (tap_device->has_bd_addr) {
289         for (int i = 0; i < 6; ++i) {
290             bd_addr += QString("%1:").arg(tap_device->bd_addr[i], 2, 16, QChar('0'));
291         }
292         bd_addr.chop(1); // remove extra character ":" from the end of the string
293         manuf = get_ether_name(tap_device->bd_addr);
294         if (manuf) {
295             int pos;
296 
297             bd_addr_oui = QString(manuf);
298             pos = bd_addr_oui.indexOf('_');
299             if (pos < 0) {
300                 manuf = NULL;
301             } else {
302                 bd_addr_oui.remove(pos, bd_addr_oui.size());
303             }
304         }
305 
306         if (!manuf)
307             bd_addr_oui = "";
308     }
309 
310     if (dialog->ui->showInformationStepsCheckBox->checkState() != Qt::Checked) {
311         QTreeWidgetItemIterator i_item(dialog->ui->tableTreeWidget);
312 
313         while (*i_item) {
314             QTreeWidgetItem *current_item = static_cast<QTreeWidgetItem*>(*i_item);
315             bluetooth_item_data_t *item_data = VariantPointer<bluetooth_item_data_t>::asPtr(current_item->data(0, Qt::UserRole));
316 
317             if ((tap_device->has_bd_addr && current_item->text(column_number_bd_addr) == bd_addr) ||
318                     (tap_device->is_local &&
319                     item_data->interface_id == tap_device->interface_id &&
320                     item_data->adapter_id == tap_device->adapter_id &&
321                     !current_item->text(column_number_is_local_adapter).isEmpty())) {
322                 item = current_item;
323                 break;
324             }
325             ++i_item;
326         }
327     }
328 
329     if (!item) {
330         item = new QTreeWidgetItem(dialog->ui->tableTreeWidget);
331         item->setText(column_number_bd_addr, bd_addr);
332         item->setText(column_number_bd_addr_oui, bd_addr_oui);
333         if (tap_device->is_local) {
334             item->setText(column_number_is_local_adapter,  tr("true"));
335         }
336 
337         bluetooth_item_data_t *item_data = wmem_new(wmem_file_scope(), bluetooth_item_data_t);
338         item_data->interface_id = tap_device->interface_id;
339         item_data->adapter_id = tap_device->adapter_id;
340         item_data->frame_number = pinfo->num;
341         item->setData(0, Qt::UserRole, VariantPointer<bluetooth_item_data_t>::asQVariant(item_data));
342     }
343 
344     if (tap_device->type == BLUETOOTH_DEVICE_BD_ADDR) {
345         item->setText(column_number_bd_addr, bd_addr);
346         item->setText(column_number_bd_addr_oui, bd_addr_oui);
347     }
348 
349     if (tap_device->type == BLUETOOTH_DEVICE_NAME) {
350         item->setText(column_number_name,  tap_device->data.name);
351     }
352 
353     if (tap_device->type == BLUETOOTH_DEVICE_LOCAL_ADAPTER)
354         item->setText(column_number_is_local_adapter,  tr("true"));
355 
356     if (tap_device->type == BLUETOOTH_DEVICE_LOCAL_VERSION) {
357         item->setText(column_number_hci_version,    val_to_str_const(tap_device->data.local_version.hci_version, bthci_evt_hci_version, "Unknown 0x%02x"));
358         item->setText(column_number_hci_revision,   QString::number(tap_device->data.local_version.hci_revision));
359         item->setText(column_number_lmp_version,    val_to_str_const(tap_device->data.local_version.lmp_version, bthci_evt_lmp_version, "Unknown 0x%02x"));
360         item->setText(column_number_lmp_subversion, QString::number(tap_device->data.local_version.lmp_subversion));
361         item->setText(column_number_manufacturer,   val_to_str_ext_const(tap_device->data.local_version.manufacturer, &bluetooth_company_id_vals_ext, "Unknown 0x%04x"));
362     }
363     if (tap_device->type == BLUETOOTH_DEVICE_REMOTE_VERSION) {
364         item->setText(column_number_lmp_version,    val_to_str_const(tap_device->data.remote_version.lmp_version, bthci_evt_lmp_version, "Unknown 0x%02x"));
365         item->setText(column_number_lmp_subversion, QString::number(tap_device->data.remote_version.lmp_subversion));
366         item->setText(column_number_manufacturer,   val_to_str_ext_const(tap_device->data.remote_version.manufacturer, &bluetooth_company_id_vals_ext, "Unknown 0x%04x"));
367     }
368 
369     for (int i = 0; i < dialog->ui->tableTreeWidget->columnCount(); i++) {
370         dialog->ui->tableTreeWidget->resizeColumnToContents(i);
371     }
372 
373     dialog->ui->hintLabel->setText(QString(tr("%1 items; Right click for more option; Double click for device details")).arg(dialog->ui->tableTreeWidget->topLevelItemCount()));
374 
375     return TAP_PACKET_REDRAW;
376 }
377 
interfaceCurrentIndexChanged(int)378 void BluetoothDevicesDialog::interfaceCurrentIndexChanged(int)
379 {
380     cap_file_.retapPackets();
381 }
382 
showInformationStepsChanged(int)383 void BluetoothDevicesDialog::showInformationStepsChanged(int)
384 {
385     cap_file_.retapPackets();
386 }
387 
on_tableTreeWidget_itemActivated(QTreeWidgetItem * item,int)388 void BluetoothDevicesDialog::on_tableTreeWidget_itemActivated(QTreeWidgetItem *item, int)
389 {
390     if (file_closed_)
391         return;
392 
393     bluetooth_item_data_t *item_data = VariantPointer<bluetooth_item_data_t>::asPtr(item->data(0, Qt::UserRole));
394 
395     emit goToPacket(item_data->frame_number);
396 
397 }
398 
on_actionCopy_All_triggered()399 void BluetoothDevicesDialog::on_actionCopy_All_triggered()
400 {
401     QClipboard              *clipboard = QApplication::clipboard();
402     QString                  copy;
403     QTreeWidgetItemIterator  i_item(ui->tableTreeWidget);
404     QTreeWidgetItem         *item;
405 
406     item = ui->tableTreeWidget->headerItem();
407 
408     copy += QString("%1  %2  %3  %4  %5  %6  %7  %8  %9\n")
409             .arg(item->text(column_number_bd_addr), -20)
410             .arg(item->text(column_number_bd_addr_oui), -20)
411             .arg(item->text(column_number_name), -30)
412             .arg(item->text(column_number_lmp_version), -20)
413             .arg(item->text(column_number_lmp_subversion), -20)
414             .arg(item->text(column_number_manufacturer), -30)
415             .arg(item->text(column_number_hci_version), -20)
416             .arg(item->text(column_number_hci_revision), -20)
417             .arg(item->text(column_number_is_local_adapter), -20);
418 
419     while (*i_item) {
420         item = static_cast<QTreeWidgetItem*>(*i_item);
421         copy += QString("%1  %2  %3  %4  %5  %6  %7  %8  %9\n")
422                 .arg(item->text(column_number_bd_addr), -20)
423                 .arg(item->text(column_number_bd_addr_oui), -20)
424                 .arg(item->text(column_number_name), -30)
425                 .arg(item->text(column_number_lmp_version), -20)
426                 .arg(item->text(column_number_lmp_subversion), -20)
427                 .arg(item->text(column_number_manufacturer), -30)
428                 .arg(item->text(column_number_hci_version), -20)
429                 .arg(item->text(column_number_hci_revision), -20)
430                 .arg(item->text(column_number_is_local_adapter), -20);
431         ++i_item;
432     }
433 
434     clipboard->setText(copy);
435 }
436 
on_actionSave_as_image_triggered()437 void BluetoothDevicesDialog::on_actionSave_as_image_triggered()
438 {
439     QPixmap image;
440 
441     QString fileName = WiresharkFileDialog::getSaveFileName(this,
442             tr("Save Table Image"),
443             "bluetooth_devices_table.png",
444             tr("PNG Image (*.png)"));
445 
446     if (fileName.isEmpty()) return;
447 
448     image = ui->tableTreeWidget->grab();
449     image.save(fileName, "PNG");
450 }
451 
on_buttonBox_clicked(QAbstractButton *)452 void BluetoothDevicesDialog::on_buttonBox_clicked(QAbstractButton *)
453 {
454 /*    if (button == foo_button_) */
455 }
456