From 76067f913beec0247309007705b12a6c620470d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Oct 2022 20:57:12 -1000 Subject: [PATCH] feat: add cython extension for messages and signature --- build.py | 2 + src/dbus_fast/_private/marshaller.pxd | 4 +- src/dbus_fast/_private/unmarshaller.pxd | 14 +++--- src/dbus_fast/_private/unmarshaller.py | 16 +++---- src/dbus_fast/message.pxd | 48 ++++++++++++++++++++ src/dbus_fast/message.py | 60 +++++++++++++++---------- src/dbus_fast/signature.pxd | 18 ++++++++ src/dbus_fast/signature.py | 2 + 8 files changed, 124 insertions(+), 40 deletions(-) create mode 100644 src/dbus_fast/message.pxd create mode 100644 src/dbus_fast/signature.pxd diff --git a/build.py b/build.py index 61c4b2bd..c9f91313 100644 --- a/build.py +++ b/build.py @@ -22,6 +22,8 @@ def build(setup_kwargs): dict( ext_modules=cythonize( [ + "src/dbus_fast/message.py", + "src/dbus_fast/signature.py", "src/dbus_fast/unpack.py", "src/dbus_fast/_private/marshaller.py", "src/dbus_fast/_private/unmarshaller.py", diff --git a/src/dbus_fast/_private/marshaller.pxd b/src/dbus_fast/_private/marshaller.pxd index 0f089018..5e99a886 100644 --- a/src/dbus_fast/_private/marshaller.pxd +++ b/src/dbus_fast/_private/marshaller.pxd @@ -9,13 +9,11 @@ cdef class Marshaller: cdef bytearray _buf cdef object body - @cython.locals( offset=cython.ulong, ) cpdef int align(self, unsigned long n) - @cython.locals( signature_len=cython.uint, written=cython.uint, @@ -44,4 +42,4 @@ cdef class Marshaller: @cython.locals( offset=cython.ulong, ) - cpdef _construct_buffer(self) + cdef _construct_buffer(self) diff --git a/src/dbus_fast/_private/unmarshaller.pxd b/src/dbus_fast/_private/unmarshaller.pxd index 4402ea06..e34fd421 100644 --- a/src/dbus_fast/_private/unmarshaller.pxd +++ b/src/dbus_fast/_private/unmarshaller.pxd @@ -33,18 +33,20 @@ cdef class Unmarshaller: cpdef reset(self) + cdef read_sock(self, unsigned long length) + @cython.locals( start_len=cython.ulong, missing_bytes=cython.ulong ) - cpdef read_to_pos(self, unsigned long pos) + cdef read_to_pos(self, unsigned long pos) cpdef read_uint32_cast(self, object type_) @cython.locals( buf_bytes=cython.bytearray, ) - cpdef read_string_cast(self, type_ = *) + cpdef read_string_cast(self, object type_) @cython.locals( beginning_pos=cython.ulong, @@ -56,16 +58,16 @@ cdef class Unmarshaller: o=cython.ulong, signature_len=cython.uint, ) - cpdef read_signature(self, type_ = *) + cpdef read_signature(self, object type_) @cython.locals( endian=cython.uint, protocol_version=cython.uint, can_cast=cython.bint ) - cpdef _read_header(self) + cdef _read_header(self) - cpdef _read_body(self) + cdef _read_body(self) cpdef unmarshall(self) @@ -74,4 +76,4 @@ cdef class Unmarshaller: o=cython.ulong, signature_len=cython.uint, ) - cpdef header_fields(self, unsigned int header_length) + cdef header_fields(self, unsigned int header_length) diff --git a/src/dbus_fast/_private/unmarshaller.py b/src/dbus_fast/_private/unmarshaller.py index 3ed53d21..43e61713 100644 --- a/src/dbus_fast/_private/unmarshaller.py +++ b/src/dbus_fast/_private/unmarshaller.py @@ -236,14 +236,14 @@ def read_to_pos(self, pos) -> None: if len(data) + start_len != pos: raise MarshallerStreamEndError() - def read_uint32_cast(self, signature: SignatureType) -> Any: + def read_uint32_cast(self, type_: SignatureType) -> Any: self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align return self._view[self._pos - UINT32_SIZE : self._pos].cast(UINT32_CAST)[0] - def read_boolean(self, type_=None) -> bool: + def read_boolean(self, type_: SignatureType) -> bool: return bool(self._readers[UINT32_SIGNATURE.token](self, UINT32_SIGNATURE)) - def read_string_cast(self, type_=None) -> str: + def read_string_cast(self, type_: SignatureType) -> str: """Read a string using cast.""" self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align str_start = self._pos @@ -252,7 +252,7 @@ def read_string_cast(self, type_=None) -> str: self._pos += self._view[start_pos : self._pos].cast(UINT32_CAST)[0] + 1 return self._buf[str_start : self._pos - 1].decode() - def read_string_unpack(self, type_=None) -> str: + def read_string_unpack(self, type_: SignatureType) -> str: """Read a string using unpack.""" self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align str_start = self._pos @@ -260,21 +260,21 @@ def read_string_unpack(self, type_=None) -> str: self._pos += self._uint32_unpack(self._view, str_start - UINT32_SIZE)[0] + 1 return self._buf[str_start : self._pos - 1].decode() - def read_signature(self, type_=None) -> str: + def read_signature(self, type_: SignatureType) -> str: signature_len = self._view[self._pos] # byte o = self._pos + 1 # read terminating '\0' byte as well (str_length + 1) self._pos = o + signature_len + 1 return self._buf[o : o + signature_len].decode() - def read_variant(self, type_=None) -> Variant: - tree = SignatureTree._get(self.read_signature()) + def read_variant(self, type_: SignatureType) -> Variant: + tree = SignatureTree._get(self.read_signature(type_)) # verify in Variant is only useful on construction not unmarshalling return Variant( tree, self._readers[tree.types[0].token](self, tree.types[0]), verify=False ) - def read_struct(self, type_=None) -> List[Any]: + def read_struct(self, type_: SignatureType) -> List[Any]: self._pos += -self._pos & 7 # align 8 readers = self._readers return [ diff --git a/src/dbus_fast/message.pxd b/src/dbus_fast/message.pxd new file mode 100644 index 00000000..c423ebcb --- /dev/null +++ b/src/dbus_fast/message.pxd @@ -0,0 +1,48 @@ +"""cdefs for message.py""" + +import cython + + +cdef unsigned int LITTLE_ENDIAN +cdef unsigned int BIG_ENDIAN +cdef unsigned int PROTOCOL_VERSION + +cdef unsigned int HEADER_PATH +cdef unsigned int HEADER_INTERFACE +cdef unsigned int HEADER_MEMBER +cdef unsigned int HEADER_ERROR_NAME +cdef unsigned int HEADER_REPLY_SERIAL +cdef unsigned int HEADER_DESTINATION +cdef unsigned int HEADER_SIGNATURE +cdef unsigned int HEADER_UNIX_FDS + +cdef unsigned int METHOD_CALL +cdef unsigned int SIGNAL +cdef unsigned int ERROR +cdef unsigned int METHOD_RETURN + + +cdef class Message: + + cdef public str destination + cdef public str path + cdef public str interface + cdef public str member + cdef public object message_type + cdef public object flags + cdef public str error_name + cdef public object reply_serial + cdef public str sender + cdef public list unix_fds + cdef public str signature + cdef public object signature_tree + cdef public list body + cdef public object serial + + @cython.locals( + fields=cython.list, + header_body=cython.list, + ) + cpdef _marshall(self, negotiate_unix_fd = *) + + cpdef _matches(self, dict matcher) diff --git a/src/dbus_fast/message.py b/src/dbus_fast/message.py index 270453b9..52bc3e04 100644 --- a/src/dbus_fast/message.py +++ b/src/dbus_fast/message.py @@ -1,4 +1,4 @@ -from typing import Any, List, Union +from typing import Any, Dict, List, Union from ._private.constants import LITTLE_ENDIAN, PROTOCOL_VERSION, HeaderField from ._private.marshaller import Marshaller @@ -12,13 +12,6 @@ assert_object_path_valid, ) -REQUIRED_FIELDS = { - MessageType.METHOD_CALL: ("path", "member"), - MessageType.SIGNAL: ("path", "member", "interface"), - MessageType.ERROR: ("error_name", "reply_serial"), - MessageType.METHOD_RETURN: ("reply_serial",), -} - HEADER_PATH = HeaderField.PATH.value HEADER_INTERFACE = HeaderField.INTERFACE.value HEADER_MEMBER = HeaderField.MEMBER.value @@ -28,6 +21,11 @@ HEADER_SIGNATURE = HeaderField.SIGNATURE.value HEADER_UNIX_FDS = HeaderField.UNIX_FDS.value +METHOD_CALL = MessageType.METHOD_CALL.value +SIGNAL = MessageType.SIGNAL.value +ERROR = MessageType.ERROR.value +METHOD_RETURN = MessageType.METHOD_RETURN.value + class Message: """A class for sending and receiving messages through the @@ -135,23 +133,39 @@ def __init__( if not validate: return - if self.destination is not None: - assert_bus_name_valid(self.destination) - if self.interface is not None: - assert_interface_name_valid(self.interface) - if self.path is not None: - assert_object_path_valid(self.path) - if self.member is not None: - assert_member_name_valid(self.member) + if destination is not None: + assert_bus_name_valid(destination) + if interface is not None: + assert_interface_name_valid(interface) + if path is not None: + assert_object_path_valid(path) + if member is not None: + assert_member_name_valid(member) if self.error_name is not None: assert_interface_name_valid(self.error_name) - required_fields = REQUIRED_FIELDS.get(self.message_type) - if not required_fields: + if message_type == METHOD_CALL: + if not path: + raise InvalidMessageError(f"missing required field: path") + if not member: + raise InvalidMessageError(f"missing required field: member") + elif message_type == SIGNAL: + if not path: + raise InvalidMessageError(f"missing required field: path") + if not member: + raise InvalidMessageError(f"missing required field: member") + if not interface: + raise InvalidMessageError(f"missing required field: interface") + elif message_type == ERROR: + if not error_name: + raise InvalidMessageError(f"missing required field: error_name") + if not reply_serial: + raise InvalidMessageError(f"missing required field: reply_serial") + elif message_type == METHOD_RETURN: + if not reply_serial: + raise InvalidMessageError(f"missing required field: reply_serial") + else: raise InvalidMessageError(f"got unknown message type: {self.message_type}") - for field in required_fields: - if not getattr(self, field): - raise InvalidMessageError(f"missing required field: {field}") @staticmethod def new_error(msg: "Message", error_name: str, error_text: str) -> "Message": @@ -255,8 +269,8 @@ def new_signal( unix_fds=unix_fds, ) - def _matches(self, **kwargs): - for attr, val in kwargs.items(): + def _matches(self, matchers: Dict[str, Any]) -> bool: + for attr, val in matchers.items(): if getattr(self, attr) != val: return False diff --git a/src/dbus_fast/signature.pxd b/src/dbus_fast/signature.pxd new file mode 100644 index 00000000..402949d9 --- /dev/null +++ b/src/dbus_fast/signature.pxd @@ -0,0 +1,18 @@ +"""cdefs for signature.py""" + +import cython + + +cdef class SignatureType: + + cdef public str token + cdef public list children + cdef str _signature + + cdef _collapse(self) + + +cdef class SignatureTree: + + cdef public str signature + cdef public list types diff --git a/src/dbus_fast/signature.py b/src/dbus_fast/signature.py index a9081b6f..6b79442a 100644 --- a/src/dbus_fast/signature.py +++ b/src/dbus_fast/signature.py @@ -330,6 +330,8 @@ class SignatureTree: :class:`InvalidSignatureError` if the given signature is not valid. """ + __slots__ = ("signature", "types") + @staticmethod @lru_cache(maxsize=None) def _get(signature: str = "") -> "SignatureTree":