Exemple #1
0
class Header(Comparable):
    """Create a PyGaSe package header.

    # Arguments
    sequence (int): package sequence number
    ack (int): sequence number of the last received package
    ack_bitfield (str): A 32 character string representing the 32 sequence numbers prior to the last one received,
        with the first character corresponding the packge directly preceding it and so forth.
        '1' means that package has been received, '0' means it hasn't.

    # Attributes
    sequence (int): see corresponding constructor argument
    ack (int): see corresponding constructor argument
    ack_bitfield (str): see corresponding constructor argument

    ---
    Sequence numbers: A sequence of 0 means no packages have been sent or received.
    After 65535 sequence numbers wrap around to 1, so they can be stored in 2 bytes.

    """
    def __init__(self, sequence: int, ack: int, ack_bitfield: str):
        self.sequence = Sqn(sequence)
        self.ack = Sqn(ack)
        self.ack_bitfield = ack_bitfield

    def to_bytearray(self) -> bytearray:
        """Return 12 bytes representing the header."""
        result = bytearray(PROTOCOL_ID)
        result.extend(self.sequence.to_sqn_bytes())
        result.extend(self.ack.to_sqn_bytes())
        result.extend(int(self.ack_bitfield, 2).to_bytes(4, "big"))
        return result

    def destructure(self) -> tuple:
        """Return the tuple `(sequence, ack, ack_bitfield)`."""
        return (self.sequence, self.ack, self.ack_bitfield)

    @classmethod
    def deconstruct_datagram(cls, datagram: bytes) -> tuple:
        """Return a tuple containing the header and the rest of the datagram.

        # Arguments
        datagram (bytes): serialized PyGaSe package to deconstruct

        # Returns
        tuple: `(header, payload)` with `payload` being a bytestring of the rest of the datagram

        """
        if datagram[:4] != PROTOCOL_ID:
            raise ProtocolIDMismatchError
        sequence = Sqn.from_sqn_bytes(datagram[4:6])
        ack = Sqn.from_sqn_bytes(datagram[6:8])
        ack_bitfield = bin(int.from_bytes(datagram[8:12], "big"))[2:].zfill(32)
        payload = datagram[12:]
        return (cls(sequence, ack, ack_bitfield), payload)
Exemple #2
0
class ClientPackage(Package):
    """Subclass of #Package for packages sent by PyGaSe clients.

    # Arguments
    time_order (int): the clients last known time order of the game state

    # Attributes
    time_order (int): see corresponding constructor argument

    """
    def __init__(self, header: Header, time_order: int, events: list = None):
        super().__init__(header, events)
        self.time_order = Sqn(time_order)

    def to_datagram(self) -> bytes:
        """Override `Package.to_datagram` to include `time_order`."""
        if self._datagram is not None:
            return self._datagram
        datagram = self.header.to_bytearray()
        # The header makes up the first 12 bytes of the package
        datagram.extend(self.time_order.to_sqn_bytes())
        datagram.extend(self._create_event_block())
        datagram = datagram
        if len(datagram) > self._max_size:
            raise OverflowError("Package exceeds the maximum size of " +
                                str(self._max_size) + " bytes.")
        self._datagram = bytes(datagram)
        return self._datagram

    @classmethod
    def from_datagram(cls, datagram: bytes) -> "ClientPackage":
        """Override #Package.from_datagram to include `time_order`."""
        header, payload = Header.deconstruct_datagram(datagram)
        time_order = Sqn.from_sqn_bytes(payload[:2])
        payload = payload[2:]
        events = cls._read_out_event_block(payload)
        result = cls(header, time_order, events)
        result._datagram = datagram  # pylint: disable=protected-access
        return result