def cri_from_knx(cri: bytes) -> int: """Parse CRI (Connection Request Information).""" if cri[0] != ConnectRequest.CRI_LENGTH: raise CouldNotParseKNXIP("CRI has wrong length") if len(cri) < ConnectRequest.CRI_LENGTH: raise CouldNotParseKNXIP("CRI data has wrong length") self.request_type = ConnectRequestType(cri[1]) self.flags = cri[2] return 4
def header_from_knx(header: bytes) -> int: """Parse header bytes.""" if header[0] != TunnellingRequest.HEADER_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") if len(header) < TunnellingRequest.HEADER_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") self.communication_channel_id = header[1] self.sequence_counter = header[2] return TunnellingRequest.HEADER_LENGTH
def crd_from_knx(crd): """Parse CRD (Connection Response Data Block).""" if crd[0] != ConnectResponse.CRD_LENGTH: raise CouldNotParseKNXIP("CRD has wrong length") if len(crd) < ConnectResponse.CRD_LENGTH: raise CouldNotParseKNXIP("CRD data has wrong length") self.request_type = ConnectRequestType(crd[1]) self.identifier = crd[2] * 256 + crd[3] return 4
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if raw[0] != TunnellingAck.BODY_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") if len(raw) != TunnellingAck.BODY_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") self.communication_channel_id = raw[1] self.sequence_counter = raw[2] self.status_code = ErrorCode(raw[3]) return TunnellingAck.BODY_LENGTH
def ack_from_knx(header: bytes) -> int: """Parse ACK information.""" if header[0] != TunnellingAck.BODY_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") if len(header) < TunnellingAck.BODY_LENGTH: raise CouldNotParseKNXIP("connection header wrong length") self.communication_channel_id = header[1] self.sequence_counter = header[2] self.status_code = ErrorCode(header[3]) return 4
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) != SessionStatus.LENGTH: raise CouldNotParseKNXIP("SessionStatus has wrong length") try: self.status = SecureSessionStatusCode(raw[0]) except ValueError as err: raise CouldNotParseKNXIP( f"SessionStatus has unsupported status code: {raw[0]}" ) from err return SessionStatus.LENGTH
def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" if len(raw) < HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") if raw[0] != HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") if raw[1] != HPAI.TYPE_UDP: raise CouldNotParseKNXIP("wrong HPAI type") self.ip_addr = "{}.{}.{}.{}".format(raw[2], raw[3], raw[4], raw[5]) self.port = raw[6] * 256 + raw[7] return HPAI.LENGTH
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") if raw[0] != HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") if raw[1] != HPAI.TYPE_UDP: raise CouldNotParseKNXIP("wrong HPAI type") self.ip_addr = f"{raw[2]}.{raw[3]}.{raw[4]}.{raw[5]}" self.port = raw[6] * 256 + raw[7] return HPAI.LENGTH
def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" try: self.code = CEMIMessageCode(raw[0]) except ValueError: raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} ".format(raw[0])) if self.code == CEMIMessageCode.L_DATA_IND or \ self.code == CEMIMessageCode.L_Data_REQ or \ self.code == CEMIMessageCode.L_DATA_CON: return self.from_knx_data_link_layer(raw) raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} / {1}".format(self.code, raw[0]))
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") if raw[0] != HPAI.LENGTH: raise CouldNotParseKNXIP("wrong HPAI length") try: self.protocol = HostProtocol(raw[1]) except ValueError as err: raise CouldNotParseKNXIP("unsupported host protocol code") from err self.ip_addr = socket.inet_ntoa(raw[2:6]) self.port = raw[6] * 256 + raw[7] return HPAI.LENGTH
def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" if len(raw) < 2: raise CouldNotParseKNXIP("could not parse DIB header") dib_length = raw[0] if len(raw) < dib_length: raise CouldNotParseKNXIP("DIB wrong length") self.dtc = DIBTypeCode(raw[1]) self.data = raw[:dib_length] return dib_length
def from_knx(data: bytes) -> SRP: """Convert the bytes to a SRP object.""" if len(data) < SRP.SRP_HEADER_SIZE: raise CouldNotParseKNXIP("Data too short for SRP object.") size: int = data[0] if size > len(data): raise CouldNotParseKNXIP("SRP is larger than actual data size.") return SRP( srp_type=SearchRequestParameterType(data[1] & 0x7F), mandatory=bool(data[1] >> SRP.MANDATORY_BIT_INDEX), data=data[2:size], )
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < 2: raise CouldNotParseKNXIP("DIB header too small") length = raw[0] if len(raw) < length: raise CouldNotParseKNXIP("DIB wrong size") if DIBTypeCode(raw[1]) != DIBTypeCode.SUPP_SVC_FAMILIES: raise CouldNotParseKNXIP("DIB is no device info") for i in range(0, int((length - 2) / 2)): name = DIBServiceFamily(raw[i * 2 + 2]) version = raw[i * 2 + 3] self.families.append(DIBSuppSVCFamilies.Family(name, version)) return length
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < 2: raise CouldNotParseKNXIP("could not parse DIB header") dib_length = raw[0] if len(raw) < dib_length: raise CouldNotParseKNXIP("DIB wrong length") try: self.dtc = DIBTypeCode(raw[1]) except ValueError: self.dtc = raw[1] self.data = raw[:dib_length] return dib_length
def from_knx(self, data): """Parse/deserialize from KNX/IP raw data.""" if len(data) < KNXIPHeader.HEADERLENGTH: raise CouldNotParseKNXIP("wrong connection header length") if data[0] != KNXIPHeader.HEADERLENGTH: raise CouldNotParseKNXIP("wrong connection header length") if data[1] != KNXIPHeader.PROTOCOLVERSION: raise CouldNotParseKNXIP("wrong protocol version") self.header_length = data[0] self.protocol_version = data[1] self.service_type_ident = KNXIPServiceType(data[2] * 256 + data[3]) self.b4_reserve = data[4] self.total_length = data[5] return KNXIPHeader.HEADERLENGTH
def handle_knxipframe(self, knxipframe: KNXIPFrame, source: HPAI) -> None: """Handle KNXIP Frame and call all callbacks matching the service type ident.""" # TODO: disallow unencrypted frames with exceptions for discovery etc. eg. DescriptionResponse if isinstance(knxipframe.body, SecureWrapper): if not self.initialized: raise CouldNotParseKNXIP( "Received SecureWrapper while Secure session not initialized" ) new_sequence_number = int.from_bytes( knxipframe.body.sequence_information, "big") if not new_sequence_number > self._sequence_number_received: knx_logger.warning( "Discarding SecureWrapper with invalid sequence number: %s", knxipframe, ) return try: knxipframe = self.decrypt_frame(knxipframe) except KNXSecureValidationError as err: knx_logger.warning("Could not decrypt KNXIPFrame: %s", err) # Frame shall be discarded return except CouldNotParseKNXIP as couldnotparseknxip: knx_logger.debug( "Unsupported encrypted KNXIPFrame: %s", couldnotparseknxip.description, ) return self._sequence_number_received = new_sequence_number knx_logger.debug("Decrypted frame: %s", knxipframe) super().handle_knxipframe(knxipframe, source)
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < 2: raise CouldNotParseKNXIP("DIB header too small") length = raw[0] if (len(raw) < length) or (length % 2): raise CouldNotParseKNXIP("DIB wrong size") if DIBTypeCode(raw[1]) != self.type_code: raise CouldNotParseKNXIP( f"DIB has wrong type code for {self.__class__.__name__}") for pos in range(2, length, 2): name = DIBServiceFamily(raw[pos]) version = raw[pos + 1] self.families.append(DIBSuppSVCFamilies.Family(name, version)) return length
def info_from_knx(info): """Parse info bytes.""" if len(info) < 2: raise CouldNotParseKNXIP("Disconnect info has wrong length") self.communication_channel_id = info[0] self.status_code = ErrorCode(info[1]) return 2
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) != SessionAuthenticate.LENGTH: raise CouldNotParseKNXIP("SessionAuthenticate has wrong length") self.user_id = raw[1] self.message_authentication_code = raw[2:] return SessionAuthenticate.LENGTH
def init(self, service_type_ident: KNXIPServiceType) -> KNXIPBody: """Init object by service_type_ident. Will instanciate a body object depending on service_type_ident.""" self.header.service_type_ident = service_type_ident body: KNXIPBody if service_type_ident == KNXIPServiceType.ROUTING_INDICATION: body = RoutingIndication(self.xknx) elif service_type_ident == KNXIPServiceType.CONNECT_REQUEST: body = ConnectRequest(self.xknx) elif service_type_ident == KNXIPServiceType.CONNECT_RESPONSE: body = ConnectResponse(self.xknx) elif service_type_ident == KNXIPServiceType.TUNNELLING_REQUEST: body = TunnellingRequest(self.xknx) elif service_type_ident == KNXIPServiceType.TUNNELLING_ACK: body = TunnellingAck(self.xknx) elif service_type_ident == KNXIPServiceType.SEARCH_REQUEST: body = SearchRequest(self.xknx) elif service_type_ident == KNXIPServiceType.SEARCH_RESPONSE: body = SearchResponse(self.xknx) elif service_type_ident == KNXIPServiceType.DISCONNECT_REQUEST: body = DisconnectRequest(self.xknx) elif service_type_ident == KNXIPServiceType.DISCONNECT_RESPONSE: body = DisconnectResponse(self.xknx) elif service_type_ident == KNXIPServiceType.CONNECTIONSTATE_REQUEST: body = ConnectionStateRequest(self.xknx) elif service_type_ident == KNXIPServiceType.CONNECTIONSTATE_RESPONSE: body = ConnectionStateResponse(self.xknx) else: raise CouldNotParseKNXIP( f"KNXIPServiceType not implemented: {service_type_ident.name}") self.body = body return body
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) != SessionRequest.LENGTH: raise CouldNotParseKNXIP("SessionRequest has wrong length") pos = self.control_endpoint.from_knx(raw) self.ecdh_client_public_key = raw[pos:] return SessionRequest.LENGTH
def info_from_knx(info): """Parse info bytes.""" if len(info) < 2: raise CouldNotParseKNXIP("Disconnect info has wrong length") self.communication_channel_id = info[0] # info[1] is reserved return 2
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < 2: raise CouldNotParseKNXIP("Disconnect info has wrong length") self.communication_channel_id = raw[0] # raw[1] is reserved return self.control_endpoint.from_knx(raw[2:]) + 2
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) < ConnectionStateResponse.LENGTH: raise CouldNotParseKNXIP( "ConnectionStateResponse info has wrong length") self.communication_channel_id = raw[0] self.status_code = ErrorCode(raw[1]) return ConnectionStateResponse.LENGTH
def to_knx(self) -> List[int]: """Serialize to KNX/IP raw data.""" if self.body is None: raise CouldNotParseKNXIP("No body defined in KNXIPFrame.") data = [] data.extend(self.header.to_knx()) data.extend(self.body.to_knx()) return data
def from_knx(self, raw: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(raw) != SessionResponse.LENGTH: raise CouldNotParseKNXIP("SessionResponse has wrong length") self.secure_session_id = int.from_bytes(raw[:2], "big") self.ecdh_server_public_key = raw[2:34] self.message_authentication_code = raw[34:] return SessionResponse.LENGTH
def from_knx_data_link_layer(self, cemi): """Parse L_DATA_IND, CEMIMessageCode.L_Data_REQ, CEMIMessageCode.L_DATA_CON.""" if len(cemi) < 11: raise CouldNotParseKNXIP("CEMI too small") # AddIL (Additional Info Length), as specified within # KNX Chapter 3.6.3/4.1.4.3 "Additional information." # Additional information is not yet parsed. addil = cemi[1] self.flags = cemi[2 + addil] * 256 + cemi[3 + addil] self.src_addr = PhysicalAddress((cemi[4 + addil], cemi[5 + addil])) if self.flags & CEMIFlags.DESTINATION_GROUP_ADDRESS: self.dst_addr = GroupAddress((cemi[6 + addil], cemi[7 + addil]), levels=self.xknx.address_format) else: self.dst_addr = PhysicalAddress((cemi[6 + addil], cemi[7 + addil])) self.mpdu_len = cemi[8 + addil] # TPCI (transport layer control information) -> First 14 bit # APCI (application layer control information) -> Last 10 bit tpci_apci = cemi[9 + addil] * 256 + cemi[10 + addil] try: self.cmd = APCICommand(tpci_apci & 0xFFC0) except ValueError: raise CouldNotParseKNXIP( "APCI not supported: {0:#012b}".format(tpci_apci & 0xFFC0)) apdu = cemi[10 + addil:] if len(apdu) != self.mpdu_len: raise CouldNotParseKNXIP( "APDU LEN should be {} but is {}".format( self.mpdu_len, len(apdu))) if len(apdu) == 1: apci = tpci_apci & DPTBinary.APCI_BITMASK self.payload = DPTBinary(apci) else: self.payload = DPTArray(cemi[11 + addil:]) return 10 + addil + len(apdu)
def from_knx(self, data: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(data) < KNXIPHeader.HEADERLENGTH: raise IncompleteKNXIPFrame("wrong connection header length") if data[0] != KNXIPHeader.HEADERLENGTH: raise CouldNotParseKNXIP("wrong connection header length") # set immediately, as we need it for tcp stream parsing before raising exception self.total_length = data[5] if data[1] != KNXIPHeader.PROTOCOLVERSION: raise CouldNotParseKNXIP("wrong protocol version") try: self.service_type_ident = KNXIPServiceType(data[2] * 256 + data[3]) except ValueError: raise CouldNotParseKNXIP( f"KNXIPServiceType unknown: {hex(data[2] * 256 + data[3])}") self.b4_reserve = data[4] return KNXIPHeader.HEADERLENGTH
def from_knx_data_link_layer(self, cemi: bytes) -> int: """Parse L_DATA_IND, CEMIMessageCode.L_DATA_REQ, CEMIMessageCode.L_DATA_CON.""" if len(cemi) < 10: raise UnsupportedCEMIMessage( f"CEMI too small. Length: {len(cemi)}; CEMI: {cemi.hex()}") # AddIL (Additional Info Length), as specified within # KNX Chapter 3.6.3/4.1.4.3 "Additional information." # Additional information is not yet parsed. addil = cemi[1] # Control field 1 and Control field 2 - first 2 octets after Additional information self.flags = cemi[2 + addil] * 256 + cemi[3 + addil] self.src_addr = IndividualAddress((cemi[4 + addil], cemi[5 + addil])) dst_is_group_address = bool(self.flags & CEMIFlags.DESTINATION_GROUP_ADDRESS) dst_raw_address = (cemi[6 + addil], cemi[7 + addil]) self.dst_addr = (GroupAddress(dst_raw_address) if dst_is_group_address else IndividualAddress(dst_raw_address)) npdu_len = cemi[8 + addil] apdu = cemi[9 + addil:] if len(apdu) != (npdu_len + 1): # TCPI octet not included in NPDU length raise CouldNotParseKNXIP( f"APDU LEN should be {npdu_len} but is {len(apdu) - 1} in CEMI: {cemi.hex()}" ) # TPCI (transport layer control information) # - with control bit set -> 8 bit; no APDU # - no control bit set (data) -> First 6 bit # APCI (application layer control information) -> Last 10 bit of TPCI/APCI try: self.tpci = TPCI.resolve(raw_tpci=cemi[9 + addil], dst_is_group_address=dst_is_group_address) except ConversionError as err: raise UnsupportedCEMIMessage( f"TPCI not supported: {cemi[9 + addil]:#10b}") from err if self.tpci.control: if npdu_len: raise UnsupportedCEMIMessage( f"Invalid length for control TPDU {self.tpci}: {npdu_len}") return 10 + addil _apci = apdu[0] * 256 + apdu[1] try: self.payload = APCI.resolve_apci(_apci) except ConversionError as err: raise UnsupportedCEMIMessage( f"APCI not supported: {_apci:#012b}") from err self.payload.from_knx(apdu) return 10 + addil + npdu_len
def from_knx(self, data: bytes) -> int: """Parse/deserialize from KNX/IP raw data.""" if len(data) < KNXIPHeader.HEADERLENGTH: raise CouldNotParseKNXIP("wrong connection header length") if data[0] != KNXIPHeader.HEADERLENGTH: raise CouldNotParseKNXIP("wrong connection header length") if data[1] != KNXIPHeader.PROTOCOLVERSION: raise CouldNotParseKNXIP("wrong protocol version") self.header_length = data[0] self.protocol_version = data[1] try: self.service_type_ident = KNXIPServiceType(data[2] * 256 + data[3]) except ValueError: raise CouldNotParseKNXIP("KNXIPServiceType unknown: {}".format( hex(data[2] * 256 + data[3]))) self.b4_reserve = data[4] self.total_length = data[5] return KNXIPHeader.HEADERLENGTH