""" Module for Serialization and Deserialization of a KNX Connect Response information. Connect requests are used to start a new tunnel connection on a KNX/IP device. With a Connect Response the receiving party acknowledges the valid processing of the request, assigns a communication channel and an individual address for the client. """ from __future__ import annotations from xknx.exceptions import CouldNotParseKNXIP from xknx.telegram import IndividualAddress from .body import KNXIPBodyResponse from .error_code import ErrorCode from .hpai import HPAI from .knxip_enum import ConnectRequestType, KNXIPServiceType class ConnectResponse(KNXIPBodyResponse): """Representation of a KNX Connect Response.""" SERVICE_TYPE = KNXIPServiceType.CONNECT_RESPONSE def __init__( self, communication_channel: int = 0, status_code: ErrorCode = ErrorCode.E_NO_ERROR, data_endpoint: HPAI | None = None, crd: ConnectResponseData | None = None, ) -> None: """Initialize ConnectResponse class.""" self.communication_channel = communication_channel self.status_code = status_code self.data_endpoint = data_endpoint or HPAI() self.crd = crd or ConnectResponseData() def calculated_length(self) -> int: """Get length of KNX/IP body.""" return 2 + HPAI.LENGTH + self.crd.calculated_length() def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" self.communication_channel = raw[0] self.status_code = ErrorCode(raw[1]) pos = 2 if self.status_code == ErrorCode.E_NO_ERROR: pos += self.data_endpoint.from_knx(raw[pos:]) pos += self.crd.from_knx(raw[pos:]) else: # do not parse HPAI and CRD in case of errors - just check length pos = len(raw) return pos def to_knx(self) -> bytes: """Serialize to KNX/IP raw data.""" return ( bytes((self.communication_channel, self.status_code.value)) + self.data_endpoint.to_knx() + self.crd.to_knx() ) def __repr__(self) -> str: """Return object as readable string.""" return ( "' ) class ConnectResponseData: """Representation of a KNX Connect Response Data block (CRD).""" CRD_LENGTH = 2 CRD_TUNNEL_LENGTH = 4 def __init__( self, request_type: ConnectRequestType = ConnectRequestType.TUNNEL_CONNECTION, individual_address: IndividualAddress | None = None, ) -> None: """Initialize ConnectResponseData object.""" self.request_type = request_type self.individual_address = individual_address def _is_tunnel_crd(self) -> bool: return self.request_type == ConnectRequestType.TUNNEL_CONNECTION def calculated_length(self) -> int: """Get length of KNX/IP body.""" return ( ConnectResponseData.CRD_TUNNEL_LENGTH if self._is_tunnel_crd() else ConnectResponseData.CRD_LENGTH ) def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" crd_length = raw[0] if len(raw) < crd_length: raise CouldNotParseKNXIP("CRD has wrong length") if crd_length < ConnectResponseData.CRD_LENGTH: raise CouldNotParseKNXIP("CRD length too small") self.request_type = ConnectRequestType(raw[1]) if self._is_tunnel_crd(): if crd_length != ConnectResponseData.CRD_TUNNEL_LENGTH: raise CouldNotParseKNXIP("CRD has wrong length") self.individual_address = IndividualAddress.from_knx(raw[2:4]) elif crd_length != ConnectResponseData.CRD_LENGTH: raise CouldNotParseKNXIP("CRD has wrong length") return crd_length def to_knx(self) -> bytes: """Serialize CRD (Connect Response Data Block).""" _crd = bytes( ( self.calculated_length(), self.request_type.value, ) ) if self._is_tunnel_crd(): assert self.individual_address is not None return _crd + self.individual_address.to_knx() return _crd def __eq__(self, other: object) -> bool: """Equal operator.""" return self.__dict__ == other.__dict__ def __repr__(self) -> str: """Return object as readable string.""" _address = ( f'individual_address="{self.individual_address}" ' if self._is_tunnel_crd() and self.individual_address else "" ) return f''