diff --git a/bench/unmarshall_interfaces_added.py b/bench/unmarshall_interfaces_added.py new file mode 100644 index 0000000..40fd8c5 --- /dev/null +++ b/bench/unmarshall_interfaces_added.py @@ -0,0 +1,40 @@ +import io +import timeit + +from dbus_fast._private.unmarshaller import Unmarshaller + +# cythonize -X language_level=3 -a -i src/dbus_fast/_private/unmarshaller.py + + +bluez_interfaces_added_message = ( + b'l\4\1\1\240\2\0\0\227\272\23\0u\0\0\0\1\1o\0\1\0\0\0/\0\0\0\0\0\0\0\2\1s\0"\0\0\0' + b"org.freedesktop.DBus.ObjectManager\0\0\0\0\0\0\3\1s\0\17\0\0\0InterfacesAdded\0\10" + b"\1g\0\noa{sa{sv}}\0\7\1s\0\4\0\0\0:1.4\0\0\0\0%\0\0\0/org/bluez/hci1/dev_58_2D_34" + b"_60_26_36\0\0\0p\2\0\0#\0\0\0org.freedesktop.DBus.Introspectable\0\0\0\0\0\0\0\0\0" + b"\21\0\0\0org.bluez.Device1\0\0\0\364\1\0\0\0\0\0\0\7\0\0\0Address\0\1s\0\0\21\0\0" + b"\00058:2D:34:60:26:36\0\0\0\v\0\0\0AddressType\0\1s\0\0\6\0\0\0public\0\0\4\0\0\0" + b"Name\0\1s\0\33\0\0\0Qingping Door/Window Sensor\0\0\0\0\0\5\0\0\0Alias\0\1s\0\0\0" + b"\0\33\0\0\0Qingping Door/Window Sensor\0\6\0\0\0Paired\0\1b\0\0\0\0\0\0\0\0\0\0\0" + b"\7\0\0\0Trusted\0\1b\0\0\0\0\0\0\0\0\0\0\7\0\0\0Blocked\0\1b\0\0\0\0\0\0\0\0\0\0\r" + b"\0\0\0LegacyPairing\0\1b\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0RSSI\0\1n\0\316\377\0\0\t" + b"\0\0\0Connected\0\1b\0\0\0\0\0\0\0\0\5\0\0\0UUIDs\0\2as\0\0\0\0\0\0\0\0\0\0\0\7\0" + b"\0\0Adapter\0\1o\0\0\17\0\0\0/org/bluez/hci1\0\0\0\0\0\v\0\0\0ServiceData\0\5a{sv}" + b"\0\0@\0\0\0\0\0\0\0$\0\0\0000000fe95-0000-1000-8000-00805f9b34fb\0\2ay\0\0\0\0\f\0" + b"\0\0000X\326\3\0026&`4-X\10\20\0\0\0ServicesResolved\0\1b\0\0\0\0\0\0\0\0\0\37\0\0" + b"\0org.freedesktop.DBus.Properties\0\0\0\0\0" +) + + +stream = io.BytesIO(bluez_interfaces_added_message) +unmarshaller = Unmarshaller(stream) + + +def unmarshall_interfaces_added_message(): + stream.seek(0) + unmarshaller.reset() + unmarshaller.unmarshall() + + +count = 3000000 +time = timeit.Timer(unmarshall_interfaces_added_message).timeit(count) +print(f"Unmarshalling {count} bluetooth InterfacesAdded messages took {time} seconds") diff --git a/src/dbus_fast/_private/unmarshaller.pxd b/src/dbus_fast/_private/unmarshaller.pxd index ad7f680..2cf713e 100644 --- a/src/dbus_fast/_private/unmarshaller.pxd +++ b/src/dbus_fast/_private/unmarshaller.pxd @@ -41,11 +41,19 @@ cdef object MESSAGE_FLAG_MAP cdef object HEADER_MESSAGE_ARG_NAME cdef object SIGNATURE_TREE_EMPTY +cdef object SIGNATURE_TREE_B cdef object SIGNATURE_TREE_N +cdef object SIGNATURE_TREE_O cdef object SIGNATURE_TREE_S +cdef object SIGNATURE_TREE_AS +cdef object SIGNATURE_TREE_AS_TYPES_0 +cdef object SIGNATURE_TREE_A_SV +cdef object SIGNATURE_TREE_A_SV_TYPES_0 cdef object SIGNATURE_TREE_SA_SV_AS cdef object SIGNATURE_TREE_SA_SV_AS_TYPES_1 cdef object SIGNATURE_TREE_SA_SV_AS_TYPES_2 +cdef object SIGNATURE_TREE_OA_SA_SV +cdef object SIGNATURE_TREE_OA_SA_SV_TYPES_1 cdef object SIGNATURE_TREE_AY cdef object SIGNATURE_TREE_AY_TYPES_0 cdef object SIGNATURE_TREE_A_QV @@ -101,6 +109,10 @@ cdef class Unmarshaller: ) cdef _read_to_pos(self, unsigned long pos) + cpdef read_boolean(self, object type_) + + cdef bint _read_boolean(self) + cpdef read_uint32_unpack(self, object type_) cdef unsigned int _read_uint32_unpack(self) diff --git a/src/dbus_fast/_private/unmarshaller.py b/src/dbus_fast/_private/unmarshaller.py index b03dfd5..41a9bf6 100644 --- a/src/dbus_fast/_private/unmarshaller.py +++ b/src/dbus_fast/_private/unmarshaller.py @@ -59,10 +59,17 @@ HEADER_ARRAY_OF_STRUCT_SIGNATURE_POSITION = 12 SIGNATURE_TREE_EMPTY = get_signature_tree("") +SIGNATURE_TREE_B = get_signature_tree("b") SIGNATURE_TREE_N = get_signature_tree("n") SIGNATURE_TREE_S = get_signature_tree("s") +SIGNATURE_TREE_O = get_signature_tree("o") SIGNATURE_TREE_AY = get_signature_tree("ay") +SIGNATURE_TREE_AS = get_signature_tree("as") +SIGNATURE_TREE_AS_TYPES_0 = SIGNATURE_TREE_AS.types[0] +SIGNATURE_TREE_A_SV = get_signature_tree("a{sv}") +SIGNATURE_TREE_A_SV_TYPES_0 = SIGNATURE_TREE_A_SV.types[0] + SIGNATURE_TREE_AY_TYPES_0 = SIGNATURE_TREE_AY.types[0] SIGNATURE_TREE_A_QV = get_signature_tree("a{qv}") SIGNATURE_TREE_A_QV_TYPES_0 = SIGNATURE_TREE_A_QV.types[0] @@ -71,6 +78,9 @@ SIGNATURE_TREE_SA_SV_AS = get_signature_tree("sa{sv}as") SIGNATURE_TREE_SA_SV_AS_TYPES_1 = SIGNATURE_TREE_SA_SV_AS.types[1] SIGNATURE_TREE_SA_SV_AS_TYPES_2 = SIGNATURE_TREE_SA_SV_AS.types[2] +SIGNATURE_TREE_OA_SA_SV = get_signature_tree("oa{sa{sv}}") +SIGNATURE_TREE_OA_SA_SV_TYPES_1 = SIGNATURE_TREE_OA_SA_SV.types[1] + HEADER_MESSAGE_ARG_NAME = { 1: "path", 2: "interface", @@ -292,6 +302,9 @@ class Unmarshaller: return self._int16_unpack(self._buf, self._pos - INT16_SIZE)[0] def read_boolean(self, type_: SignatureType) -> bool: + return self._read_boolean() + + def _read_boolean(self) -> bool: return bool(self._read_uint32_unpack()) def read_string_unpack(self, type_: SignatureType) -> str: @@ -338,6 +351,22 @@ class Unmarshaller: self._read_array(SIGNATURE_TREE_A_QV_TYPES_0), False, ) + elif signature == "s": + return Variant(SIGNATURE_TREE_S, self._read_string_unpack(), False) + elif signature == "b": + return Variant(SIGNATURE_TREE_B, self._read_boolean(), False) + elif signature == "o": + return Variant(SIGNATURE_TREE_O, self._read_string_unpack(), False) + elif signature == "as": + return Variant( + SIGNATURE_TREE_AS, self._read_array(SIGNATURE_TREE_AS_TYPES_0), False + ) + elif signature == "a{sv}": + return Variant( + SIGNATURE_TREE_A_SV, + self._read_array(SIGNATURE_TREE_A_SV_TYPES_0), + False, + ) tree = get_signature_tree(signature) signature_type = tree.types[0] return Variant( @@ -395,20 +424,25 @@ class Unmarshaller: # Strings with variant values are the most common case # so we optimize for that by inlining the string reading # and the variant reading here - if child_1_token == "v": - if child_0_token in "os": - while self._pos - beginning_pos < array_length: - self._pos += -self._pos & 7 # align 8 - key = self._read_string_unpack() - result_dict[key] = self._read_variant() - elif child_0_token == "q": - while self._pos - beginning_pos < array_length: - self._pos += -self._pos & 7 # align 8 - key = self._read_uint16_unpack() - result_dict[key] = self._read_variant() + if child_0_token in "os" and child_1_token == "v": + while self._pos - beginning_pos < array_length: + self._pos += -self._pos & 7 # align 8 + key = self._read_string_unpack() + result_dict[key] = self._read_variant() + elif child_0_token == "q" and child_1_token == "v": + while self._pos - beginning_pos < array_length: + self._pos += -self._pos & 7 # align 8 + key = self._read_uint16_unpack() + result_dict[key] = self._read_variant() + elif child_0_token == "s" and child_1_token == "a": + while self._pos - beginning_pos < array_length: + self._pos += -self._pos & 7 # align 8 + key = self._read_string_unpack() + result_dict[key] = self._read_array(child_1) else: reader_1 = self._readers[child_1_token] reader_0 = self._readers[child_0_token] + while self._pos - beginning_pos < array_length: self._pos += -self._pos & 7 # align 8 key = reader_0(self, child_0) @@ -528,6 +562,12 @@ class Unmarshaller: self._read_array(SIGNATURE_TREE_SA_SV_AS_TYPES_1), self._read_array(SIGNATURE_TREE_SA_SV_AS_TYPES_2), ] + elif signature == "oa{sa{sv}}": + tree = SIGNATURE_TREE_OA_SA_SV + body = [ + self._read_string_unpack(), + self._read_array(SIGNATURE_TREE_OA_SA_SV_TYPES_1), + ] else: tree = get_signature_tree(signature) body = [self._readers[t.token](self, t) for t in tree.types] diff --git a/tests/test_marshaller.py b/tests/test_marshaller.py index abce89c..091e149 100644 --- a/tests/test_marshaller.py +++ b/tests/test_marshaller.py @@ -228,6 +228,99 @@ def test_unmarshall_bluez_message(): ] +def test_unmarshall_bluez_interfaces_added_message(): + bluez_interfaces_added_message = ( + b'l\4\1\1\240\2\0\0\227\272\23\0u\0\0\0\1\1o\0\1\0\0\0/\0\0\0\0\0\0\0\2\1s\0"\0\0\0' + b"org.freedesktop.DBus.ObjectManager\0\0\0\0\0\0\3\1s\0\17\0\0\0InterfacesAdded\0\10" + b"\1g\0\noa{sa{sv}}\0\7\1s\0\4\0\0\0:1.4\0\0\0\0%\0\0\0/org/bluez/hci1/dev_58_2D_34" + b"_60_26_36\0\0\0p\2\0\0#\0\0\0org.freedesktop.DBus.Introspectable\0\0\0\0\0\0\0\0\0" + b"\21\0\0\0org.bluez.Device1\0\0\0\364\1\0\0\0\0\0\0\7\0\0\0Address\0\1s\0\0\21\0\0" + b"\00058:2D:34:60:26:36\0\0\0\v\0\0\0AddressType\0\1s\0\0\6\0\0\0public\0\0\4\0\0\0" + b"Name\0\1s\0\33\0\0\0Qingping Door/Window Sensor\0\0\0\0\0\5\0\0\0Alias\0\1s\0\0\0" + b"\0\33\0\0\0Qingping Door/Window Sensor\0\6\0\0\0Paired\0\1b\0\0\0\0\0\0\0\0\0\0\0" + b"\7\0\0\0Trusted\0\1b\0\0\0\0\0\0\0\0\0\0\7\0\0\0Blocked\0\1b\0\0\0\0\0\0\0\0\0\0\r" + b"\0\0\0LegacyPairing\0\1b\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0RSSI\0\1n\0\316\377\0\0\t" + b"\0\0\0Connected\0\1b\0\0\0\0\0\0\0\0\5\0\0\0UUIDs\0\2as\0\0\0\0\0\0\0\0\0\0\0\7\0" + b"\0\0Adapter\0\1o\0\0\17\0\0\0/org/bluez/hci1\0\0\0\0\0\v\0\0\0ServiceData\0\5a{sv}" + b"\0\0@\0\0\0\0\0\0\0$\0\0\0000000fe95-0000-1000-8000-00805f9b34fb\0\2ay\0\0\0\0\f\0" + b"\0\0000X\326\3\0026&`4-X\10\20\0\0\0ServicesResolved\0\1b\0\0\0\0\0\0\0\0\0\37\0\0" + b"\0org.freedesktop.DBus.Properties\0\0\0\0\0" + ) + + stream = io.BytesIO(bluez_interfaces_added_message) + unmarshaller = Unmarshaller(stream) + assert unmarshaller.unmarshall() + message = unmarshaller.message + assert message is not None + assert message.body == [ + "/org/bluez/hci1/dev_58_2D_34_60_26_36", + { + "org.bluez.Device1": { + "Adapter": Variant("o", "/org/bluez/hci1"), + "Address": Variant("s", "58:2D:34:60:26:36"), + "AddressType": Variant("s", "public"), + "Alias": Variant("s", "Qingping Door/Window Sensor"), + "Blocked": Variant("b", False), + "Connected": Variant("b", False), + "LegacyPairing": Variant("b", False), + "Name": Variant("s", "Qingping Door/Window Sensor"), + "Paired": Variant("b", False), + "RSSI": Variant("n", -50), + "ServiceData": Variant( + "a{sv}", + { + "0000fe95-0000-1000-8000-00805f9b34fb": Variant( + "ay", bytearray(b"0X\xd6\x03\x026&`4-X\x08") + ) + }, + ), + "ServicesResolved": Variant("b", False), + "Trusted": Variant("b", False), + "UUIDs": Variant("as", []), + }, + "org.freedesktop.DBus.Introspectable": {}, + "org.freedesktop.DBus.Properties": {}, + }, + ] + assert message.sender == ":1.4" + assert message.path == "/" + assert message.interface == "org.freedesktop.DBus.ObjectManager" + assert message.member == "InterfacesAdded" + assert message.signature == "oa{sa{sv}}" + assert message.message_type == MessageType.SIGNAL + assert message.flags == MessageFlag.NO_REPLY_EXPECTED + assert message.serial == 1292951 + assert message.destination is None + unpacked = unpack_variants(message.body) + assert unpacked == [ + "/org/bluez/hci1/dev_58_2D_34_60_26_36", + { + "org.bluez.Device1": { + "Adapter": "/org/bluez/hci1", + "Address": "58:2D:34:60:26:36", + "AddressType": "public", + "Alias": "Qingping Door/Window Sensor", + "Blocked": False, + "Connected": False, + "LegacyPairing": False, + "Name": "Qingping Door/Window Sensor", + "Paired": False, + "RSSI": -50, + "ServiceData": { + "0000fe95-0000-1000-8000-00805f9b34fb": bytearray( + b"0X\xd6\x03" b"\x026&`" b"4-X\x08" + ) + }, + "ServicesResolved": False, + "Trusted": False, + "UUIDs": [], + }, + "org.freedesktop.DBus.Introspectable": {}, + "org.freedesktop.DBus.Properties": {}, + }, + ] + + def test_ay_buffer(): body = [bytes(10000)] msg = Message(path="/test", member="test", signature="ay", body=body)