async def _ping_loop(self, interval: float): """ Loop that will send PING packets to the server at a predefined interval. :param interval: interval to send ping packets at (in seconds). """ while True: await asyncio.sleep(interval) self._send( PacketHeader(client_id=u32(self._cid), flags=PacketFlags.PING).serialize())
def __init__( self, client_id: u32 = None, trans_num: u32 = None, flags: PacketFlags = PacketFlags.NONE, semantics: InvocationSemantics = InvocationSemantics.AT_LEAST_ONCE, method_ordinal: u32 = None, ): """ Create a new packet header. :param client_id: client identifier. :param trans_num: transaction number. :param flags: flags associated with the packet. :param semantics: invocation semantics. :param method_ordinal: ordinal of method to invoke on the remote side. """ self._client_id = client_id if client_id is not None else u32() self._trans_num = trans_num if trans_num is not None else u32() self._flags = flags self._semantics = semantics self._method_ordinal = method_ordinal if method_ordinal is not None else u32( )
async def register_notifications(self): """ register_notifications handles the "register" command. """ facility = await self._prompt_facility() monitoring_time = await self._prompt_monitoring_time() res = await self._proxy.register_notification(u32(self._cbport), u64(0), String(facility), monitoring_time) if "error" in res: self._print_error(res.value) return self._known_facilities.add(facility) clear() print( HTML( f"<ansigreen>Successfully</ansigreen> registered for notifications regarding <u>{facility}</u>." ))
async def _prompt_monitoring_time(self) -> u32: """ Prompt the client for a monitoring time, in seconds. """ class U32Validator(Validator): def validate(self, document: Document): s = document.text try: v = int(s) except ValueError: raise ValidationError(0, "expecting an integer") vmin, vmax = u32.min(), u32.max() if not (vmin <= v <= vmax): raise ValidationError( 0, f"value must be within [{vmin}, {vmax}]") return u32( int(await self._session.prompt_async( HTML("<i>Monitoring <b>Time</b>(s)</i>? >>> "), validator=U32Validator(), )))
async def call( self, ordinal: int, args: bytes, ) -> bytes: """ Call a remote method. Will wait for a connection to be established if not already connected. :param ordinal: ordinal of the remote method. :param args: serialized arguments for the remote method. :return: serialized return value for the remote method. :raises exceptions.RPCConnectionClosedError: if the connection has been closed. """ # timeout & retries will be checked by set_semantics(). if not self: await self.wait_connected() # cache all configuration parameters so they remain consistent # for this call. timeout = self.timeout if (self.timeout is not None) and (self.retries > 0): timeout /= self.retries tries = self.retries + 1 semantics = self.semantics # Generate initial header. tid = self._txid.next().copy() hdr = PacketHeader( u32(self._cid), trans_num=tid, semantics=semantics, method_ordinal=u32(ordinal), ) for i in range(tries): if i: hdr.flags |= PacketFlags.REPLAYED self._send(hdr.serialize() + args) # shouldn't race because it's single threaded, and we don't hit an await # till the future gets submitted. try: rhdr, payload = await asyncio.wait_for( self._router.listen(tid.value), timeout) except asyncio.TimeoutError: continue if not len(payload): raise exceptions.InvalidReplyError("zero-length payload") try: status = ExecutionStatus(payload[0]) except ValueError: raise exceptions.InvalidReplyError("execution status") if semantics is InvocationSemantics.AT_MOST_ONCE: # send acknowledgement to optimize result storage # doesn't matter if it gets lost because the server will age it # out anyway. hdr.flags = PacketFlags.ACK_REPLY self._send(hdr.serialize()) excc = estatus_to_exception(status) if excc is not None: raise excc() return payload[1:] else: raise asyncio.TimeoutError
def datagram_received(self, data: bytes, addr: AddressType): """ Process an incoming datagram. :param data: data received. :param addr: datagram source address. """ try: hdr = PacketHeader.deserialize(data) payload = data[hdr.LENGTH:] except ValueError: # Nothing we can do, packet cannot be decoded. return # Filter replies if hdr.is_reply: return # Check for an RST. if hdr.flags & PacketFlags.RST: # Disconnect active client. if addr in self._clients: self.disconnect_client(addr, False) return # Non RST, continuation of previous session or new connection. cid = hdr.client_id.value # New connection. if not cid: if addr in self._clients: # Delete stale connection for the same network address. self.disconnect_client(addr, False) skel = self._skel_fac(addr) # Generate random CID to avoid collisions with previously connected clients. cid = random.randint(u32.min() + 1, u32.max()) self._clients[addr] = cid, ConnectedClient( caddr=addr, skel=skel, transport=self._transport, timeout_callback=self.disconnect_client, inactivity_timeout=self._inactivity_timeout, result_cache_timeout=self._result_cache_timeout, ) # Register and send new CID rep = PacketHeader(client_id=u32(cid), flags=(PacketFlags.REPLY | PacketFlags.CHANGE_CID)) self._transport.sendto(rep.serialize(), addr) return # Existing connection if addr not in self._clients: # Unknown client # Client thinks it's using an open connection but server # has no record of that connection. Reset to signal that. self._send_rst(addr) return scid, cclient = self._clients[addr] if scid != cid: # Unknown client, also one that thinks it's using an open connection # but server also doesn't have a record of that connection. Reset. # todo: need to notify servers of shutdown! self.disconnect_client(addr) return cclient.process(hdr, payload)
class PacketHeader: # Length of the packet header LENGTH: Final = 3 * u32().size + 1 + u32().size def __init__( self, client_id: u32 = None, trans_num: u32 = None, flags: PacketFlags = PacketFlags.NONE, semantics: InvocationSemantics = InvocationSemantics.AT_LEAST_ONCE, method_ordinal: u32 = None, ): """ Create a new packet header. :param client_id: client identifier. :param trans_num: transaction number. :param flags: flags associated with the packet. :param semantics: invocation semantics. :param method_ordinal: ordinal of method to invoke on the remote side. """ self._client_id = client_id if client_id is not None else u32() self._trans_num = trans_num if trans_num is not None else u32() self._flags = flags self._semantics = semantics self._method_ordinal = method_ordinal if method_ordinal is not None else u32( ) @classmethod def deserialize(cls: Type["PacketHeader"], data: bytes) -> "PacketHeader": """ Recover a packet header from raw bytes. :param data: data representing the packet header. """ if len(data) < cls.LENGTH: raise ValueError("data too short") if data[:len(HEADER_MAGIC)] != HEADER_MAGIC: raise ValueError("packet magic number mismatch") off = len(HEADER_MAGIC) cid = u32.deserialize(data[off:]) off += cid.size tid = u32.deserialize(data[off:]) off += tid.size flags = PacketFlags(data[off] & 0x3F) semantics = InvocationSemantics(data[off] >> 6) off += 1 ordinal = u32.deserialize(data[off:]) return cls(cid, tid, flags, semantics, ordinal) @property def size(self) -> int: return self.LENGTH @property def client_id(self) -> u32: return self._client_id @client_id.setter def client_id(self, new: u32): self._client_id = new @property def trans_num(self) -> u32: return self._trans_num @trans_num.setter def trans_num(self, new: u32): self._trans_num = new @property def flags(self) -> PacketFlags: return self._flags @flags.setter def flags(self, new: PacketFlags): self._flags = new @property def semantics(self) -> InvocationSemantics: return self._semantics @semantics.setter def semantics(self, new: InvocationSemantics): self._semantics = new @property def method_ordinal(self) -> u32: return self._method_ordinal @method_ordinal.setter def method_ordinal(self, new: u32): self._method_ordinal = new @property def is_reply(self) -> bool: return bool(self.flags & self.flags.REPLY) def serialize(self) -> bytes: """ Convert this header to a sequence of bytes. """ out = bytearray() out.extend(HEADER_MAGIC) out.extend(self.client_id.serialize()) out.extend(self.trans_num.serialize()) out.append(self.flags.value | (self.semantics.value << 6)) out.extend(self.method_ordinal.serialize()) return out