Skip to content

Commit

Permalink
feat: add cython extension for messages and signature
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Oct 4, 2022
1 parent 254f9b3 commit 76067f9
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 40 deletions.
2 changes: 2 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 1 addition & 3 deletions src/dbus_fast/_private/marshaller.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -44,4 +42,4 @@ cdef class Marshaller:
@cython.locals(
offset=cython.ulong,
)
cpdef _construct_buffer(self)
cdef _construct_buffer(self)
14 changes: 8 additions & 6 deletions src/dbus_fast/_private/unmarshaller.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)

Expand All @@ -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)
16 changes: 8 additions & 8 deletions src/dbus_fast/_private/unmarshaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -252,29 +252,29 @@ 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
# read terminating '\0' byte as well (str_length + 1)
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 [
Expand Down
48 changes: 48 additions & 0 deletions src/dbus_fast/message.pxd
Original file line number Diff line number Diff line change
@@ -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)
60 changes: 37 additions & 23 deletions src/dbus_fast/message.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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

Expand Down
18 changes: 18 additions & 0 deletions src/dbus_fast/signature.pxd
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/dbus_fast/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down

0 comments on commit 76067f9

Please sign in to comment.