""" Module for Serialization and Deserialization of a KNX Connect Request information. Connect requests are used to start a new tunnel connection on a KNX/IP device. """ from __future__ import annotations from xknx.exceptions import CouldNotParseKNXIP from xknx.telegram import IndividualAddress from .body import KNXIPBody from .hpai import HPAI from .knxip_enum import ConnectRequestType, KNXIPServiceType, TunnellingLayer class ConnectRequest(KNXIPBody): """Representation of a KNX Connect Request.""" SERVICE_TYPE = KNXIPServiceType.CONNECT_REQUEST def __init__( self, control_endpoint: HPAI | None = None, data_endpoint: HPAI | None = None, cri: ConnectRequestInformation | None = None, ) -> None: """Initialize ConnectRequest object.""" self.control_endpoint = control_endpoint or HPAI() self.data_endpoint = data_endpoint or HPAI() self.cri = cri or ConnectRequestInformation() def calculated_length(self) -> int: """Get length of KNX/IP body.""" return HPAI.LENGTH + HPAI.LENGTH + self.cri.calculated_length() def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" pos = self.control_endpoint.from_knx(raw) pos += self.data_endpoint.from_knx(raw[pos:]) pos += self.cri.from_knx(raw[pos:]) return pos def to_knx(self) -> bytes: """Serialize to KNX/IP raw data.""" return ( self.control_endpoint.to_knx() + self.data_endpoint.to_knx() + self.cri.to_knx() ) def __repr__(self) -> str: """Return object as readable string.""" return ( "' ) class ConnectRequestInformation: """ Representation of a KNX Connect Request Information (CRI). A Basic CRI requests a tunnel without requesting any specific IA. Using `individual_address` yields an Extended CRI which is only supported by Tunnelling v2 devices. """ CRI_LENGTH = 2 CRI_TUNNEL_LENGTH = 4 CRI_TUNNEL_EXT_LENGTH = 6 def __init__( self, connection_type: ConnectRequestType = ConnectRequestType.TUNNEL_CONNECTION, knx_layer: TunnellingLayer = TunnellingLayer.DATA_LINK_LAYER, individual_address: IndividualAddress | None = None, ) -> None: """Initialize ConnectRequest object.""" self.connection_type = connection_type self.knx_layer = knx_layer self.individual_address = individual_address def _is_tunnel_cri(self) -> bool: return self.connection_type == ConnectRequestType.TUNNEL_CONNECTION def calculated_length(self) -> int: """Get length of KNX/IP body.""" if self._is_tunnel_cri(): return ( ConnectRequestInformation.CRI_TUNNEL_EXT_LENGTH if self.individual_address else ConnectRequestInformation.CRI_TUNNEL_LENGTH ) return ConnectRequestInformation.CRI_LENGTH def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" cri_length = raw[0] if len(raw) < cri_length: raise CouldNotParseKNXIP("CRI data has wrong length") if cri_length < ConnectRequestInformation.CRI_LENGTH: raise CouldNotParseKNXIP("CRI length too small") self.connection_type = ConnectRequestType(raw[1]) if self._is_tunnel_cri(): if cri_length == ConnectRequestInformation.CRI_TUNNEL_LENGTH: extended = False elif cri_length == ConnectRequestInformation.CRI_TUNNEL_EXT_LENGTH: extended = True else: raise CouldNotParseKNXIP("CRI has wrong length") self.knx_layer = TunnellingLayer(raw[2]) self.individual_address = ( IndividualAddress.from_knx(raw[4:6]) if extended else None ) elif cri_length != ConnectRequestInformation.CRI_LENGTH: raise CouldNotParseKNXIP("CRI has wrong length") return cri_length def to_knx(self) -> bytes: """Serialize to KNX/IP raw data.""" _cri = bytes( ( self.calculated_length(), self.connection_type.value, ) ) if self._is_tunnel_cri(): _cri = _cri + bytes( ( self.knx_layer.value, 0x00, # Reserved ) ) if self.individual_address: _cri = _cri + self.individual_address.to_knx() return _cri def __eq__(self, other: object) -> bool: """Equal operator.""" return self.__dict__ == other.__dict__ def __repr__(self) -> str: """Return object as readable string.""" _tunnel_layer = ( f'knx_layer="{self.knx_layer.name}" ' if self._is_tunnel_cri() else "" ) _extended = ( f'individual_address="{self.individual_address}" ' if self._is_tunnel_cri() and self.individual_address else "" ) return ( "" )