""" Module for serialization and deserialization of KNX/IP packets. It consists of a header and a body. Depending on the service_type_ident different types of body classes are instantiated. """ from __future__ import annotations from xknx.exceptions import CouldNotParseKNXIP, IncompleteKNXIPFrame from .body import KNXIPBody from .connect_request import ConnectRequest from .connect_response import ConnectResponse from .connectionstate_request import ConnectionStateRequest from .connectionstate_response import ConnectionStateResponse from .description_request import DescriptionRequest from .description_response import DescriptionResponse from .device_configuration_ack import DeviceConfigurationAck from .device_configuration_request import DeviceConfigurationRequest from .disconnect_request import DisconnectRequest from .disconnect_response import DisconnectResponse from .header import KNXIPHeader from .knxip_enum import KNXIPServiceType from .routing_busy import RoutingBusy from .routing_indication import RoutingIndication from .routing_lost_message import RoutingLostMessage from .search_request import SearchRequest from .search_request_extended import SearchRequestExtended from .search_response import SearchResponse from .search_response_extended import SearchResponseExtended from .secure_wrapper import SecureWrapper from .session_authenticate import SessionAuthenticate from .session_request import SessionRequest from .session_response import SessionResponse from .session_status import SessionStatus from .timer_notify import TimerNotify from .tunnelling_ack import TunnellingAck from .tunnelling_feature import ( TunnellingFeatureGet, TunnellingFeatureInfo, TunnellingFeatureResponse, TunnellingFeatureSet, ) from .tunnelling_request import TunnellingRequest class KNXIPFrame: """Class for KNX/IP Frames.""" def __init__(self, header: KNXIPHeader, body: KNXIPBody) -> None: """Initialize object.""" self.header = header self.body = body @staticmethod def init_from_body(knxip_body: KNXIPBody) -> KNXIPFrame: """Return KNXIPFrame from KNXIPBody.""" header = KNXIPHeader() header.service_type_ident = knxip_body.__class__.SERVICE_TYPE header.set_length(knxip_body) return KNXIPFrame(header=header, body=knxip_body) @staticmethod def from_knx(data: bytes) -> tuple[KNXIPFrame, bytes]: """ Parse/deserialize from KNX/IP raw data. Returns a tuple of the KNXIPFrame and the rest of the data. Raises IncompleteKNXIPFrame if the data is not enough to parse the KNXIPFrame or CouldNotParseKNXIP on parsing error. """ header = KNXIPHeader() pos_body = header.from_knx(data) if len(data) < header.total_length: raise IncompleteKNXIPFrame("Incomplete data for KNXIPFrame") # limit data to self.header.total_length for streaming socket data raw_body = data[pos_body : header.total_length] body: KNXIPBody # Core if header.service_type_ident == KNXIPServiceType.SEARCH_REQUEST: body = SearchRequest() elif header.service_type_ident == KNXIPServiceType.SEARCH_REQUEST_EXTENDED: body = SearchRequestExtended() elif header.service_type_ident == KNXIPServiceType.SEARCH_RESPONSE: body = SearchResponse() elif header.service_type_ident == KNXIPServiceType.SEARCH_RESPONSE_EXTENDED: body = SearchResponseExtended() elif header.service_type_ident == KNXIPServiceType.DESCRIPTION_REQUEST: body = DescriptionRequest() elif header.service_type_ident == KNXIPServiceType.DESCRIPTION_RESPONSE: body = DescriptionResponse() elif header.service_type_ident == KNXIPServiceType.CONNECT_REQUEST: body = ConnectRequest() elif header.service_type_ident == KNXIPServiceType.CONNECT_RESPONSE: body = ConnectResponse() elif header.service_type_ident == KNXIPServiceType.CONNECTIONSTATE_REQUEST: body = ConnectionStateRequest() elif header.service_type_ident == KNXIPServiceType.CONNECTIONSTATE_RESPONSE: body = ConnectionStateResponse() elif header.service_type_ident == KNXIPServiceType.DISCONNECT_REQUEST: body = DisconnectRequest() elif header.service_type_ident == KNXIPServiceType.DISCONNECT_RESPONSE: body = DisconnectResponse() # Device Management elif header.service_type_ident == KNXIPServiceType.DEVICE_CONFIGURATION_REQUEST: body = DeviceConfigurationRequest() elif header.service_type_ident == KNXIPServiceType.DEVICE_CONFIGURATION_ACK: body = DeviceConfigurationAck() # Tunnelling elif header.service_type_ident == KNXIPServiceType.TUNNELLING_REQUEST: body = TunnellingRequest() elif header.service_type_ident == KNXIPServiceType.TUNNELLING_ACK: body = TunnellingAck() elif header.service_type_ident == KNXIPServiceType.TUNNELLING_FEATURE_GET: body = TunnellingFeatureGet() elif header.service_type_ident == KNXIPServiceType.TUNNELLING_FEATURE_INFO: body = TunnellingFeatureInfo() elif header.service_type_ident == KNXIPServiceType.TUNNELLING_FEATURE_RESPONSE: body = TunnellingFeatureResponse() elif header.service_type_ident == KNXIPServiceType.TUNNELLING_FEATURE_SET: body = TunnellingFeatureSet() # Routing elif header.service_type_ident == KNXIPServiceType.ROUTING_INDICATION: body = RoutingIndication() elif header.service_type_ident == KNXIPServiceType.ROUTING_BUSY: body = RoutingBusy() elif header.service_type_ident == KNXIPServiceType.ROUTING_LOST_MESSAGE: body = RoutingLostMessage() # Secure elif header.service_type_ident == KNXIPServiceType.SECURE_WRAPPER: body = SecureWrapper() elif header.service_type_ident == KNXIPServiceType.SESSION_AUTHENTICATE: body = SessionAuthenticate() elif header.service_type_ident == KNXIPServiceType.SESSION_REQUEST: body = SessionRequest() elif header.service_type_ident == KNXIPServiceType.SESSION_RESPONSE: body = SessionResponse() elif header.service_type_ident == KNXIPServiceType.SESSION_STATUS: body = SessionStatus() elif header.service_type_ident == KNXIPServiceType.TIMER_NOTIFY: body = TimerNotify() else: raise CouldNotParseKNXIP( f"KNXIPServiceType not implemented: {header.service_type_ident.name}" ) body.from_knx(raw_body) return KNXIPFrame(header=header, body=body), data[header.total_length :] def to_knx(self) -> bytes: """Serialize to KNX/IP raw data.""" return self.header.to_knx() + self.body.to_knx() def __repr__(self) -> str: """Return object as readable string.""" return f'' def __eq__(self, other: object) -> bool: """Equal operator.""" return self.__dict__ == other.__dict__