From 8cb16f25c2ec72479497b170cb7c9a93f8c8a209 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 25 Jun 2024 19:48:14 +0200 Subject: [PATCH] Raise InvalidServerVersion when Server is too old too (#772) --- matter_server/client/client.py | 43 ++++++++++++++---------------- matter_server/client/connection.py | 19 +++++++++---- matter_server/client/exceptions.py | 8 ++++++ matter_server/server/server.py | 4 +++ 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/matter_server/client/client.py b/matter_server/client/client.py index 719ccadf..3777d15c 100644 --- a/matter_server/client/client.py +++ b/matter_server/client/client.py @@ -37,7 +37,7 @@ SuccessResultMessage, ) from .connection import MatterClientConnection -from .exceptions import ConnectionClosed, InvalidServerVersion, InvalidState +from .exceptions import ConnectionClosed, InvalidState, ServerVersionTooOld from .models.node import ( MatterFabricData, MatterNode, @@ -509,14 +509,13 @@ async def interview_node(self, node_id: int) -> None: """Interview a node.""" await self.send_command(APICommand.INTERVIEW_NODE, node_id=node_id) - async def send_command( + def _prepare_message( self, command: str, require_schema: int | None = None, **kwargs: Any, - ) -> Any: - """Send a command and get a response.""" - if not self.connection.connected or not self._loop: + ) -> CommandMessage: + if not self.connection.connected: raise InvalidState("Not connected") if ( @@ -524,16 +523,28 @@ async def send_command( and self.server_info is not None and require_schema > self.server_info.schema_version ): - raise InvalidServerVersion( + raise ServerVersionTooOld( "Command not available due to incompatible server version. Update the Matter " - f"Server to a version that supports at least api schema {require_schema}." + f"Server to a version that supports at least api schema {require_schema}.", ) - message = CommandMessage( + return CommandMessage( message_id=uuid.uuid4().hex, command=command, args=kwargs, ) + + async def send_command( + self, + command: str, + require_schema: int | None = None, + **kwargs: Any, + ) -> Any: + """Send a command and get a response.""" + if not self._loop: + raise InvalidState("Not connected") + + message = self._prepare_message(command, require_schema, **kwargs) future: asyncio.Future[Any] = self._loop.create_future() self._result_futures[message.message_id] = future await self.connection.send_message(message) @@ -549,22 +560,8 @@ async def send_command_no_wait( **kwargs: Any, ) -> None: """Send a command without waiting for the response.""" - if not self.server_info: - raise InvalidState("Not connected") - if ( - require_schema is not None - and require_schema > self.server_info.schema_version - ): - raise InvalidServerVersion( - "Command not available due to incompatible server version. Update the Matter " - f"Server to a version that supports at least api schema {require_schema}." - ) - message = CommandMessage( - message_id=uuid.uuid4().hex, - command=command, - args=kwargs, - ) + message = self._prepare_message(command, require_schema, **kwargs) await self.connection.send_message(message) async def get_diagnostics(self) -> ServerDiagnostics: diff --git a/matter_server/client/connection.py b/matter_server/client/connection.py index afe78430..ae5d9754 100644 --- a/matter_server/client/connection.py +++ b/matter_server/client/connection.py @@ -26,9 +26,10 @@ ConnectionClosed, ConnectionFailed, InvalidMessage, - InvalidServerVersion, InvalidState, NotConnected, + ServerVersionTooNew, + ServerVersionTooOld, ) LOGGER = logging.getLogger(f"{__package__}.connection") @@ -79,14 +80,22 @@ async def connect(self) -> None: info = cast(ServerInfoMessage, await self.receive_message_or_raise()) self.server_info = info - # basic check for server schema version compatibility + if info.schema_version < SCHEMA_VERSION: + # The client schema is too new, the server can't handle it yet + await self._ws_client.close() + raise ServerVersionTooOld( + f"Matter schema version is incompatible: {SCHEMA_VERSION}, " + f"the server supports at most {info.schema_version} " + "- update the Matter server to a more recent version or downgrade the client." + ) + if info.min_supported_schema_version > SCHEMA_VERSION: - # our schema version is too low and can't be handled by the server anymore. + # The client schema version is too low and can't be handled by the server anymore await self._ws_client.close() - raise InvalidServerVersion( + raise ServerVersionTooNew( f"Matter schema version is incompatible: {SCHEMA_VERSION}, " f"the server requires at least {info.min_supported_schema_version} " - " - update the Matter client to a more recent version or downgrade the server." + "- update the Matter client to a more recent version or downgrade the server." ) LOGGER.info( diff --git a/matter_server/client/exceptions.py b/matter_server/client/exceptions.py index 6019ebe0..a90cb874 100644 --- a/matter_server/client/exceptions.py +++ b/matter_server/client/exceptions.py @@ -53,3 +53,11 @@ class InvalidMessage(MatterClientException): class InvalidServerVersion(MatterClientException): """Exception raised when connected to server with incompatible version.""" + + +class ServerVersionTooOld(InvalidServerVersion): + """Exception raised when connected to server with is too old to support this client.""" + + +class ServerVersionTooNew(InvalidServerVersion): + """Exception raised when connected to server with is too new for this client.""" diff --git a/matter_server/server/server.py b/matter_server/server/server.py index d92f4a43..c04b2fec 100644 --- a/matter_server/server/server.py +++ b/matter_server/server/server.py @@ -131,6 +131,10 @@ def __init__( self.command_handlers: dict[str, APICommandHandler] = {} self._device_controller: MatterDeviceController | None = None self._subscribers: set[EventCallBackType] = set() + if MIN_SCHEMA_VERSION > SCHEMA_VERSION: + raise RuntimeError( + "Minimum supported schema version can't be higher than current schema version." + ) @cached_property def device_controller(self) -> MatterDeviceController: