Пример #1
0
def test_unhandled():
    unhandled = []
    router = Router(Future, on_unhandled=unhandled.append)
    msg = message_bus.Hello()
    router.incoming(msg)
    assert len(unhandled) == 1
    assert unhandled[0] == msg
Пример #2
0
 def __init__(self, sock):
     self.sock = sock
     self.parser = Parser()
     self.router = Router(_Future)
     self.bus_proxy = Proxy(message_bus, self)
     hello_reply = self.bus_proxy.Hello()
     self.unique_name = hello_reply[0]
Пример #3
0
    def Init(self, *args, **kwargs):
        self.module = self.GetModule()
        self.debug = self.module.debug
        self.logger = get_logger(self.__class__.__name__)
        # Well-known name (org.asamk.Signal) registered with message bus
        self.has_service = False
        #
        self.__dict__.update(kwargs)
        self.unique_name = None
        #
        self.auth_parser = SASLParserAnonAuth()
        self.parser = Parser()
        #
        FakeFuture.fake_loop = FakeLoop(self.module)
        self.router = Router(FakeFuture)
        #
        if self.debug:
            from jeepney.low_level import HeaderFields

            def on_unhandled(msg):
                member = msg.header.fields[HeaderFields.member]
                if member == "NameAcquired":
                    # This fires before the "Hello" reply callback
                    log_msg = f"Received routine opening signal: {member!r}; "
                else:
                    log_msg = "See 'data_received' entry above for contents"
                self.logger.debug(log_msg)

            self.router.on_unhandled = on_unhandled
        self.authentication = FakeFuture()
        # FIXME explain why this appears twice (see above)
        self.unique_name = None
Пример #4
0
    def __init__(self, conn: DBusConnection):
        self.conn = conn
        self._replies = ReplyMatcher()
        self._filters = MessageFilters()
        self._stop_receiving = Event()
        IOLoop.current().add_callback(self._receiver)

        # For backwards compatibility - old-style signal callbacks
        self.router = Router(Future)
Пример #5
0
def test_error():
    router = Router(Future)
    call = message_bus.Hello()
    future = router.outgoing(call)
    router.incoming(new_error(call, 'TestError', 'u', (31, )))
    with pytest.raises(DBusErrorResponse) as e:
        future.result()
    assert e.value.name == 'TestError'
    assert e.value.data == (31, )
Пример #6
0
    def __init__(self, sock: socket.socket, enable_fds=False):
        super().__init__(sock, enable_fds)
        self._unwrap_reply = False

        # Message routing machinery
        self.router = Router(_Future)  # Old interface, for backwards compat
        self._filters = MessageFilters()

        # Say Hello, get our unique name
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]
Пример #7
0
    def __init__(self, bus_addr):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(Future)
        self.authentication = Future()
        self.unique_name = None

        self._sock = socket.socket(family=socket.AF_UNIX)
        self.stream = IOStream(self._sock, read_chunk_size=4096)

        def connected():
            self.stream.write(b'\0' + make_auth_external())

        self.stream.connect(bus_addr, connected)
        self.stream.read_until_close(streaming_callback=self.data_received)
Пример #8
0
    def __init__(self, sock):
        self.sock = sock
        self.parser = Parser()
        self.outgoing_serial = count(start=1)
        self.selector = DefaultSelector()
        self.select_key = self.selector.register(sock, EVENT_READ)
        self._unwrap_reply = False

        # Message routing machinery
        self.router = Router(_Future)  # Old interface, for backwards compat
        self._filters = MessageFilters()

        # Say Hello, get our unique name
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]
Пример #9
0
class DBusConnection:
    def __init__(self, sock):
        self.sock = sock
        self.parser = Parser()
        self.router = Router(_Future)
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False

    def send_message(self, message):
        future = self.router.outgoing(message)
        data = message.serialise()
        self.sock.sendall(data)
        return future

    def recv_messages(self):
        """Read data from the socket and handle incoming messages.
        
        Blocks until at least one message has been read.
        """
        while True:
            b = self.sock.recv(4096)
            msgs = self.parser.feed(b)
            if msgs:
                for msg in msgs:
                    self.router.incoming(msg)
                return

    def send_and_get_reply(self, message):
        """Send a message, wait for the reply and return it.
        """
        future = self.send_message(message)
        while not future.done():
            self.recv_messages()

        return future.result()

    def close(self):
        self.sock.close()
Пример #10
0
class DBusProtocol(asyncio.Protocol):
    def __init__(self):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(asyncio.Future)
        self.authentication = asyncio.Future()
        self.unique_name = None

    def connection_made(self, transport):
        self.transport = transport
        self.transport.write(b'\0' + make_auth_external())

    def _authenticated(self):
        self.transport.write(BEGIN)
        self.authentication.set_result(True)
        self.data_received = self.data_received_post_auth
        self.data_received(self.auth_parser.buffer)

    def data_received(self, data):
        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(
                AuthenticationError(self.auth_parser.error))

    def data_received_post_auth(self, data):
        for msg in self.parser.feed(data):
            self.router.incoming(msg)

    def send_message(self, message):
        if not self.authentication.done():
            raise RuntimeError(
                "Wait for authentication before sending messages")

        future = self.router.outgoing(message)
        data = message.serialise()
        self.transport.write(data)
        return future

    async def send_and_get_reply(self, message):
        if message.header.message_type != MessageType.method_call:
            raise TypeError("Only method call messages have replies")

        return await self.send_message(message)
Пример #11
0
class DBusConnection:
    def __init__(self, bus_addr):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(Future)
        self.authentication = Future()
        self.unique_name = None

        self._sock = socket.socket(family=socket.AF_UNIX)
        self.stream = IOStream(self._sock, read_chunk_size=4096)

        def connected():
            self.stream.write(b'\0' + make_auth_external())

        self.stream.connect(bus_addr, connected)
        self.stream.read_until_close(streaming_callback=self.data_received)

    def _authenticated(self):
        self.stream.write(BEGIN)
        self.authentication.set_result(True)
        self.data_received_post_auth(self.auth_parser.buffer)

    def data_received(self, data):
        if self.authentication.done():
            return self.data_received_post_auth(data)

        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(AuthenticationError(self.auth_parser.error))

    def data_received_post_auth(self, data):
        for msg in self.parser.feed(data):
            self.router.incoming(msg)

    def send_message(self, message):
        if not self.authentication.done():
            raise RuntimeError("Wait for authentication before sending messages")

        future = self.router.outgoing(message)
        data = message.serialise()
        self.stream.write(data)
        return future
Пример #12
0
class DBusConnection(DBusConnectionBase):
    def __init__(self, sock: socket.socket, enable_fds=False):
        super().__init__(sock, enable_fds)
        self._unwrap_reply = False

        # Message routing machinery
        self.router = Router(_Future)  # Old interface, for backwards compat
        self._filters = MessageFilters()

        # Say Hello, get our unique name
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]

    def send(self, message: Message, serial=None):
        """Serialise and send a :class:`~.Message` object"""
        data, fds = self._serialise(message, serial)
        if fds:
            self._send_with_fds(data, fds)
        else:
            self.sock.sendall(data)

    send_message = send  # Backwards compatibility

    def receive(self, *, timeout=None) -> Message:
        """Return the next available message from the connection

        If the data is ready, this will return immediately, even if timeout<=0.
        Otherwise, it will wait for up to timeout seconds, or indefinitely if
        timeout is None. If no message comes in time, it raises TimeoutError.
        """
        return self._receive(timeout_to_deadline(timeout))

    def recv_messages(self, *, timeout=None):
        """Receive one message and apply filters

        See :meth:`filter`. Returns nothing.
        """
        msg = self.receive(timeout=timeout)
        self.router.incoming(msg)
        for filter in self._filters.matches(msg):
            filter.queue.append(msg)

    def send_and_get_reply(self, message, *, timeout=None, unwrap=None):
        """Send a message, wait for the reply and return it

        Filters are applied to other messages received before the reply -
        see :meth:`add_filter`.
        """
        check_replyable(message)
        deadline = timeout_to_deadline(timeout)

        if unwrap is None:
            unwrap = self._unwrap_reply

        serial = next(self.outgoing_serial)
        self.send_message(message, serial=serial)
        while True:
            msg_in = self.receive(timeout=deadline_to_timeout(deadline))
            reply_to = msg_in.header.fields.get(HeaderFields.reply_serial, -1)
            if reply_to == serial:
                if unwrap:
                    return unwrap_msg(msg_in)
                return msg_in

            # Not the reply
            self.router.incoming(msg_in)
            for filter in self._filters.matches(msg_in):
                filter.queue.append(msg_in)

    def filter(self, rule, *, queue: Optional[deque] = None, bufsize=1):
        """Create a filter for incoming messages

        Usage::

            with conn.filter(rule) as matches:
                # matches is a deque containing matched messages
                matching_msg = conn.recv_until_filtered(matches)

        :param jeepney.MatchRule rule: Catch messages matching this rule
        :param collections.deque queue: Matched messages will be added to this
        :param int bufsize: If no deque is passed in, create one with this size
        """
        if queue is None:
            queue = deque(maxlen=bufsize)
        return FilterHandle(self._filters, rule, queue)

    def recv_until_filtered(self, queue, *, timeout=None) -> Message:
        """Process incoming messages until one is filtered into queue

        Pops the message from queue and returns it, or raises TimeoutError if
        the optional timeout expires. Without a timeout, this is equivalent to::

            while len(queue) == 0:
                conn.recv_messages()
            return queue.popleft()

        In the other I/O modules, there is no need for this, because messages
        are placed in queues by a separate task.

        :param collections.deque queue: A deque connected by :meth:`filter`
        :param float timeout: Maximum time to wait in seconds
        """
        deadline = timeout_to_deadline(timeout)
        while len(queue) == 0:
            self.recv_messages(timeout=deadline_to_timeout(deadline))
        return queue.popleft()
Пример #13
0
class DBusRouter:
    def __init__(self, conn: DBusConnection):
        self.conn = conn
        self._replies = ReplyMatcher()
        self._filters = MessageFilters()
        self._stop_receiving = Event()
        IOLoop.current().add_callback(self._receiver)

        # For backwards compatibility - old-style signal callbacks
        self.router = Router(Future)

    async def send(self, message, *, serial=None):
        await self.conn.send(message, serial=serial)

    async def send_and_get_reply(self, message):
        check_replyable(message)
        if self._stop_receiving.is_set():
            raise RouterClosed("This DBusRouter has stopped")

        serial = next(self.conn.outgoing_serial)

        with self._replies.catch(serial, Future()) as reply_fut:
            await self.send(message, serial=serial)
            return (await reply_fut)

    def filter(self, rule, *, queue: Optional[Queue] = None, bufsize=1):
        """Create a filter for incoming messages

        Usage::

            with router.filter(rule) as queue:
                matching_msg = await queue.get()

        :param jeepney.MatchRule rule: Catch messages matching this rule
        :param tornado.queues.Queue queue: Matched messages will be added to this
        :param int bufsize: If no queue is passed in, create one with this size
        """
        return FilterHandle(self._filters, rule, queue or Queue(bufsize))

    def stop(self):
        self._stop_receiving.set()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()
        return False

    # Backwards compatible interface (from old DBusConnection) --------

    @property
    def unique_name(self):
        return self.conn.unique_name

    async def send_message(self, message: Message):
        if (message.header.message_type == MessageType.method_return and
                not (message.header.flags & MessageFlag.no_reply_expected)):
            return unwrap_msg(await self.send_and_get_reply(message))
        else:
            await self.send(message)

    # Code to run in receiver task ------------------------------------

    def _dispatch(self, msg: Message):
        """Handle one received message"""
        if self._replies.dispatch(msg):
            return

        for filter in self._filters.matches(msg):
            try:
                filter.queue.put_nowait(msg)
            except QueueFull:
                pass

    async def _receiver(self):
        """Receiver loop - runs in a separate task"""
        try:
            while True:
                for coro in as_completed(
                    [self.conn.receive(),
                     self._stop_receiving.wait()]):
                    msg = await coro
                    if msg is None:
                        return  # Stopped
                    self._dispatch(msg)
                    self.router.incoming(msg)
        finally:
            self.is_running = False
            # Send errors to any tasks still waiting for a message.
            self._replies.drop_all()
Пример #14
0
def test_message_reply():
    router = Router(Future)
    call = message_bus.Hello()
    future = router.outgoing(call)
    router.incoming(new_method_return(call, 's', ('test', )))
    assert future.result() == ('test', )
Пример #15
0
class DBusConnection:
    def __init__(self, sock):
        self.sock = sock
        self.parser = Parser()
        self.outgoing_serial = count(start=1)
        self.selector = DefaultSelector()
        self.select_key = self.selector.register(sock, EVENT_READ)
        self._unwrap_reply = False

        # Message routing machinery
        self.router = Router(_Future)  # Old interface, for backwards compat
        self._filters = MessageFilters()

        # Say Hello, get our unique name
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False

    def send(self, message: Message, serial=None):
        """Serialise and send a :class:`~.Message` object"""
        if serial is None:
            serial = next(self.outgoing_serial)
        data = message.serialise(serial=serial)
        self.sock.sendall(data)

    send_message = send  # Backwards compatibility

    def receive(self, *, timeout=None) -> Message:
        """Return the next available message from the connection

        If the data is ready, this will return immediately, even if timeout<=0.
        Otherwise, it will wait for up to timeout seconds, or indefinitely if
        timeout is None. If no message comes in time, it raises TimeoutError.
        """
        if timeout is not None:
            deadline = time.monotonic() + timeout
        else:
            deadline = None

        while True:
            msg = self.parser.get_next_message()
            if msg is not None:
                return msg

            if deadline is not None:
                timeout = deadline - time.monotonic()

            b = self._read_some_data(timeout)
            self.parser.add_data(b)

    def _read_some_data(self, timeout=None):
        for key, ev in self.selector.select(timeout):
            if key == self.select_key:
                return unwrap_read(self.sock.recv(4096))

        raise TimeoutError

    def recv_messages(self, *, timeout=None):
        """Receive one message and apply filters

        See :meth:`filter`. Returns nothing.
        """
        msg = self.receive(timeout=timeout)
        self.router.incoming(msg)
        for filter in self._filters.matches(msg):
            filter.queue.append(msg)

    def send_and_get_reply(self, message, *, timeout=None, unwrap=None):
        """Send a message, wait for the reply and return it

        Filters are applied to other messages received before the reply -
        see :meth:`add_filter`.
        """
        check_replyable(message)
        if timeout is not None:
            deadline = time.monotonic() + timeout
        else:
            deadline = None

        if unwrap is None:
            unwrap = self._unwrap_reply

        serial = next(self.outgoing_serial)
        self.send_message(message, serial=serial)
        while True:
            if deadline is not None:
                timeout = deadline - time.monotonic()
            msg_in = self.receive(timeout=timeout)
            reply_to = msg_in.header.fields.get(HeaderFields.reply_serial, -1)
            if reply_to == serial:
                if unwrap:
                    return unwrap_msg(msg_in)
                return msg_in

            # Not the reply
            self.router.incoming(msg_in)
            for filter in self._filters.matches(msg_in):
                filter.queue.append(msg_in)

    def filter(self, rule, *, queue: Optional[deque] = None, bufsize=1):
        """Create a filter for incoming messages

        Usage::

            with conn.filter(rule) as matches:
                # matches is a deque containing matched messages
                while len(matches) == 0:
                    conn.recv_messages()
                matching_msg = matches.popleft()

        :param jeepney.MatchRule rule: Catch messages matching this rule
        :param collections.deque queue: Matched messages will be added to this
        :param int bufsize: If no deque is passed in, create one with this size
        """
        return FilterHandle(self._filters, rule, queue
                            or deque(maxlen=bufsize))

    def close(self):
        """Close this connection"""
        self.selector.close()
        self.sock.close()
Пример #16
0
 def __init__(self):
     self.auth_parser = SASLParser()
     self.parser = Parser()
     self.router = Router(asyncio.Future)
     self.authentication = asyncio.Future()
     self.unique_name = None
Пример #17
0
class DBusConnection(znc.Socket):
    """Connection to the Signal host's message bus

    Currently, only TCP is supported.
    """
    from .commonweal import put_issuer

    def Init(self, *args, **kwargs):
        self.module = self.GetModule()
        self.debug = self.module.debug
        self.logger = get_logger(self.__class__.__name__)
        # Well-known name (org.asamk.Signal) registered with message bus
        self.has_service = False
        #
        self.__dict__.update(kwargs)
        self.unique_name = None
        #
        self.auth_parser = SASLParserAnonAuth()
        self.parser = Parser()
        #
        FakeFuture.fake_loop = FakeLoop(self.module)
        self.router = Router(FakeFuture)
        #
        if self.debug:
            from jeepney.low_level import HeaderFields

            def on_unhandled(msg):
                member = msg.header.fields[HeaderFields.member]
                if member == "NameAcquired":
                    # This fires before the "Hello" reply callback
                    log_msg = f"Received routine opening signal: {member!r}; "
                else:
                    log_msg = "See 'data_received' entry above for contents"
                self.logger.debug(log_msg)

            self.router.on_unhandled = on_unhandled
        self.authentication = FakeFuture()
        # FIXME explain why this appears twice (see above)
        self.unique_name = None

    def check_subscription(self, service_name=None, member=None):
        """Check if a 'signal-received' callback has been registered

        Without ``member``, return True if any subscriptions exist for
        object described by ``service_name``.
        """
        if service_name is None:
            return bool(self.router.signal_callbacks)
        service = get_msggen(service_name)
        if member:
            key = (service.object_path, service.interface, member)
            return key in self.router.signal_callbacks
        else:
            key = (service.object_path, service.interface)
            return any(k[:-1] == key for k in self.router.signal_callbacks)

    def remove_subscription(self, service_name=None, member=None):
        """Remove a 'signal-received' callback

        Without ``member``, remove all subscriptions registered to
        object described by ``service_name``.
        """
        if service_name is None:
            self.router.signal_callbacks.clear()
            return
        service = get_msggen(service_name)
        if member:
            key = (service.object_path, service.interface, member)
            if key in self.router.signal_callbacks:
                del self.router.signal_callbacks[key]
        else:
            key = (service.object_path, service.interface)
            for k in tuple(self.router.signal_callbacks):
                if k[:-1] == key:
                    del self.router.signal_callbacks[k]

    def add_subscription(self, service_name, member, callback):
        """Add a 'signal-received' callback"""
        service = get_msggen(service_name)
        self.router.subscribe_signal(callback=callback,
                                     path=service.object_path,
                                     interface=service.interface,
                                     member=member)

    def subscribe_incoming(self):
        """Register handler for incoming Signal messages"""
        self.put_issuer("Signal service found")
        self.has_service = True
        #
        if not self.module.config or not self.module.config.settings["obey"]:
            return
        #
        def watch_message_received_cb(msg_body):  # noqa: E306
            if self.debug:
                assert isinstance(msg_body, tuple)
            try:
                self.module.handle_incoming(incoming_NT(*msg_body))
            except Exception:
                self.module.print_traceback()

        #
        def add_message_received_cb():  # noqa: E306
            self.put_issuer("Subscribed to incoming Signal messages")
            if self.debug:
                self.logger.debug("Registering signal callback for "
                                  "'MessageReceived' on 'Signal'")
            self.add_subscription("Signal", "MessageReceived",
                                  watch_message_received_cb)

        #
        if self.debug:
            self.logger.debug("Adding match rule for 'MessageReceived'")
        try:
            self.module.do_subscribe("Signal", "MessageReceived",
                                     add_message_received_cb)
        except Exception:
            self.module.print_traceback()

    def ensure_service(self):
        """Query message bus for Signal service, act accordingly

        For now, this just waits for an announcement of `name
        acquisition`__, then resumes the normal subscription sequence.

        .. __: https://dbus.freedesktop.org/doc/dbus-specification.html
           #bus-messages-name-owner-changed
        """
        service_name = get_msggen("Signal").bus_name
        member = "NameOwnerChanged"

        #
        def watch_name_acquired_cb(msg_body):  # noqa: E306
            if self.debug:
                assert type(msg_body) is tuple
                assert len(msg_body) == 3
                assert all(type(s) is str for s in msg_body)
            if msg_body[0] == service_name:
                self.remove_subscription("DBus", member)
                self.module.do_subscribe("DBus",
                                         member,
                                         remove_name_owner_changed_cb,
                                         remove=True)

        #
        def remove_name_owner_changed_cb():  # noqa: E306
            if self.debug:
                self.logger.debug("Cancelled subscription for "
                                  f"{member} on 'DBus'")
            self.subscribe_incoming()

        #
        def add_name_owner_changed_cb():  # noqa: E306
            if self.debug:
                self.logger.debug("Registering signal callback for "
                                  f"{member} on 'DBus'")
            self.add_subscription("DBus", member, watch_name_acquired_cb)

        #
        def name_has_owner_cb(result):  # noqa: E306
            if self.debug:
                assert type(result) is int
            if result:
                self.subscribe_incoming()
            else:
                self.put_issuer("Waiting for Signal service...")
                self.module.do_subscribe("DBus", member,
                                         add_name_owner_changed_cb)

        #
        wrapped = self.module.make_generic_callback(name_has_owner_cb)
        self.module.do_send("DBus",
                            "NameHasOwner",
                            wrapped,
                            args=(service_name, ))

    def _open_session(self):
        bus = Proxy(get_msggen("DBus"), self)
        hello_reply = bus.Hello()

        def hello_cb(fut):
            if self.debug:
                self.logger.debug("Got hello reply: %r" % fut)
            self.unique_name = fut.result()[0]
            self.put_issuer("Registered with message bus; session id is: %r" %
                            self.unique_name)
            self.ensure_service()

        if self.debug:
            self.logger.debug("Waiting for hello reply: %r" % hello_reply)
        #
        hello_reply.add_done_callback(hello_cb)

    def _authenticated(self):
        self.WriteBytes(BEGIN)
        self.authentication.set_result(True)
        self.data_received = self.data_received_post_auth
        self.data_received(self.auth_parser.buffer)
        if self.debug:
            self.logger.debug("D-Bus connection authenticated")
        self._open_session()

    def data_received(self, data):
        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(
                ValueError(self.auth_parser.error))
        elif self.auth_parser.rejected is not None:
            if b"ANONYMOUS" in self.auth_parser.rejected:
                self.WriteBytes(make_auth_anonymous())
                self.auth_parser.rejected = None
            else:
                self.auth_parser.error = self.auth_parser.rejected

    def format_debug_msg(self, msg):
        from .ootil import OrderedPrettyPrinter as OrdPP
        from jeepney.low_level import Message
        if not hasattr(self, "_opp"):
            self._opp = OrdPP(width=72)
        if not isinstance(msg, Message):
            return self._opp.pformat(msg)
        header = msg.header
        data = {
            "header": (header.endianness, header.message_type,
                       dict(
                           flags=header.flags,
                           version=header.protocol_version,
                           length=header.body_length,
                           serial=header.serial,
                       ), {
                           "fields":
                           {k.name: v
                            for k, v in header.fields.items()}
                       }),
            "body":
            msg.body
        }
        return self._opp.pformat(data)

    def data_received_post_auth(self, data):
        if self.debug:
            num_futs = len(self.router.awaiting_reply)
            log_msg = [f"Futures awaiting reply: {num_futs}"]
        for msg in self.parser.feed(data):
            self.router.incoming(msg)
            if self.debug:
                log_msg.append(self.format_debug_msg(msg))
        if self.debug:
            self.logger.debug("\n".join(log_msg))

    def send_message(self, message):
        if not self.authentication.done():
            # TODO remove if unable to trigger this
            raise RuntimeError("Wait for authentication before sending "
                               "messages")
        future = self.router.outgoing(message)
        data = message.serialise()
        # Logging must happen here, after:
        #   1. Router increments serial cookie
        #   2. Message.serialize() updates the header w. correct body length
        if self.debug:
            num_futs = len(self.router.awaiting_reply)
            log_msg = self.format_debug_msg(message)
            self.logger.debug(f"Futures awaiting reply: {num_futs}\n{log_msg}")
        self.WriteBytes(data)
        return future

    def OnConnected(self):
        self.WriteBytes(b'\0' + make_auth_external())
        self.put_issuer("Connected to: %s:%s" % self.bus_addr)
        self.SetSockName("DBus proxy to signal server")
        self.SetTimeout(0)

    def OnReadData(self, data):
        self.data_received(data)

    def OnDisconnected(self):
        self.put_issuer("Disconnected from %s:%s for session %r" %
                        (*self.bus_addr, self.unique_name))

    def OnShutdown(self):
        name = self.GetSockName()
        if self.debug:
            try:
                self.logger.debug("%r shutting down" % name)
            except ValueError as exc:
                # Only occurs when disconnect teardown is interrupted
                if "operation on closed file" not in repr(exc):
                    raise