diff --git a/source/network/Client.py b/source/network/Client.py index 3671d45..f7a7089 100644 --- a/source/network/Client.py +++ b/source/network/Client.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Any from source.gui import scene from source.network import game_network from source.network.packet import PacketUsername, PacketSettings -from source.network.packet.abc import Packet from source.utils import StoppableThread from source.utils.thread import in_pyglet_context @@ -35,7 +34,7 @@ class Client(StoppableThread): print(f"[Client] Connecté avec {connection}") settings: Any = PacketSettings.from_connection(connection) - PacketUsername(username=self.username).instance_send_connection(connection) + PacketUsername(username=self.username).send_data_connection(connection) packet_username = PacketUsername.from_connection(connection) game_scene = in_pyglet_context( diff --git a/source/network/Host.py b/source/network/Host.py index 5b0cb2a..55338e0 100644 --- a/source/network/Host.py +++ b/source/network/Host.py @@ -44,9 +44,9 @@ class Host(StoppableThread): print(f"[Serveur] Connecté avec {address}") - self.settings.instance_send_connection(connection) + self.settings.send_data_connection(connection) packet_username = PacketUsername.from_connection(connection) - PacketUsername(username=self.username).instance_send_connection(connection) + PacketUsername(username=self.username).send_data_connection(connection) game_scene = in_pyglet_context( self.window.set_scene, diff --git a/source/network/packet/PacketBoatPlaced.py b/source/network/packet/PacketBoatPlaced.py index 3af2f82..f3864f9 100644 --- a/source/network/packet/PacketBoatPlaced.py +++ b/source/network/packet/PacketBoatPlaced.py @@ -1,17 +1,17 @@ from dataclasses import dataclass -from source.network.packet.abc import Packet +from source.network.packet.abc import SimplePacket @dataclass -class PacketBoatPlaced(Packet): +class PacketBoatPlaced(SimplePacket): """ A packet that signal that all the boat of the player have been placed """ - def to_bytes(self): + def to_bytes(self) -> bytes: return b"" @classmethod - def from_bytes(cls, data: bytes): + def from_bytes(cls, data: bytes) -> "PacketBoatPlaced": return cls() diff --git a/source/network/packet/PacketBombPlaced.py b/source/network/packet/PacketBombPlaced.py index 7e30149..2da032f 100644 --- a/source/network/packet/PacketBombPlaced.py +++ b/source/network/packet/PacketBombPlaced.py @@ -2,12 +2,12 @@ import struct from dataclasses import dataclass, field -from source.network.packet.abc import Packet +from source.network.packet.abc import SimplePacket from source.type import Point2D @dataclass -class PacketBombPlaced(Packet): +class PacketBombPlaced(SimplePacket): """ A packet that signal that a bomb have been placed on the board """ @@ -16,11 +16,11 @@ class PacketBombPlaced(Packet): packet_format: str = ">BB" - def to_bytes(self): + def to_bytes(self) -> bytes: x, y = self.position return struct.pack(self.packet_format, x, y) @classmethod - def from_bytes(cls, data: bytes): + def from_bytes(cls, data: bytes) -> "PacketBombPlaced": x, y = struct.unpack(cls.packet_format, data) return cls(position=(x, y)) diff --git a/source/network/packet/PacketBombState.py b/source/network/packet/PacketBombState.py index cb1fc79..4389ae1 100644 --- a/source/network/packet/PacketBombState.py +++ b/source/network/packet/PacketBombState.py @@ -2,12 +2,12 @@ import struct from dataclasses import dataclass, field from source.core.enums import BombState -from source.network.packet.abc import Packet +from source.network.packet.abc import SimplePacket from source.type import Point2D @dataclass -class PacketBombState(Packet): +class PacketBombState(SimplePacket): """ A packet that signal how a bomb exploded on the board """ @@ -17,11 +17,11 @@ class PacketBombState(Packet): packet_format: str = ">BBb" - def to_bytes(self): + def to_bytes(self) -> bytes: x, y = self.position return struct.pack(self.packet_format, x, y, self.bomb_state.value) @classmethod - def from_bytes(cls, data: bytes): + def from_bytes(cls, data: bytes) -> "PacketBombState": x, y, bomb_state = struct.unpack(cls.packet_format, data) return cls(position=(x, y), bomb_state=BombState(bomb_state)) diff --git a/source/network/packet/PacketChat.py b/source/network/packet/PacketChat.py index 014b573..130e581 100644 --- a/source/network/packet/PacketChat.py +++ b/source/network/packet/PacketChat.py @@ -1,40 +1,20 @@ -import socket -import struct from dataclasses import dataclass, field -from typing import Optional -from source.network.packet.abc import Packet +from source.network.packet.abc import VariableLengthBytesPacket @dataclass -class PacketChat(Packet): +class PacketChat(VariableLengthBytesPacket): """ A packet that represent a message from the chat """ message: str = field() - packet_format = ">I" - - def to_bytes(self) -> bytes: - message: bytes = self.message.encode("utf-8") - message_len: int = len(message) - - # envoie la taille du message, suivi des données du message - return struct.pack(f"{self.packet_format}{message_len}s", message_len, message) + @property + def data(self) -> bytes: + return self.message.encode("utf-8") @classmethod - def from_connection(cls, connection: socket.socket) -> "Packet": - message_len, *_ = struct.unpack( - cls.packet_format, - connection.recv(struct.calcsize(cls.packet_format)) - ) - - format_: str = f">{message_len}s" - - message, *_ = struct.unpack( - format_, - connection.recv(struct.calcsize(format_)) - ) - - return cls(message=message.decode("utf-8")) + def from_bytes(cls, data: bytes) -> "PacketChat": + return cls(message=data.decode("utf-8")) diff --git a/source/network/packet/PacketSettings.py b/source/network/packet/PacketSettings.py index c0d75fa..d61d431 100644 --- a/source/network/packet/PacketSettings.py +++ b/source/network/packet/PacketSettings.py @@ -14,7 +14,7 @@ class PacketSettings(Packet): packet_format: str = ">BB?I" - def to_bytes(self): + def to_bytes(self) -> bytes: boats_len: int = len(self.boats_length) return struct.pack( @@ -30,7 +30,7 @@ class PacketSettings(Packet): ) @classmethod - def from_connection(cls, connection: socket.socket) -> "Packet": + def from_connection(cls, connection: socket.socket) -> "PacketSettings": grid_width, grid_height, host_start, boats_len = struct.unpack( cls.packet_format, connection.recv(struct.calcsize(cls.packet_format)) diff --git a/source/network/packet/PacketUsername.py b/source/network/packet/PacketUsername.py index 93767dc..c7b71df 100644 --- a/source/network/packet/PacketUsername.py +++ b/source/network/packet/PacketUsername.py @@ -1,34 +1,17 @@ -import socket -import struct from dataclasses import dataclass, field -from source.network.packet.abc import Packet +from source.network.packet.abc import VariableLengthBytesPacket @dataclass -class PacketUsername(Packet): +class PacketUsername(VariableLengthBytesPacket): username: str = field() - packet_format: str = ">I" - - def to_bytes(self): - username = self.username.encode() - username_len = len(username) - - return struct.pack(f"{self.packet_format}{username_len}s", username_len, username) + @property + def data(self) -> bytes: + return self.username.encode("utf-8") @classmethod - def from_connection(cls, connection: socket.socket) -> "PacketUsername": - username_len, *_ = struct.unpack( - cls.packet_format, - connection.recv(struct.calcsize(cls.packet_format)) - ) + def from_bytes(cls, data: bytes) -> "PacketUsername": + return cls(username=data.decode("utf-8")) - format_: str = f">{username_len}s" - - username, *_ = struct.unpack( - format_, - connection.recv(struct.calcsize(format_)) - ) - - return cls(username=username.decode("utf-8")) diff --git a/source/network/packet/abc/Packet.py b/source/network/packet/abc/Packet.py index f622bc3..4ad7eb0 100644 --- a/source/network/packet/abc/Packet.py +++ b/source/network/packet/abc/Packet.py @@ -1,18 +1,30 @@ import socket -import struct from abc import abstractmethod, ABC +from inspect import isabstract from typing import Type, Optional class Packet(ABC): - packed_header: bytes + """ + A packet that can be sent on a socket. + Multiple subtype of packet can be sent and received in an easier way. + + The to_bytes and from_connection method need to be defined. + """ + + packet_types: set[Type["Packet"]] = set() + + packet_header: bytes packet_id: int = 0 - packet_format: str = ">" def __init_subclass__(cls, **kwargs): cls.packet_header = Packet.packet_id.to_bytes(1, "big") # give a header to the newly created subclass Packet.packet_id = Packet.packet_id + 1 # increment by one the packet header for the next subclass + if not isabstract(cls): + # si la classe n'est pas abstraite, ajoute-la aux types de packet enregistré. + cls.packet_types.add(cls) + @abstractmethod def to_bytes(self) -> bytes: """ @@ -23,14 +35,14 @@ class Packet(ABC): def send_connection(self, connection: socket.socket): """ - Send the packet directly into a socket. + Send the packet data preceded with the header directly into a socket. :param connection: the socket where to send the packet to. """ connection.send(self.packet_header + self.to_bytes()) - def instance_send_connection(self, connection: socket.socket): + def send_data_connection(self, connection: socket.socket): """ - Send the packet directly into a socket. + Send the packet data directly into a socket. :param connection: the socket where to send the packet to. """ connection.send(self.to_bytes()) @@ -42,20 +54,12 @@ class Packet(ABC): :param packet_header: the header to find the corresponding subclass :return: the class associated with this header """ + return next(filter( lambda subcls: subcls.packet_header == packet_header, - cls.__subclasses__() + cls.packet_types )) - @classmethod - def from_bytes(cls, data: bytes) -> "Packet": - """ - Convert a bytes object into a packet. - :param data: the data to convert into a packet. Should be "packet_size" long. - :return: a packet corresponding to the bytes. - """ - pass - @classmethod def type_from_connection(cls, connection: socket.socket) -> Optional[Type["Packet"]]: try: @@ -67,12 +71,11 @@ class Packet(ABC): return cls.type_from_header(packet_header) @classmethod + @abstractmethod def from_connection(cls, connection: socket.socket) -> "Packet": """ Receive a packet from a socket. :param connection: the socket where to get the data from :return: the packet, or None if there was nothing in the socket to receive. """ - - packet_size: int = struct.calcsize(cls.packet_format) - return cls.from_bytes(connection.recv(packet_size)) + pass diff --git a/source/network/packet/abc/SimplePacket.py b/source/network/packet/abc/SimplePacket.py new file mode 100644 index 0000000..26a5039 --- /dev/null +++ b/source/network/packet/abc/SimplePacket.py @@ -0,0 +1,31 @@ +import socket +import struct +from abc import ABC, abstractmethod + +from source.network.packet.abc import Packet + + +class SimplePacket(Packet, ABC): + """ + A packet with a simple packet format. + Only the from_bytes and to_bytes method need to be implemented. + """ + + packet_format: str = ">" + + @classmethod + @abstractmethod + def from_bytes(cls, data: bytes) -> "Packet": + """ + Convert a bytes object into a packet. + :param data: the data to convert into a packet. Should be "packet_size" long. + :return: a packet corresponding to the bytes. + """ + pass + + @classmethod + def from_connection(cls, connection: socket.socket) -> "Packet": + # récupère la taille du packet en fonction du format et charge + # les données dans une nouvelle instance. + packet_size: int = struct.calcsize(cls.packet_format) + return cls.from_bytes(connection.recv(packet_size)) diff --git a/source/network/packet/abc/VariableLengthBytesPacket.py b/source/network/packet/abc/VariableLengthBytesPacket.py new file mode 100644 index 0000000..741d950 --- /dev/null +++ b/source/network/packet/abc/VariableLengthBytesPacket.py @@ -0,0 +1,47 @@ +import socket +import struct +from abc import ABC, abstractmethod + +from source.network.packet.abc import Packet + + +class VariableLengthBytesPacket(Packet, ABC): + """ + A Packet that represent a single value that can be encoded with a variable length. + The property "data" and the method "from_bytes" need to be defined. + """ + + packet_format: str = ">I" + + @property + @abstractmethod + def data(self) -> bytes: + pass + + def to_bytes(self) -> bytes: + data: bytes = self.data + data_len: int = len(data) + + # envoie la taille du message, suivi des données du message + return struct.pack(f"{self.packet_format}{data_len}s", data_len, data) + + @classmethod + @abstractmethod + def from_bytes(cls, data: bytes): + pass + + @classmethod + def from_connection(cls, connection: socket.socket) -> "Packet": + data_len, *_ = struct.unpack( + cls.packet_format, + connection.recv(struct.calcsize(cls.packet_format)) + ) + + format_: str = f">{data_len}s" + + data, *_ = struct.unpack( + format_, + connection.recv(struct.calcsize(format_)) + ) + + return cls.from_bytes(data) diff --git a/source/network/packet/abc/__init__.py b/source/network/packet/abc/__init__.py index a7143d4..cea164c 100644 --- a/source/network/packet/abc/__init__.py +++ b/source/network/packet/abc/__init__.py @@ -1 +1,4 @@ from .Packet import Packet + +from .SimplePacket import SimplePacket +from .VariableLengthBytesPacket import VariableLengthBytesPacket