diff --git a/declearn/communication/api/backend/_handler.py b/declearn/communication/api/backend/_handler.py index 88c5c95d73d809aa8a0457f79833eac8821643d0..f200850baf7a16cd4c4de5ae5c602ecc370e9baa 100644 --- a/declearn/communication/api/backend/_handler.py +++ b/declearn/communication/api/backend/_handler.py @@ -29,6 +29,8 @@ from declearn.communication.api.backend.actions import ( ActionMessage, Drop, Join, + LegacyMessageError, + LegacyReject, Ping, Recv, Reject, @@ -103,9 +105,13 @@ class MessagesHandler: message = parse_action_from_string(string) except (KeyError, TypeError, ValueError) as exc: self.logger.info( - "%s encountered while parsing received message: %s", repr(exc) + "Exception encountered while parsing received message: %s", + repr(exc), ) return Reject(flags.INVALID_MESSAGE) + except LegacyMessageError as exc: + self.logger.info(repr(exc)) + return LegacyReject() # Case: join request from a (new) client. Handle it. if isinstance(message, Join): return await self._handle_join_request(message, context) @@ -169,9 +175,7 @@ class MessagesHandler: message: Join, ) -> Optional[Reject]: """Return an 'Error' if a 'JoinRequest' is of incompatible version.""" - if message.version is None: - message.version = "<2.4" - elif message.version.split(".")[:2] == VERSION.split(".")[:2]: + if message.version.split(".")[:2] == VERSION.split(".")[:2]: return None self.logger.info( "Received a registration request under name %s, that is " diff --git a/declearn/communication/api/backend/actions.py b/declearn/communication/api/backend/actions.py index e5c28d7d8fe9d4fd218948a01ed855dcc977eca9..974e2f062c930503ea40f03760b2fe13ec4c2a3e 100644 --- a/declearn/communication/api/backend/actions.py +++ b/declearn/communication/api/backend/actions.py @@ -37,11 +37,16 @@ import dataclasses import json from typing import Optional + +from declearn.version import VERSION + __all__ = [ "Accept", "ActionMessage", "Drop", "Join", + "LegacyReject", + "LegacyMessageError", "Ping", "Recv", "Reject", @@ -50,6 +55,10 @@ __all__ = [ ] +class LegacyMessageError(Exception): + """Custom exception to denote legacy Message being received.""" + + @dataclasses.dataclass class ActionMessage(metaclass=abc.ABCMeta): """Abstract base class for fundamental messages.""" @@ -82,7 +91,7 @@ class Join(ActionMessage): """Client action message to request joining a server.""" name: str - version: Optional[str] = None + version: str @dataclasses.dataclass @@ -153,6 +162,10 @@ def parse_action_from_string( except json.JSONDecodeError as exc: raise ValueError("Failed to parse 'ActionMessage' string.") from exc if "action" not in data: + if "typekey" in data: + raise LegacyMessageError( + f"Received a legacy message with type '{data['typekey']}'." + ) raise ValueError( "Failed to parse 'ActionMessage' string: no 'action' key." ) @@ -164,3 +177,21 @@ def parse_action_from_string( f"'{data['action']}' key." ) return cls(**data) + + +@dataclasses.dataclass +class LegacyReject(ActionMessage): + """Server action to reject a legacy client's (registration) message. + + This message will be serialized in a way that is compatible with the + legacy message parser, but not with the current one. + """ + + def to_string( + self, + ) -> str: + message = ( + "Cannot communicate due to the DecLearn version in use. " + f"Please update to `declearn ~= {VERSION}`." + ) + return json.dumps({"typekey": "error", "message": message})