示例#1
0
 def test_symmetry(self):
     s = Sqn(1)
     greater = 0
     lower = 0
     for i in range(1, Sqn._max_sequence + 1):
         if Sqn(i) > s:
             greater += 1
         elif Sqn(i) < s:
             lower += 1
     assert greater > 0 and greater == lower
示例#2
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)
示例#3
0
 def test_recv_duplicate_package_out_of_sequence(self):
     connection = Connection(("host", 1234), None)
     connection.remote_sequence = Sqn(1000)
     connection.ack_bitfield = "1" * 32
     with pytest.raises(DuplicateSequenceError):
         curio.run(
             connection._recv,
             Package(Header(sequence=990, ack=500, ack_bitfield="1" * 32)))
示例#4
0
 def test_recv_second_package(self):
     connection = Connection(("host", 1234), None)
     connection.remote_sequence = Sqn(1)
     connection.ack_bitfield = "0" * 32
     curio.run(connection._recv,
               Package(Header(sequence=2, ack=1, ack_bitfield="0" * 32)))
     assert connection.remote_sequence == 2
     assert connection.ack_bitfield == "1" + "0" * 31
示例#5
0
    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)
示例#6
0
 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
示例#7
0
 def __init__(self, remote_address: tuple, event_handler, event_wire=None):
     logger.debug(
         f"Creating connection instance for remote address {remote_address}."
     )
     self.remote_address = remote_address
     self.event_handler = event_handler
     self.event_wire = event_wire
     self.local_sequence = Sqn(0)
     self.remote_sequence = Sqn(0)
     self.ack_bitfield = "0" * 32
     self.latency = 0.0
     self.status = ConnectionStatus.get("Disconnected")
     self.quality = "good"  # this is used for congestion avoidance
     self._package_interval = self._package_intervals["good"]
     self._outgoing_event_queue = curio.UniversalQueue()
     self._incoming_event_queue = curio.UniversalQueue()
     self._pending_acks: dict = {}
     self._event_callback_sequence = Sqn(0)
     self._events_with_callbacks: dict = {}
     self._event_callbacks: dict = {}
     self._last_recv = time.time()
示例#8
0
 def test_recv_three_packages_arrive_out_of_sequence(self):
     connection = Connection(("host", 1234), None)
     connection.remote_sequence = Sqn(100)
     connection.ack_bitfield = "0110" + "1" * 28
     curio.run(
         connection._recv,
         Package(Header(sequence=101, ack=100, ack_bitfield="1" * 32)))
     assert connection.remote_sequence == 101
     assert connection.ack_bitfield == "10110" + "1" * 27
     curio.run(connection._recv,
               Package(Header(sequence=99, ack=100, ack_bitfield="1" * 32)))
     assert connection.remote_sequence == 101
     assert connection.ack_bitfield == "11110" + "1" * 27
     curio.run(connection._recv,
               Package(Header(sequence=96, ack=101, ack_bitfield="1" * 32)))
     assert connection.remote_sequence == 101
     assert connection.ack_bitfield == "1" * 32
示例#9
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
示例#10
0
 def test_send_package(self):
     send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     recv_socket.bind(("localhost", 0))
     connection = Connection(recv_socket.getsockname(), None)
     assert connection.local_sequence == 0
     curio.run(connection._send_next_package, send_socket)
     data = curio.run(recv_socket.recv, Package._max_size)
     package = Package.from_datagram(data)
     assert package.header.sequence == 1 and connection.local_sequence == 1
     assert package.header.ack == 0 and package.header.ack_bitfield == "0" * 32
     curio.run(connection._send_next_package, send_socket)
     data = curio.run(recv_socket.recv, Package._max_size)
     package = Package.from_datagram(data)
     assert package.header.sequence == 2 and connection.local_sequence == 2
     assert package.header.ack == 0 and package.header.ack_bitfield == "0" * 32
     connection.local_sequence = Sqn.get_max_sequence()
     curio.run(connection._send_next_package, send_socket)
     data = curio.run(recv_socket.recv, Package._max_size)
     package = Package.from_datagram(data)
     assert package.header.sequence == 1 and connection.local_sequence == 1
     assert package.header.ack == 0 and package.header.ack_bitfield == "0" * 32
     curio.run(send_socket.close)
     curio.run(recv_socket.close)
示例#11
0
 def test_initialize_with_overflowing_value(self):
     for i in range(Sqn._max_sequence + 1, 2 * Sqn._max_sequence):
         with pytest.raises(ValueError) as error:
             Sqn(i)
         assert str(error.value) == "value exceeds maximum sequence number"
示例#12
0
 def test_add_within_sequence_with_ints(self):
     s = Sqn(0)
     for i in range(1, Sqn._max_sequence + 1):
         s += 1
         assert s == i
         assert s.__class__ == Sqn
示例#13
0
 def __init__(self, time_order: int, **kwargs):
     self.__dict__ = kwargs
     self.time_order = Sqn(time_order)
示例#14
0
 def test_sequence_wrap_around_with_int(self):
     s = Sqn(Sqn._max_sequence)
     s += 1
     assert s == 1
示例#15
0
 def test_initialize_negative_values(self):
     for i in range(-1, -(Sqn._max_sequence + 2), -1):
         with pytest.raises(ValueError) as error:
             Sqn(i)
         assert str(error.value) == "sequence numbers must not be negative"
示例#16
0
 def test_small_difference_around_sequence_edge(self):
     s1 = Sqn(2)
     s2 = Sqn(Sqn._max_sequence - 2)
     assert s2 - s1 == -4
     assert s1 - s2 == 4
     assert s1 > s2 and s2 < s1
示例#17
0
 def test_small_difference_within_sequence(self):
     s1 = Sqn(2)
     s2 = Sqn(5)
     assert s2 - s1 == 3
     assert s1 - s2 == -3
     assert s2 > s1 and s1 < s2
示例#18
0
 def test_add_negative_int_with_larger_norm(self):
     s = Sqn(2)
     with pytest.raises(ValueError) as error:
         s += -3
     assert str(error.value) == "sequence numbers must not be negative"
示例#19
0
 def test_add_negative_int(self):
     s = Sqn(2)
     s += -1
     assert s == 1 and s.__class__ == Sqn
示例#20
0
 def __init__(self, header: Header, time_order: int, events: list = None):
     super().__init__(header, events)
     self.time_order = Sqn(time_order)
示例#21
0
 def __init__(self, time_order: int = 0, game_status: int = GameStatus.get("Paused"), **kwargs):
     self.__dict__ = kwargs
     self.game_status = game_status
     self.time_order = Sqn(time_order)
示例#22
0
 def __init__(self, sequence: int, ack: int, ack_bitfield: str):
     self.sequence = Sqn(sequence)
     self.ack = Sqn(ack)
     self.ack_bitfield = ack_bitfield
示例#23
0
 def test_sequence_wrap_around(self):
     s = Sqn(Sqn._max_sequence)
     s += Sqn(1)
     assert s == 1
示例#24
0
 def test_bytes(self):
     for i in range(2 * Sqn._bytesize):
         b = Sqn(i).to_sqn_bytes()
         assert len(b) == Sqn._bytesize
         assert b.__class__ == bytes
         assert Sqn.from_sqn_bytes(b) == Sqn(i)
示例#25
0
 def test_initialize_valid_values(self):
     for i in range(Sqn._max_sequence + 1):
         s = Sqn(i)
         assert s == i
示例#26
0
 def from_bytes(cls, bytepack: bytes) -> "GameStateUpdate":
     """Extend `Sendable.from_bytes` to make sure time_order is of type `Sqn`."""
     update = super().from_bytes(bytepack)
     update.time_order = Sqn(update.time_order)  # pylint: disable=no-member
     return update
示例#27
0
 def test_large_distance(self):
     assert Sqn(50000) - Sqn(20000) == 30000
     assert Sqn(Sqn._max_sequence - 100) - Sqn(20000) == -20100