1 /* bluetooth_att_server_attributes_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "bluetooth_att_server_attributes_dialog.h"
11 #include <ui_bluetooth_att_server_attributes_dialog.h>
13 #include <ui/qt/utils/color_utils.h>
15 #include "epan/epan.h"
16 #include "epan/to_str.h"
17 #include "epan/epan_dissect.h"
18 #include "epan/prefs.h"
19 #include "epan/dissectors/packet-bluetooth.h"
20 #include "epan/dissectors/packet-btatt.h"
22 #include "ui/simple_dialog.h"
24 #include "ui/qt/widgets/wireshark_file_dialog.h"
27 #include <QContextMenuEvent>
28 #include <QPushButton>
29 #include <QTreeWidget>
31 static const int column_number_handle
= 0;
32 static const int column_number_uuid
= 1;
33 static const int column_number_uuid_name
= 2;
35 static tap_packet_status
36 btatt_handle_tap_packet(void *tapinfo_ptr
, packet_info
*pinfo
, epan_dissect_t
*edt
, const void* data
, tap_flags_t flags
)
38 tapinfo_t
*tapinfo
= (tapinfo_t
*) tapinfo_ptr
;
40 if (tapinfo
->tap_packet
)
41 tapinfo
->tap_packet(tapinfo
, pinfo
, edt
, data
, flags
);
43 return TAP_PACKET_REDRAW
;
47 btatt_handle_tap_reset(void *tapinfo_ptr
)
49 tapinfo_t
*tapinfo
= (tapinfo_t
*) tapinfo_ptr
;
51 if (tapinfo
->tap_reset
)
52 tapinfo
->tap_reset(tapinfo
);
55 static QTreeWidgetItem
*
56 item_with_handle_get(QTreeWidget
*tableTree
, uint16_t handle
)
58 QTreeWidgetItemIterator
i_item(tableTree
);
61 QTreeWidgetItem
*item
= static_cast<QTreeWidgetItem
*>(*i_item
);
63 if (item
->data(1, Qt::UserRole
).value
<uint32_t>() == handle
) {
73 BluetoothAttServerAttributesDialog::BluetoothAttServerAttributesDialog(QWidget
&parent
, CaptureFile
&cf
) :
74 WiresharkDialog(parent
, cf
),
75 ui(new Ui::BluetoothAttServerAttributesDialog
)
78 loadGeometry(parent
.width() * 4 / 5, parent
.height() * 2 / 3);
80 connect(ui
->tableTreeWidget
, &QTreeWidget::customContextMenuRequested
, this, &BluetoothAttServerAttributesDialog::tableContextMenu
);
81 connect(ui
->interfaceComboBox
, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged
), this, &BluetoothAttServerAttributesDialog::interfaceCurrentIndexChanged
);
82 connect(ui
->deviceComboBox
, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged
), this, &BluetoothAttServerAttributesDialog::deviceCurrentIndexChanged
);
83 #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
84 connect(ui
->removeDuplicatesCheckBox
, &QCheckBox::checkStateChanged
, this, &BluetoothAttServerAttributesDialog::removeDuplicatesStateChanged
);
86 connect(ui
->removeDuplicatesCheckBox
, &QCheckBox::stateChanged
, this, &BluetoothAttServerAttributesDialog::removeDuplicatesStateChanged
);
89 ui
->tableTreeWidget
->sortByColumn(column_number_handle
, Qt::AscendingOrder
);
91 ui
->tableTreeWidget
->setStyleSheet("QTreeView::item:hover{background-color:lightyellow; color:black;}");
93 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionMark_Unmark_Cell
);
94 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionMark_Unmark_Row
);
95 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionCopy_Cell
);
96 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionCopy_Rows
);
97 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionCopy_All
);
98 context_menu_
.addActions(QList
<QAction
*>() << ui
->actionSave_as_image
);
100 tapinfo_
.tap_packet
= tapPacket
;
101 tapinfo_
.tap_reset
= tapReset
;
104 registerTapListener("btatt.handles", &tapinfo_
, NULL
,
106 btatt_handle_tap_reset
,
107 btatt_handle_tap_packet
,
111 cap_file_
.retapPackets();
115 BluetoothAttServerAttributesDialog::~BluetoothAttServerAttributesDialog()
121 void BluetoothAttServerAttributesDialog::captureFileClosed()
123 ui
->interfaceComboBox
->setEnabled(false);
124 ui
->deviceComboBox
->setEnabled(false);
125 ui
->removeDuplicatesCheckBox
->setEnabled(false);
127 WiresharkDialog::captureFileClosed();
131 void BluetoothAttServerAttributesDialog::changeEvent(QEvent
*event
)
135 switch (event
->type())
137 case QEvent::LanguageChange
:
138 ui
->retranslateUi(this);
144 QDialog::changeEvent(event
);
148 void BluetoothAttServerAttributesDialog::keyPressEvent(QKeyEvent
*event
)
150 /* NOTE: Do nothing*, but in real it "takes focus" from button_box so allow user
151 * to use Enter button to jump to frame from tree widget */
152 /* * - reimplement shortcuts from contex menu */
154 if (event
->modifiers() & Qt::ControlModifier
&& event
->key()== Qt::Key_M
)
155 on_actionMark_Unmark_Row_triggered();
159 void BluetoothAttServerAttributesDialog::tableContextMenu(const QPoint
&pos
)
161 context_menu_
.popup(ui
->tableTreeWidget
->viewport()->mapToGlobal(pos
));
165 void BluetoothAttServerAttributesDialog::on_actionMark_Unmark_Cell_triggered()
167 QTreeWidgetItem
*current_item
= ui
->tableTreeWidget
->currentItem();
174 if (current_item
->background(ui
->tableTreeWidget
->currentColumn()) == QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_bg
))) {
178 fg
= QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_fg
));
179 bg
= QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_bg
));
182 current_item
->setForeground(ui
->tableTreeWidget
->currentColumn(), fg
);
183 current_item
->setBackground(ui
->tableTreeWidget
->currentColumn(), bg
);
187 void BluetoothAttServerAttributesDialog::on_actionMark_Unmark_Row_triggered()
189 QTreeWidgetItem
*current_item
= ui
->tableTreeWidget
->currentItem();
195 bool is_marked
= true;
197 for (int i
= 0; i
< ui
->tableTreeWidget
->columnCount(); i
+= 1) {
198 if (current_item
->background(i
) != QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_bg
)))
206 fg
= QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_fg
));
207 bg
= QBrush(ColorUtils::fromColorT(&prefs
.gui_marked_bg
));
210 for (int i
= 0; i
< ui
->tableTreeWidget
->columnCount(); i
+= 1) {
211 current_item
->setForeground(i
, fg
);
212 current_item
->setBackground(i
, bg
);
217 void BluetoothAttServerAttributesDialog::on_actionCopy_Cell_triggered()
219 QTreeWidgetItem
*current_item
= ui
->tableTreeWidget
->currentItem();
223 QClipboard
*clipboard
= QApplication::clipboard();
226 copy
= QString(current_item
->text(ui
->tableTreeWidget
->currentColumn()));
228 clipboard
->setText(copy
);
232 void BluetoothAttServerAttributesDialog::on_actionCopy_Rows_triggered()
234 QClipboard
*clipboard
= QApplication::clipboard();
236 QList
<QTreeWidgetItem
*> items
;
237 QList
<QTreeWidgetItem
*>::iterator i_item
;
239 items
= ui
->tableTreeWidget
->selectedItems();
241 for (i_item
= items
.begin(); i_item
!= items
.end(); ++i_item
) {
242 copy
+= QStringLiteral("%1 %2 %3\n")
243 .arg((*i_item
)->text(column_number_handle
), -6)
244 .arg((*i_item
)->text(column_number_uuid
), -32)
245 .arg((*i_item
)->text(column_number_uuid_name
));
249 clipboard
->setText(copy
);
252 void BluetoothAttServerAttributesDialog::tapReset(void *tapinfo_ptr
)
254 tapinfo_t
*tapinfo
= (tapinfo_t
*) tapinfo_ptr
;
255 BluetoothAttServerAttributesDialog
*bluetooth_att_server_attributes_dialog
= static_cast<BluetoothAttServerAttributesDialog
*>(tapinfo
->ui
);
258 bluetooth_att_server_attributes_dialog
->ui
->tableTreeWidget
->clear();
262 tap_packet_status
BluetoothAttServerAttributesDialog::tapPacket(void *tapinfo_ptr
, packet_info
*pinfo
, epan_dissect_t
*, const void *data
, tap_flags_t
)
264 tapinfo_t
*tapinfo
= static_cast<tapinfo_t
*>(tapinfo_ptr
);
265 BluetoothAttServerAttributesDialog
*dialog
= static_cast<BluetoothAttServerAttributesDialog
*>(tapinfo
->ui
);
266 tap_handles_t
*tap_handles
= static_cast<tap_handles_t
*>(const_cast<void *>(data
));
272 if (dialog
->file_closed_
)
273 return TAP_PACKET_DONT_REDRAW
;
275 if (pinfo
->rec
->rec_type
!= REC_TYPE_PACKET
)
276 return TAP_PACKET_DONT_REDRAW
;
278 if (pinfo
->rec
->presence_flags
& WTAP_HAS_INTERFACE_ID
) {
280 const char *interface_name
;
282 unsigned section_number
= pinfo
->rec
->presence_flags
& WTAP_HAS_SECTION_NUMBER
? pinfo
->rec
->section_number
: 0;
283 interface_name
= epan_get_interface_name(pinfo
->epan
, pinfo
->rec
->rec_header
.packet_header
.interface_id
, section_number
);
284 interface
= wmem_strdup_printf(pinfo
->pool
, "%u: %s", pinfo
->rec
->rec_header
.packet_header
.interface_id
, interface_name
);
286 if (dialog
->ui
->interfaceComboBox
->findText(interface
) == -1)
287 dialog
->ui
->interfaceComboBox
->addItem(interface
);
289 if (interface
&& dialog
->ui
->interfaceComboBox
->currentIndex() > 0) {
290 if (dialog
->ui
->interfaceComboBox
->currentText() != interface
)
291 return TAP_PACKET_REDRAW
;
295 if (pinfo
->p2p_dir
== P2P_DIR_SENT
|| pinfo
->p2p_dir
== P2P_DIR_RECV
)
296 addr
= address_to_str(pinfo
->pool
, &pinfo
->src
);
298 if (addr
&& dialog
->ui
->deviceComboBox
->findText(addr
) == -1) {
299 dialog
->ui
->deviceComboBox
->addItem(addr
);
302 if (addr
&& dialog
->ui
->deviceComboBox
->currentIndex() > 0) {
303 if (dialog
->ui
->deviceComboBox
->currentText() != addr
)
304 return TAP_PACKET_REDRAW
;
307 handle
= QStringLiteral("0x%1").arg(tap_handles
->handle
, 4, 16, QChar('0'));
308 uuid
= QString(print_numeric_bluetooth_uuid(pinfo
->pool
, &tap_handles
->uuid
));
309 uuid_name
= QString(print_bluetooth_uuid(pinfo
->pool
, &tap_handles
->uuid
));
311 if (dialog
->ui
->removeDuplicatesCheckBox
->checkState() == Qt::Checked
) {
312 QTreeWidgetItem
*item
= item_with_handle_get(dialog
->ui
->tableTreeWidget
,
313 tap_handles
->handle
);
316 if (item
->text(column_number_handle
) == handle
&&
317 item
->text(column_number_uuid
) == uuid
&&
318 item
->text(column_number_uuid_name
) == uuid_name
)
319 return TAP_PACKET_REDRAW
;
323 QTreeWidgetItem
*parent
= NULL
;
325 if (tap_handles
->attribute_type
== ATTRIBUTE_TYPE_SERVICE
) {
326 /* Service declarations are the top level items */
327 parent
= dialog
->ui
->tableTreeWidget
->invisibleRootItem();
328 } else if ((tap_handles
->uuid
.bt_uuid
== UUID_GATT_INCLUDE_DECLARATION
) ||
329 (tap_handles
->uuid
.bt_uuid
== UUID_GATT_CHARACTERISTIC_DECLARATION
)) {
330 /* Characteristic and include declarations are part of services. */
331 parent
= item_with_handle_get(dialog
->ui
->tableTreeWidget
,
332 tap_handles
->service_handle
);
334 /* Each characteristic may have several attributes. */
335 parent
= item_with_handle_get(dialog
->ui
->tableTreeWidget
,
336 tap_handles
->char_decl_handle
);
340 QTreeWidgetItem
*item
= new QTreeWidgetItem(parent
);
342 item
->setText(column_number_handle
, handle
);
343 item
->setText(column_number_uuid
, uuid
);
344 item
->setText(column_number_uuid_name
, uuid_name
);
345 item
->setData(0, Qt::UserRole
, QVariant::fromValue(pinfo
->num
));
346 item
->setData(1, Qt::UserRole
, QVariant::fromValue(tap_handles
->handle
));
348 parent
->setExpanded(true);
350 /* Do not insert items without a known parent into the tree.
351 * The parent will likely be found later.
355 for (int i
= 0; i
< dialog
->ui
->tableTreeWidget
->columnCount(); i
++) {
356 dialog
->ui
->tableTreeWidget
->resizeColumnToContents(i
);
359 return TAP_PACKET_REDRAW
;
362 void BluetoothAttServerAttributesDialog::interfaceCurrentIndexChanged(int)
364 cap_file_
.retapPackets();
368 void BluetoothAttServerAttributesDialog::deviceCurrentIndexChanged(int)
370 cap_file_
.retapPackets();
373 #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
374 void BluetoothAttServerAttributesDialog::removeDuplicatesStateChanged(Qt::CheckState
)
376 void BluetoothAttServerAttributesDialog::removeDuplicatesStateChanged(int)
379 cap_file_
.retapPackets();
384 void BluetoothAttServerAttributesDialog::on_tableTreeWidget_itemActivated(QTreeWidgetItem
*item
, int)
389 uint32_t frame_number
= item
->data(0, Qt::UserRole
).value
<uint32_t>();
391 emit
goToPacket(frame_number
);
395 void BluetoothAttServerAttributesDialog::on_actionCopy_All_triggered()
397 QClipboard
*clipboard
= QApplication::clipboard();
399 QTreeWidgetItemIterator
i_item(ui
->tableTreeWidget
);
401 copy
= QStringLiteral("%1 %2 %3\n")
402 .arg(ui
->tableTreeWidget
->headerItem()->text(column_number_handle
), -6)
403 .arg(ui
->tableTreeWidget
->headerItem()->text(column_number_uuid
), -32)
404 .arg(ui
->tableTreeWidget
->headerItem()->text(column_number_uuid_name
));
407 QTreeWidgetItem
*item
= static_cast<QTreeWidgetItem
*>(*i_item
);
408 copy
+= QStringLiteral("%1 %2 %3\n")
409 .arg(item
->text(column_number_handle
), -6)
410 .arg(item
->text(column_number_uuid
), -32)
411 .arg(item
->text(column_number_uuid_name
));
415 clipboard
->setText(copy
);
418 void BluetoothAttServerAttributesDialog::on_actionSave_as_image_triggered()
422 QString fileName
= WiresharkFileDialog::getSaveFileName(this, tr("Save Table Image"),
423 "att_server_attributes_table.png",
424 tr("PNG Image (*.png)"));
426 if (fileName
.isEmpty()) return;
428 image
= ui
->tableTreeWidget
->grab();
429 image
.save(fileName
, "PNG");
432 void BluetoothAttServerAttributesDialog::on_buttonBox_clicked(QAbstractButton
*)
434 /* if (button == foo_button_) */