Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion scapy/layers/bluetooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2720,12 +2720,72 @@ class HCI_Event_Vendor(Packet):

Bluetooth Core 5.4, Vol 4, Part E, section 5.4.4 reserves 0xFF for
vendor-specific debugging events; the format of the parameters is
vendor-defined, so the data is exposed as a raw byte string.
vendor-defined, so by default the parameters are exposed as a raw ``data``
byte string.

Because several vendors reuse this one event code with incompatible
parameter layouts, a vendor can register a payload handler with
:meth:`register_handler`. Each handler provides a ``check(body)`` that
recognises its own parameter layout (typically a fixed leading subcode).
When a registered check matches, the body is dissected by that vendor's own
layer as the payload, so the generic event is preserved and several vendor
contribs coexist. When no check claims the body it stays in ``data``,
exactly as before.
"""
name = "HCI_Vendor_Specific"
fields_desc = [StrLenField("data", b"",
length_from=lambda pkt: pkt.underlayer.len)]

registered_handlers = {}

@classmethod
def register_handler(cls, handler_cls, check=None):
"""
Registers a vendor handler for the ``code=0xFF`` event body.

This event is shared across vendors with incompatible parameter
layouts, so a contrib cannot simply bind its layer to it. Instead each
vendor handler declares a ``check`` that returns True only for its own
body (typically by testing a fixed leading subcode) and registers
itself here. The first registered check that accepts a body wins, so
checks should be as specific as possible. Re-registering the same
``handler_cls`` replaces its previous entry, so reloading a contrib is
idempotent.

:param Type[scapy.packet.Packet] handler_cls:
A reference to a Packet subclass to register as a payload.
:param Callable[[bytes], bool] check:
(optional) callable used to decide whether a body should be
associated with this handler. If not supplied,
``handler_cls.check`` is used instead.
:raises TypeError: If ``check`` is not specified,
and ``handler_cls.check`` is not implemented.
"""
if check is None:
if hasattr(handler_cls, "check"):
check = handler_cls.check
else:
raise TypeError("check not specified, and {} has no "
"attribute check".format(handler_cls))

cls.registered_handlers[handler_cls] = check

def default_payload_class(self, payload):
for handler_cls, check in (
HCI_Event_Vendor.registered_handlers.items()
):
if check(payload):
return handler_cls

return Packet.default_payload_class(self, payload)

def do_dissect(self, s):
if s and self.default_payload_class(s) is not conf.raw_layer:
self.raw_packet_cache = None
self.explicit = 1
return s
return super(HCI_Event_Vendor, self).do_dissect(s)


class HCI_Event_LE_Meta(Packet):
"""
Expand Down
82 changes: 82 additions & 0 deletions test/scapy/layers/bluetooth.uts
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,88 @@ assert parsed[HCI_Event_Hdr].len == 4
assert parsed[HCI_Event_Vendor].data == b"\xde\xad\xbe\xef"


+ HCI_Event_Vendor handler registration (register_handler / check dispatch)

= register_handler routes a recognised body to the vendor handler layer
# The 0xff event is shared across vendors, so a contrib registers a handler
# whose check() recognises its own body (here a leading 0xa5 subcode). A
# matching body is dissected by that handler as the payload, and the generic
# event's ``data`` stays empty. Save the registry first so it can be restored.
_saved_vendor_handlers = dict(HCI_Event_Vendor.registered_handlers)

class _VendorHandlerA(Packet):
name = "Vendor Handler A"
fields_desc = [ByteField("subcode", 0), ByteField("value", 0)]
@classmethod
def check(cls, body):
return len(body) >= 1 and body[0] == 0xa5

HCI_Event_Vendor.register_handler(_VendorHandlerA)
assert _VendorHandlerA in HCI_Event_Vendor.registered_handlers

evt = HCI_Hdr(hex_bytes("04" "ff" "02" "a542"))
assert HCI_Event_Vendor in evt
assert _VendorHandlerA in evt
assert evt[HCI_Event_Vendor].data == b"" # body released to the payload
assert evt[_VendorHandlerA].subcode == 0xa5
assert evt[_VendorHandlerA].value == 0x42
assert raw(evt) == hex_bytes("04" "ff" "02" "a542") # round-trips unchanged

= register_handler accepts an explicit check callable
# A handler need not define check() itself; the callable may be supplied.
class _VendorHandlerB(Packet):
name = "Vendor Handler B"
fields_desc = [StrField("body", b"")]

HCI_Event_Vendor.register_handler(_VendorHandlerB, check=lambda body: body[:1] == b"\x5a")
evt = HCI_Hdr(hex_bytes("04" "ff" "03" "5abeef"))
assert _VendorHandlerB in evt
assert _VendorHandlerA not in evt # A's check does not claim it
assert evt[HCI_Event_Vendor].data == b""
assert evt[_VendorHandlerB].body == b"\x5a\xbe\xef"

= Two registered handlers dispatch independently by content
# With both handlers loaded, each body is routed to the handler that claims it.
evt_a = HCI_Hdr(hex_bytes("04" "ff" "02" "a542"))
assert _VendorHandlerA in evt_a and _VendorHandlerB not in evt_a
evt_b = HCI_Hdr(hex_bytes("04" "ff" "03" "5abeef"))
assert _VendorHandlerB in evt_b and _VendorHandlerA not in evt_b

= An unrecognised 0xff body still falls back to the raw data field
# No registered check matches, so the historical raw-``data`` behaviour holds.
evt = HCI_Hdr(hex_bytes("04" "ff" "02" "0102"))
assert HCI_Event_Vendor in evt
assert _VendorHandlerA not in evt
assert _VendorHandlerB not in evt
assert evt[HCI_Event_Vendor].data == b"\x01\x02"
assert raw(evt) == hex_bytes("04" "ff" "02" "0102")

= register_handler is idempotent when the same handler is re-registered
# Keyed by handler class, so reloading a contrib does not duplicate entries.
_n = len(HCI_Event_Vendor.registered_handlers)
HCI_Event_Vendor.register_handler(_VendorHandlerA)
assert len(HCI_Event_Vendor.registered_handlers) == _n

= register_handler raises TypeError when no check is available
class _VendorNoCheck(Packet):
name = "Vendor No Check"
fields_desc = []

try:
HCI_Event_Vendor.register_handler(_VendorNoCheck)
assert False, "expected TypeError for a handler without a check"
except TypeError:
pass

assert _VendorNoCheck not in HCI_Event_Vendor.registered_handlers

= Restore the HCI_Event_Vendor handler registry
# Undo the test registrations so later tests see a clean registry.
HCI_Event_Vendor.registered_handlers.clear()
HCI_Event_Vendor.registered_handlers.update(_saved_vendor_handlers)
assert HCI_Event_Vendor.registered_handlers == _saved_vendor_handlers


+ Bluetooth LE Advertising / Scan Response Data Parsing
= Parse EIR_IncompleteList32BitServiceUUIDs

Expand Down
Loading