diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index f5259305143..addafd87704 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -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): """ diff --git a/test/scapy/layers/bluetooth.uts b/test/scapy/layers/bluetooth.uts index 0aafc3fcb0a..a7475941d24 100644 --- a/test/scapy/layers/bluetooth.uts +++ b/test/scapy/layers/bluetooth.uts @@ -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