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
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 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 __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)
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, )
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 __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 __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]
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()
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)
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
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()
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()
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', )
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()
def __init__(self): self.auth_parser = SASLParser() self.parser = Parser() self.router = Router(asyncio.Future) self.authentication = asyncio.Future() self.unique_name = None
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