diff --git a/src/dbus_fast/message_bus.pxd b/src/dbus_fast/message_bus.pxd index f7a0a4a..b55f4fb 100644 --- a/src/dbus_fast/message_bus.pxd +++ b/src/dbus_fast/message_bus.pxd @@ -58,6 +58,8 @@ cdef class BaseMessageBus: ) cdef _find_message_handler(self, Message msg) + cdef _find_any_message_handler_matching_signature(self, dict interfaces, Message msg) + cdef _setup_socket(self) cpdef _call(self, Message msg, object callback) diff --git a/src/dbus_fast/message_bus.py b/src/dbus_fast/message_bus.py index 6626047..7ad6eb9 100644 --- a/src/dbus_fast/message_bus.py +++ b/src/dbus_fast/message_bus.py @@ -906,10 +906,12 @@ class BaseMessageBus: return partial(self._callback_method_handler, interface, method) def _find_message_handler(self, msg: _Message) -> HandlerType | None: + """Find the message handler for for METHOD_CALL messages.""" if TYPE_CHECKING: - assert msg.interface is not None + assert msg.member is not None + assert msg.path is not None - if "org.freedesktop.DBus." in msg.interface: + if msg.interface is not None and "org.freedesktop.DBus." in msg.interface: if ( msg.interface == "org.freedesktop.DBus.Introspectable" and msg.member == "Introspect" @@ -932,19 +934,33 @@ class BaseMessageBus: ): return self._default_get_managed_objects_handler - if ( - msg.path is not None - and msg.member is not None - and (interfaces := self._path_exports.get(msg.path)) is not None - and (interface := interfaces.get(msg.interface)) is not None - and ( + if (interfaces := self._path_exports.get(msg.path)) is None: + return None + + if msg.interface is None: + return self._find_any_message_handler_matching_signature(interfaces, msg) + + if (interface := interfaces.get(msg.interface)) is not None and ( + handler := ServiceInterface._get_enabled_handler_by_name_signature( + interface, self, msg.member, msg.signature + ) + ) is not None: + return handler + + return None + + def _find_any_message_handler_matching_signature( + self, interfaces: dict[str, ServiceInterface], msg: _Message + ) -> HandlerType | None: + # No interface, so we need to search all interfaces for the method + # with a matching signature + for interface in interfaces.values(): + if ( handler := ServiceInterface._get_enabled_handler_by_name_signature( interface, self, msg.member, msg.signature ) - ) - is not None - ): - return handler + ) is not None: + return handler return None def _default_introspect_handler(self, msg: Message, send_reply: SendReply) -> None: diff --git a/tests/service/test_methods.py b/tests/service/test_methods.py index 99f21b6..d3753b8 100644 --- a/tests/service/test_methods.py +++ b/tests/service/test_methods.py @@ -61,7 +61,7 @@ class ExampleInterface(ServiceInterface): @method() def throws_dbus_error(self): assert type(self) is ExampleInterface - raise DBusError("test.error", "an error ocurred") + raise DBusError("test.error", "an error occurred") class AsyncInterface(ServiceInterface): @@ -112,7 +112,7 @@ class AsyncInterface(ServiceInterface): @method() def throws_dbus_error(self): assert type(self) is AsyncInterface - raise DBusError("test.error", "an error ocurred") + raise DBusError("test.error", "an error occurred") @pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface]) @@ -124,12 +124,14 @@ async def test_methods(interface_class): interface = interface_class("test.interface") export_path = "/test/path" - async def call(member, signature="", body=[], flags=MessageFlag.NONE): + async def call( + member, signature="", body=[], flags=MessageFlag.NONE, interface=interface.name + ): return await bus2.call( Message( destination=bus1.unique_name, path=export_path, - interface=interface.name, + interface=interface, member=member, signature=signature, body=body, @@ -165,6 +167,31 @@ async def test_methods(interface_class): assert reply.signature == signature assert reply.body == body + # Wrong interface should be a failure + reply = await call( + "echo_containers", signature, body, interface="org.abc.xyz.Props" + ) + assert reply.message_type == MessageType.ERROR, reply.body[0] + assert reply.error_name == "org.freedesktop.DBus.Error.UnknownMethod", reply.body[0] + assert reply.body == [ + 'org.abc.xyz.Props.echo_containers with signature "asva{sv}(s(s(v)))" could not be found' + ] + + # No interface should result in finding anything that matches the member name + # and the signature + reply = await call("echo_containers", signature, body, interface=None) + assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0] + assert reply.signature == signature + assert reply.body == body + + # No interface should result in finding anything that matches the member name + # and the signature, but in this case it will be nothing because + # the signature is wrong + reply = await call("echo_containers", "as", body, interface=None) + assert reply.message_type == MessageType.ERROR, reply.body[0] + assert reply.error_name == "org.freedesktop.DBus.Error.UnknownMethod", reply.body[0] + assert reply.body == ['None.echo_containers with signature "as" could not be found'] + reply = await call("ping") assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0] assert reply.signature == "" @@ -177,7 +204,7 @@ async def test_methods(interface_class): reply = await call("throws_dbus_error") assert reply.message_type == MessageType.ERROR, reply.body[0] assert reply.error_name == "test.error", reply.body[0] - assert reply.body == ["an error ocurred"] + assert reply.body == ["an error occurred"] reply = await call("ping", flags=MessageFlag.NO_REPLY_EXPECTED) assert reply is None @@ -188,6 +215,13 @@ async def test_methods(interface_class): reply = await call("throws_dbus_error", flags=MessageFlag.NO_REPLY_EXPECTED) assert reply is None + reply = await call("does_not_exist") + assert reply.message_type == MessageType.ERROR, reply.body[0] + assert reply.error_name == "org.freedesktop.DBus.Error.UnknownMethod", reply.body[0] + assert reply.body == [ + 'test.interface.does_not_exist with signature "" could not be found' + ] + bus1.disconnect() bus2.disconnect() bus1._sock.close()