feat: add cython extension for signature (#72)

This commit is contained in:
J. Nick Koston
2022-10-04 07:32:21 -10:00
committed by GitHub
parent 254f9b3f95
commit 0ad8801215
11 changed files with 76 additions and 53 deletions

View File

@@ -22,6 +22,7 @@ def build(setup_kwargs):
dict( dict(
ext_modules=cythonize( ext_modules=cythonize(
[ [
"src/dbus_fast/signature.py",
"src/dbus_fast/unpack.py", "src/dbus_fast/unpack.py",
"src/dbus_fast/_private/marshaller.py", "src/dbus_fast/_private/marshaller.py",
"src/dbus_fast/_private/unmarshaller.py", "src/dbus_fast/_private/unmarshaller.py",

View File

@@ -9,14 +9,13 @@ cdef class Marshaller:
cdef bytearray _buf cdef bytearray _buf
cdef object body cdef object body
@cython.locals( @cython.locals(
offset=cython.ulong, offset=cython.ulong,
) )
cpdef int align(self, unsigned long n) cpdef int align(self, unsigned long n)
@cython.locals( @cython.locals(
value_len=cython.uint,
signature_len=cython.uint, signature_len=cython.uint,
written=cython.uint, written=cython.uint,
) )
@@ -26,11 +25,13 @@ cdef class Marshaller:
array_len=cython.uint, array_len=cython.uint,
written=cython.uint, written=cython.uint,
array_len_packed=cython.bytes, array_len_packed=cython.bytes,
i=cython.uint,
) )
cpdef write_array(self, object array, object type) cpdef write_array(self, object array, object type)
@cython.locals( @cython.locals(
written=cython.uint, written=cython.uint,
i=cython.uint,
) )
cpdef write_struct(self, object array, object type) cpdef write_struct(self, object array, object type)
@@ -39,9 +40,14 @@ cdef class Marshaller:
) )
cpdef write_single(self, object type_, object body) cpdef write_single(self, object type_, object body)
@cython.locals(
written=cython.uint,
)
cpdef write_dict_entry(self, object type_, object body)
cpdef marshall(self) cpdef marshall(self)
@cython.locals( @cython.locals(
offset=cython.ulong, offset=cython.ulong,
) )
cpdef _construct_buffer(self) cdef _construct_buffer(self)

View File

@@ -1,7 +1,7 @@
from struct import Struct, error from struct import Struct, error
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
from ..signature import SignatureTree, SignatureType, Variant from ..signature import SignatureType, Variant, get_signature_tree
PACK_UINT32 = Struct("<I").pack PACK_UINT32 = Struct("<I").pack
@@ -13,7 +13,7 @@ class Marshaller:
def __init__(self, signature: str, body: List[Any]) -> None: def __init__(self, signature: str, body: List[Any]) -> None:
"""Marshaller constructor.""" """Marshaller constructor."""
self.signature_tree = SignatureTree._get(signature) self.signature_tree = get_signature_tree(signature)
self._buf = bytearray() self._buf = bytearray()
self.body = body self.body = body

View File

@@ -13,6 +13,9 @@ cdef unsigned int BIG_ENDIAN
cdef str UINT32_CAST cdef str UINT32_CAST
cdef object UINT32_SIGNATURE cdef object UINT32_SIGNATURE
cdef class MarshallerStreamEndError(Exception):
pass
cdef class Unmarshaller: cdef class Unmarshaller:
cdef object _unix_fds cdef object _unix_fds
@@ -33,18 +36,20 @@ cdef class Unmarshaller:
cpdef reset(self) cpdef reset(self)
cdef read_sock(self, unsigned long length)
@cython.locals( @cython.locals(
start_len=cython.ulong, start_len=cython.ulong,
missing_bytes=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_) cpdef read_uint32_cast(self, object type_)
@cython.locals( @cython.locals(
buf_bytes=cython.bytearray, buf_bytes=cython.bytearray,
) )
cpdef read_string_cast(self, type_ = *) cpdef read_string_cast(self, object type_)
@cython.locals( @cython.locals(
beginning_pos=cython.ulong, beginning_pos=cython.ulong,
@@ -56,16 +61,16 @@ cdef class Unmarshaller:
o=cython.ulong, o=cython.ulong,
signature_len=cython.uint, signature_len=cython.uint,
) )
cpdef read_signature(self, type_ = *) cpdef read_signature(self, object type_)
@cython.locals( @cython.locals(
endian=cython.uint, endian=cython.uint,
protocol_version=cython.uint, protocol_version=cython.uint,
can_cast=cython.bint can_cast=cython.bint
) )
cpdef _read_header(self) cdef _read_header(self)
cpdef _read_body(self) cdef _read_body(self)
cpdef unmarshall(self) cpdef unmarshall(self)
@@ -74,4 +79,4 @@ cdef class Unmarshaller:
o=cython.ulong, o=cython.ulong,
signature_len=cython.uint, signature_len=cython.uint,
) )
cpdef header_fields(self, unsigned int header_length) cdef header_fields(self, unsigned int header_length)

View File

@@ -3,12 +3,12 @@ import io
import socket import socket
import sys import sys
from struct import Struct from struct import Struct
from typing import Any, Callable, Dict, List, Optional, Tuple from typing import Any, Callable, Dict, List, Tuple
from ..constants import MESSAGE_FLAG_MAP, MESSAGE_TYPE_MAP, MessageFlag, MessageType from ..constants import MESSAGE_FLAG_MAP, MESSAGE_TYPE_MAP
from ..errors import InvalidMessageError from ..errors import InvalidMessageError
from ..message import Message from ..message import Message
from ..signature import SignatureTree, SignatureType, Variant from ..signature import SignatureType, Variant, get_signature_tree
from .constants import ( from .constants import (
BIG_ENDIAN, BIG_ENDIAN,
HEADER_NAME_MAP, HEADER_NAME_MAP,
@@ -28,7 +28,7 @@ UNPACK_LENGTHS = {BIG_ENDIAN: Struct(">III"), LITTLE_ENDIAN: Struct("<III")}
UINT32_CAST = "I" UINT32_CAST = "I"
UINT32_SIZE = 4 UINT32_SIZE = 4
UINT32_DBUS_TYPE = "u" UINT32_DBUS_TYPE = "u"
UINT32_SIGNATURE = SignatureTree._get(UINT32_DBUS_TYPE).types[0] UINT32_SIGNATURE = get_signature_tree(UINT32_DBUS_TYPE).types[0]
DBUS_TO_CTYPE = { DBUS_TO_CTYPE = {
"y": ("B", 1), # byte "y": ("B", 1), # byte
@@ -236,14 +236,14 @@ class Unmarshaller:
if len(data) + start_len != pos: if len(data) + start_len != pos:
raise MarshallerStreamEndError() 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 self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align
return self._view[self._pos - UINT32_SIZE : self._pos].cast(UINT32_CAST)[0] 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)) 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.""" """Read a string using cast."""
self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align
str_start = self._pos str_start = self._pos
@@ -252,7 +252,7 @@ class Unmarshaller:
self._pos += self._view[start_pos : self._pos].cast(UINT32_CAST)[0] + 1 self._pos += self._view[start_pos : self._pos].cast(UINT32_CAST)[0] + 1
return self._buf[str_start : self._pos - 1].decode() 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.""" """Read a string using unpack."""
self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align
str_start = self._pos str_start = self._pos
@@ -260,21 +260,21 @@ class Unmarshaller:
self._pos += self._uint32_unpack(self._view, str_start - UINT32_SIZE)[0] + 1 self._pos += self._uint32_unpack(self._view, str_start - UINT32_SIZE)[0] + 1
return self._buf[str_start : self._pos - 1].decode() 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 signature_len = self._view[self._pos] # byte
o = self._pos + 1 o = self._pos + 1
# read terminating '\0' byte as well (str_length + 1) # read terminating '\0' byte as well (str_length + 1)
self._pos = o + signature_len + 1 self._pos = o + signature_len + 1
return self._buf[o : o + signature_len].decode() return self._buf[o : o + signature_len].decode()
def read_variant(self, type_=None) -> Variant: def read_variant(self, type_: SignatureType) -> Variant:
tree = SignatureTree._get(self.read_signature()) tree = get_signature_tree(self.read_signature(type_))
# verify in Variant is only useful on construction not unmarshalling # verify in Variant is only useful on construction not unmarshalling
return Variant( return Variant(
tree, self._readers[tree.types[0].token](self, tree.types[0]), verify=False 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 self._pos += -self._pos & 7 # align 8
readers = self._readers readers = self._readers
return [ return [
@@ -344,7 +344,7 @@ class Unmarshaller:
signature_len = self._view[self._pos] # byte signature_len = self._view[self._pos] # byte
o = self._pos + 1 o = self._pos + 1
self._pos += signature_len + 2 # one for the byte, one for the '\0' self._pos += signature_len + 2 # one for the byte, one for the '\0'
tree = SignatureTree._get(self._buf[o : o + signature_len].decode()) tree = get_signature_tree(self._buf[o : o + signature_len].decode())
headers[HEADER_NAME_MAP[field_0]] = self._readers[tree.types[0].token]( headers[HEADER_NAME_MAP[field_0]] = self._readers[tree.types[0].token](
self, tree.types[0] self, tree.types[0]
) )
@@ -391,7 +391,7 @@ class Unmarshaller:
self._pos = HEADER_ARRAY_OF_STRUCT_SIGNATURE_POSITION self._pos = HEADER_ARRAY_OF_STRUCT_SIGNATURE_POSITION
header_fields = self.header_fields(self._header_len) header_fields = self.header_fields(self._header_len)
self._pos += -self._pos & 7 # align 8 self._pos += -self._pos & 7 # align 8
tree = SignatureTree._get(header_fields.get(HeaderField.SIGNATURE.name, "")) tree = get_signature_tree(header_fields.get(HeaderField.SIGNATURE.name, ""))
self._message = Message( self._message = Message(
destination=header_fields.get(HEADER_DESTINATION), destination=header_fields.get(HEADER_DESTINATION),
path=header_fields.get(HEADER_PATH), path=header_fields.get(HEADER_PATH),

View File

@@ -2,7 +2,7 @@ import ast
import inspect import inspect
from typing import Any, List, Union from typing import Any, List, Union
from ..signature import SignatureTree, Variant from ..signature import SignatureTree, Variant, get_signature_tree
def signature_contains_type( def signature_contains_type(
@@ -11,7 +11,7 @@ def signature_contains_type(
"""For a given signature and body, check to see if it contains any members """For a given signature and body, check to see if it contains any members
with the given token""" with the given token"""
if type(signature) is str: if type(signature) is str:
signature = SignatureTree._get(signature) signature = get_signature_tree(signature)
queue = [] queue = []
contains_variants = False contains_variants = False
@@ -56,7 +56,7 @@ def replace_fds_with_idx(
an index and return the corresponding list of unix fds that can be set on an index and return the corresponding list of unix fds that can be set on
the Message""" the Message"""
if type(signature) is str: if type(signature) is str:
signature = SignatureTree._get(signature) signature = get_signature_tree(signature)
if not signature_contains_type(signature, body, "h"): if not signature_contains_type(signature, body, "h"):
return body, [] return body, []
@@ -82,7 +82,7 @@ def replace_idx_with_fds(
Type 'h' refers to an index in the unix_fds array. Replace those with the Type 'h' refers to an index in the unix_fds array. Replace those with the
actual file descriptor or `None` if one does not exist.""" actual file descriptor or `None` if one does not exist."""
if type(signature) is str: if type(signature) is str:
signature = SignatureTree._get(signature) signature = get_signature_tree(signature)
if not signature_contains_type(signature, body, "h"): if not signature_contains_type(signature, body, "h"):
return body return body

View File

@@ -3,7 +3,7 @@ from typing import List, Union
from .constants import ArgDirection, PropertyAccess from .constants import ArgDirection, PropertyAccess
from .errors import InvalidIntrospectionError from .errors import InvalidIntrospectionError
from .signature import SignatureTree, SignatureType from .signature import SignatureType, get_signature_tree
from .validators import assert_interface_name_valid, assert_member_name_valid from .validators import assert_interface_name_valid, assert_member_name_valid
# https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format # https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
@@ -42,7 +42,7 @@ class Arg:
type_ = signature type_ = signature
signature = signature.signature signature = signature.signature
else: else:
tree = SignatureTree._get(signature) tree = get_signature_tree(signature)
if len(tree.types) != 1: if len(tree.types) != 1:
raise InvalidIntrospectionError( raise InvalidIntrospectionError(
f"an argument must have a single complete type. (has {len(tree.types)} types)" f"an argument must have a single complete type. (has {len(tree.types)} types)"
@@ -248,7 +248,7 @@ class Property:
): ):
assert_member_name_valid(name) assert_member_name_valid(name)
tree = SignatureTree._get(signature) tree = get_signature_tree(signature)
if len(tree.types) != 1: if len(tree.types) != 1:
raise InvalidIntrospectionError( raise InvalidIntrospectionError(
f"properties must have a single complete type. (has {len(tree.types)} types)" f"properties must have a single complete type. (has {len(tree.types)} types)"

View File

@@ -4,7 +4,7 @@ from ._private.constants import LITTLE_ENDIAN, PROTOCOL_VERSION, HeaderField
from ._private.marshaller import Marshaller from ._private.marshaller import Marshaller
from .constants import ErrorType, MessageFlag, MessageType from .constants import ErrorType, MessageFlag, MessageType
from .errors import InvalidMessageError from .errors import InvalidMessageError
from .signature import SignatureTree, Variant from .signature import SignatureTree, Variant, get_signature_tree
from .validators import ( from .validators import (
assert_bus_name_valid, assert_bus_name_valid,
assert_interface_name_valid, assert_interface_name_valid,
@@ -129,7 +129,7 @@ class Message:
self.signature_tree = signature self.signature_tree = signature
else: else:
self.signature = signature self.signature = signature
self.signature_tree = SignatureTree._get(signature) self.signature_tree = get_signature_tree(signature)
self.body = body self.body = body
self.serial = serial self.serial = serial
@@ -255,13 +255,6 @@ class Message:
unix_fds=unix_fds, unix_fds=unix_fds,
) )
def _matches(self, **kwargs):
for attr, val in kwargs.items():
if getattr(self, attr) != val:
return False
return True
def _marshall(self, negotiate_unix_fd=False): def _marshall(self, negotiate_unix_fd=False):
# TODO maximum message size is 134217728 (128 MiB) # TODO maximum message size is 134217728 (128 MiB)
body_block = Marshaller(self.signature, self.body) body_block = Marshaller(self.signature, self.body)

View File

@@ -13,7 +13,7 @@ from ._private.util import (
) )
from .constants import PropertyAccess from .constants import PropertyAccess
from .errors import SignalDisabledError from .errors import SignalDisabledError
from .signature import SignatureBodyMismatchError, SignatureTree, Variant from .signature import SignatureBodyMismatchError, Variant, get_signature_tree
class _Method: class _Method:
@@ -39,7 +39,7 @@ class _Method:
out_args = [] out_args = []
out_signature = parse_annotation(inspection.return_annotation) out_signature = parse_annotation(inspection.return_annotation)
if out_signature: if out_signature:
for type_ in SignatureTree._get(out_signature).types: for type_ in get_signature_tree(out_signature).types:
out_args.append(intr.Arg(type_, intr.ArgDirection.OUT)) out_args.append(intr.Arg(type_, intr.ArgDirection.OUT))
self.name = name self.name = name
@@ -48,8 +48,8 @@ class _Method:
self.introspection = intr.Method(name, in_args, out_args) self.introspection = intr.Method(name, in_args, out_args)
self.in_signature = in_signature self.in_signature = in_signature
self.out_signature = out_signature self.out_signature = out_signature
self.in_signature_tree = SignatureTree._get(in_signature) self.in_signature_tree = get_signature_tree(in_signature)
self.out_signature_tree = SignatureTree._get(out_signature) self.out_signature_tree = get_signature_tree(out_signature)
def method(name: str = None, disabled: bool = False): def method(name: str = None, disabled: bool = False):
@@ -116,12 +116,12 @@ class _Signal:
if return_annotation: if return_annotation:
signature = return_annotation signature = return_annotation
signature_tree = SignatureTree._get(signature) signature_tree = get_signature_tree(signature)
for type_ in signature_tree.types: for type_ in signature_tree.types:
args.append(intr.Arg(type_, intr.ArgDirection.OUT)) args.append(intr.Arg(type_, intr.ArgDirection.OUT))
else: else:
signature = "" signature = ""
signature_tree = SignatureTree._get("") signature_tree = get_signature_tree("")
self.signature = signature self.signature = signature
self.signature_tree = signature_tree self.signature_tree = signature_tree
@@ -226,7 +226,7 @@ class _Property(property):
) )
self.signature = return_annotation self.signature = return_annotation
tree = SignatureTree._get(return_annotation) tree = get_signature_tree(return_annotation)
if len(tree.types) != 1: if len(tree.types) != 1:
raise ValueError("the property signature must be a single complete type") raise ValueError("the property signature must be a single complete type")

View File

@@ -0,0 +1,16 @@
"""cdefs for signature.py"""
import cython
cdef class SignatureType:
cdef public str token
cdef public list children
cdef str _signature
cdef class SignatureTree:
cdef public str signature
cdef public list types

View File

@@ -330,10 +330,7 @@ class SignatureTree:
:class:`InvalidSignatureError` if the given signature is not valid. :class:`InvalidSignatureError` if the given signature is not valid.
""" """
@staticmethod __slots__ = ("signature", "types")
@lru_cache(maxsize=None)
def _get(signature: str = "") -> "SignatureTree":
return SignatureTree(signature)
def __init__(self, signature: str = ""): def __init__(self, signature: str = ""):
self.signature = signature self.signature = signature
@@ -414,7 +411,7 @@ class Variant:
signature_str = signature.signature signature_str = signature.signature
signature_tree = None signature_tree = None
elif type(signature) is str: elif type(signature) is str:
signature_tree = SignatureTree._get(signature) signature_tree = get_signature_tree(signature)
else: else:
raise TypeError( raise TypeError(
"signature must be a SignatureTree, SignatureType, or a string" "signature must be a SignatureTree, SignatureType, or a string"
@@ -445,3 +442,8 @@ class Variant:
return "<dbus_fast.signature.Variant ('{}', {})>".format( return "<dbus_fast.signature.Variant ('{}', {})>".format(
self.type.signature, self.value self.type.signature, self.value
) )
@lru_cache(maxsize=None)
def get_signature_tree(signature: str = "") -> SignatureTree:
return SignatureTree(signature)