Skip to content

Commit

Permalink
Verify with asserts D-Bus names before passing them to sd-bus
Browse files Browse the repository at this point in the history
This will make the errors much more verbose.

Because asserts used once the optimized mode `-O` is switched
on there would be no performance impact.

Does not work of Ubuntu 20.04 because validation functions
are not exposed.
  • Loading branch information
igo95862 committed Jun 11, 2022
1 parent 9da0fa3 commit eacd64d
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 97 deletions.
4 changes: 2 additions & 2 deletions src/sdbus/autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
DbusInterfaceCommonAsync,
DbusMethodAsync,
DbusPropertyAsync,
DbusSignal,
DbusSignalAsync,
)


Expand Down Expand Up @@ -99,7 +99,7 @@ class DbusSignalDocumenter(AttributeDocumenter):

@classmethod
def can_document_member(cls, member: Any, *args: Any) -> bool:
return isinstance(member, DbusSignal)
return isinstance(member, DbusSignalAsync)

def update_annotations(self,
parent: DbusInterfaceCommonAsync) -> None:
Expand Down
124 changes: 110 additions & 14 deletions src/sdbus/dbus_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
from asyncio import get_running_loop
from inspect import getfullargspec
from types import FunctionType
from typing import Any, Dict, Iterator, List, Optional, Sequence
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple

from .sd_bus_internals import (
DbusPropertyConstFlag,
DbusPropertyEmitsChangeFlag,
DbusPropertyEmitsInvalidationFlag,
DbusPropertyExplicitFlag,
SdBus,
is_interface_name_valid,
is_member_name_valid,
sd_bus_open,
)

Expand All @@ -45,14 +47,9 @@ def count_bits(i: int) -> int:
return bin(i).count('1')


def is_property_flags_correct(flags: int) -> None:
def _is_property_flags_correct(flags: int) -> bool:
num_of_flag_bits = count_bits(PROPERTY_FLAGS_MASK & flags)
if not 0 <= num_of_flag_bits <= 1:
raise ValueError(
'Incorrect number of Property flags. '
'Only one of DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, '
'DbusPropertyEmitsInvalidationFlag or DbusPropertyExplicitFlag '
'is allowed.')
return (0 <= num_of_flag_bits <= 1)


def get_default_bus() -> SdBus:
Expand Down Expand Up @@ -131,6 +128,39 @@ class DbusSomethingSync(DbusSomethingCommon):
...


class DbusInterfaceMetaCommon(type):
def __new__(cls, name: str,
bases: Tuple[type, ...],
namespace: Dict[str, Any],
interface_name: Optional[str] = None,
serving_enabled: bool = True,
) -> DbusInterfaceMetaCommon:
if interface_name is not None:
try:
assert is_interface_name_valid(interface_name), (
f"Invalid interface name: \"{interface_name}\"; "
'Interface names must be composed of 2 or more elements '
'separated by a dot \'.\' character. All elements must '
'contain atleast one character, constist of ASCII '
'characters, first character must not be digit and '
'length must not exceed 255 characters.'
)
except NotImplementedError:
...

new_cls = super().__new__(cls, name, bases, namespace)

return new_cls


MEMBER_NAME_REQUIREMENTS = (
'Member name must only contain ASCII characters, '
'cannot start with digit, '
'must not contain dot \'.\' and be between 1 '
'and 255 characters in length.'
)


class DbusMethodCommon(DbusSomethingCommon):

def __init__(
Expand All @@ -156,6 +186,18 @@ def __init__(
"Can't have spaces in argument result names."
f"Args: {result_args_names}")

if method_name is None:
method_name = ''.join(
_method_name_converter(original_method.__name__))

try:
assert is_member_name_valid(method_name), (
f"Invalid method name: \"{method_name}\"; "
f"{MEMBER_NAME_REQUIREMENTS}"
)
except NotImplementedError:
...

super().__init__()
self.original_method = original_method
self.args_spec = getfullargspec(original_method)
Expand All @@ -167,12 +209,7 @@ def __init__(
else ())
self.default_args_start_at = self.num_of_args - len(self.args_defaults)

if method_name is None:
self.method_name = ''.join(
_method_name_converter(original_method.__name__))
else:
self.method_name = method_name

self.method_name = method_name
self.input_signature = input_signature
self.input_args_names: Sequence[str] = (
self.args_names
Expand Down Expand Up @@ -237,3 +274,62 @@ def _rebuild_args(
raise TypeError('Could not flatten the args')

return new_args_list


class DbusPropertyCommon(DbusSomethingCommon):
def __init__(self,
property_name: Optional[str],
property_signature: str,
flags: int,
original_method: FunctionType):
if property_name is None:
property_name = ''.join(
_method_name_converter(original_method.__name__))

try:
assert is_member_name_valid(property_name), (
f"Invalid property name: \"{property_name}\"; "
f"{MEMBER_NAME_REQUIREMENTS}"
)
except NotImplementedError:
...

assert _is_property_flags_correct(flags), (
'Incorrect number of Property flags. '
'Only one of DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, '
'DbusPropertyEmitsInvalidationFlag or DbusPropertyExplicitFlag '
'is allowed.'
)

super().__init__()
self.property_name: str = property_name
self.property_signature = property_signature
self.flags = flags


class DbusSingalCommon(DbusSomethingCommon):
def __init__(self,
signal_name: Optional[str],
signal_signature: str,
args_names: Sequence[str],
flags: int,
original_method: FunctionType):
if signal_name is None:
signal_name = ''.join(
_method_name_converter(original_method.__name__))

try:
assert is_member_name_valid(signal_name), (
f"Invalid signal name: \"{signal_name}\"; "
f"{MEMBER_NAME_REQUIREMENTS}"
)
except NotImplementedError:
...

super().__init__()
self.signal_name = signal_name
self.signal_signature = signal_signature
self.args_names = args_names
self.flags = flags

self.__doc__ = original_method.__doc__
101 changes: 42 additions & 59 deletions src/sdbus/dbus_proxy_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@
from weakref import ref as weak_ref

from .dbus_common import (
DbusInterfaceMetaCommon,
DbusMethodCommon,
DbusPropertyCommon,
DbusSingalCommon,
DbusSomethingAsync,
DbusSomethingSync,
_method_name_converter,
get_default_bus,
is_property_flags_correct,
)
from .dbus_exceptions import DbusFailedError
from .sd_bus_internals import (
Expand Down Expand Up @@ -226,10 +227,10 @@ def dbus_method_decorator(original_method: T_input) -> T_input:
T = TypeVar('T')


class DbusProperty(DbusSomethingAsync, Generic[T]):
class DbusPropertyAsync(DbusSomethingAsync, DbusPropertyCommon, Generic[T]):
def __init__(
self,
property_name: str,
property_name: Optional[str],
property_signature: str,
property_getter: Callable[[DbusInterfaceBaseAsync],
T],
Expand All @@ -239,22 +240,24 @@ def __init__(
flags: int,

) -> None:
is_property_flags_correct(flags) # Check that number of passed flags

super().__init__()
self.property_name = property_name
self.property_signature = property_signature
self.property_getter = property_getter
self.property_setter = property_setter
self.flags = flags
assert isinstance(property_getter, FunctionType)
super().__init__(
property_name,
property_signature,
flags,
property_getter,
)
self.property_getter: Callable[
[DbusInterfaceBaseAsync], T] = property_getter
self.property_setter: Optional[
Callable[[DbusInterfaceBaseAsync, T],
None]] = property_setter

self.properties_changed_signal: \
Optional[DbusSignalBinded[DBUS_PROPERTIES_CHANGED_TYPING]] = None

self.__doc__ = property_getter.__doc__


class DbusPropertyAsync(DbusProperty[T]):
def __get__(self,
obj: DbusInterfaceBaseAsync,
obj_class: Optional[Type[DbusInterfaceBaseAsync]] = None,
Expand Down Expand Up @@ -396,13 +399,6 @@ def property_decorator(

nonlocal property_name

if property_name is None:
property_name = ''.join(
_method_name_converter(
cast(FunctionType, function).__name__
)
)

new_wrapper: DbusPropertyAsync[T] = DbusPropertyAsync(
property_name,
property_signature,
Expand All @@ -416,23 +412,7 @@ def property_decorator(
return property_decorator


class DbusSignal(Generic[T], DbusSomethingAsync):
def __init__(
self,
original_function: Callable[[Any], T],
signal_name: str,
signature: str = "",
args_names: Sequence[str] = (),
flags: int = 0,
):
super().__init__()
self.original_function = original_function
self.signal_name = signal_name
self.signature = signature
self.args_names = args_names
self.flags = flags

self.__doc__ = original_function.__doc__
class DbusSignalAsync(DbusSomethingAsync, DbusSingalCommon, Generic[T]):

def __get__(self,
obj: DbusInterfaceBaseAsync,
Expand All @@ -443,7 +423,7 @@ def __get__(self,

class DbusSignalBinded(Generic[T], DbusBindedAsync):
def __init__(self,
dbus_signal: DbusSignal[T],
dbus_signal: DbusSignalAsync[T],
interface: DbusInterfaceBaseAsync):
self.dbus_signal = dbus_signal
self.interface_ref = weak_ref(interface)
Expand Down Expand Up @@ -526,12 +506,14 @@ def _emit_message(self, args: T) -> None:
self.dbus_signal.signal_name,
)

if ((not self.dbus_signal.signature.startswith('('))
if ((not self.dbus_signal.signal_signature.startswith('('))
and
isinstance(args, tuple)):
signal_message.append_data(self.dbus_signal.signature, *args)
signal_message.append_data(
self.dbus_signal.signal_signature, *args)
else:
signal_message.append_data(self.dbus_signal.signature, args)
signal_message.append_data(
self.dbus_signal.signal_signature, args)

signal_message.send()

Expand Down Expand Up @@ -560,27 +542,24 @@ def dbus_signal_async(
signal_name: Optional[str] = None,
) -> Callable[
[Callable[[Any], T]],
DbusSignal[T]
DbusSignalAsync[T]
]:
assert not isinstance(signal_signature, FunctionType), (
"Passed function to decorator directly. "
"Did you forget () round brackets?"
)

def signal_decorator(pseudo_function: Callable[[Any], T]) -> DbusSignal[T]:
def signal_decorator(
pseudo_function: Callable[[Any], T]) -> DbusSignalAsync[T]:
nonlocal signal_name

if signal_name is None:
signal_name = ''.join(
_method_name_converter(
cast(FunctionType, pseudo_function).__name__
)
)

return DbusSignal(
assert isinstance(pseudo_function, FunctionType)
return DbusSignalAsync(
signal_name,
signal_signature,
signal_args_names,
flags,
pseudo_function,
signal_name, signal_signature,
signal_args_names, flags,
)

return signal_decorator
Expand Down Expand Up @@ -615,7 +594,7 @@ def new_decorator(
return new_decorator


class DbusInterfaceMetaAsync(type):
class DbusInterfaceMetaAsync(DbusInterfaceMetaCommon):
def __new__(cls, name: str,
bases: Tuple[type, ...],
namespace: Dict[str, Any],
Expand Down Expand Up @@ -675,9 +654,13 @@ def __new__(cls, name: str,

namespace['_dbus_interface_name'] = interface_name
namespace['_dbus_serving_enabled'] = serving_enabled
new_cls = super().__new__(cls, name, bases, namespace)
new_cls = super().__new__(
cls, name, bases, namespace,
interface_name,
serving_enabled,
)

return new_cls
return cast(DbusInterfaceMetaAsync, new_cls)


class DbusInterfaceBaseAsync(metaclass=DbusInterfaceMetaAsync):
Expand All @@ -693,7 +676,7 @@ def __init__(self) -> None:
self._attached_bus: Optional[SdBus] = None
self._serving_object_path: Optional[str] = None
self._local_signal_queues: \
Dict[DbusSignal[Any], List[weak_ref[Queue[Any]]]] = {}
Dict[DbusSignalAsync[Any], List[weak_ref[Queue[Any]]]] = {}

async def start_serving(self,
object_path: str,
Expand Down Expand Up @@ -777,7 +760,7 @@ def export_to_dbus(
elif isinstance(dbus_something, DbusSignalBinded):
new_interface.add_signal(
dbus_something.dbus_signal.signal_name,
dbus_something.dbus_signal.signature,
dbus_something.dbus_signal.signal_signature,
dbus_something.dbus_signal.args_names,
dbus_something.dbus_signal.flags,
)
Expand Down

0 comments on commit eacd64d

Please sign in to comment.