示例#1
0
    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())
示例#2
0
    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(
        )
示例#3
0
    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>."
            ))
示例#4
0
    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(),
            )))
示例#5
0
    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
示例#6
0
    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)
示例#7
0
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