"""
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 (
""
)