def parse_bytes(profile_bytes: bytes): """ Profile generic are sent as a sequence of A-XDR encoded DlmsData. """ data_decoder = a_xdr.AXdrDecoder(encoding_conf=a_xdr.EncodingConf( attributes=[a_xdr.Sequence(attribute_name="data")])) entries: List[List[Any]] = data_decoder.decode(profile_bytes)["data"] return AssociationObjectListParser.parse_entries(entries)
encryption_key=encryption_key, authentication_key=authentication_key, ) with public_client(host=host, port=port).session() as client: response_data = client.get( cosem.CosemAttribute( interface=enumerations.CosemInterface.DATA, instance=cosem.Obis(0, 0, 0x2B, 1, 0), attribute=2, ) ) data_decoder = a_xdr.AXdrDecoder( encoding_conf=a_xdr.EncodingConf( attributes=[a_xdr.Sequence(attribute_name="data")] ) ) invocation_counter = data_decoder.decode(response_data)["data"] print(f"meter_initial_invocation_counter = {invocation_counter}") # we are not reusing the socket as of now. We just need to give the meter some time to # close the connection on its side sleep(1) with management_client( host=host, port=port, client_initial_invocation_counter=invocation_counter + 1 ).session() as client: profile = client.get( cosem.CosemAttribute(
class GeneralGlobalCipherApdu(AbstractXDlmsApdu): """ The general-global-cipher APDU can be used to cipher other APDUs with either the global key or the dedicated key. The additional authenticated data to use for decryption is depending on the portection applied. Encrypted and authenticated: Security Control Field || Authentication Key Only authenticated: Security Control Field || Authentication Key || Ciphered Text Only encrypted: b'' No protection: b'' """ TAG = 219 NAME = "general-glo-cipher" ENCODING_CONF = a_xdr.EncodingConf([ a_xdr.Attribute(attribute_name="system_title", create_instance=OctetStringData), a_xdr.Attribute( attribute_name="ciphered_content", create_instance=OctetStringData.from_bytes, ), ]) system_title: bytes security_control: SecurityControlField invocation_counter: int ciphered_text: bytes @classmethod def from_bytes(cls, source_bytes: bytes): data = bytearray(source_bytes) tag = data.pop(0) if tag != cls.TAG: raise ValueError( f"Tag not as expected. Expected: {cls.TAG} but got {tag}") decoder = a_xdr.AXdrDecoder(encoding_conf=cls.ENCODING_CONF) in_dict = decoder.decode(data) system_title = in_dict["system_title"].value ciphered_content = in_dict["ciphered_content"].value security_control = SecurityControlField.from_bytes( ciphered_content.pop(0).to_bytes(1, "big")) invocation_counter = int.from_bytes(ciphered_content[:4], "big") ciphered_text = bytes(ciphered_content[4:]) return cls(system_title, security_control, invocation_counter, ciphered_text) def to_bytes(self) -> bytes: out = bytearray() out.append(self.TAG) out.append(len(self.system_title)) out.extend(self.system_title) out.append( len(self.security_control.to_bytes() + self.invocation_counter.to_bytes(4, "big") + self.ciphered_text)) out.extend(self.security_control.to_bytes()) out.extend(self.invocation_counter.to_bytes(4, "big")) out.extend(self.ciphered_text) return bytes(out) def to_plain_apdu(self, encryption_key, authentication_key) -> bytes: plain_text = decrypt( security_control=self.security_control, key=encryption_key, auth_key=authentication_key, invocation_counter=self.invocation_counter, cipher_text=self.ciphered_text, system_title=self.system_title, ) return bytes(plain_text)
class InitiateRequestApdu(AbstractXDlmsApdu): """ InitiateRequest ::= SEQUENCE { dedicated-key: OCTET STRING OPTIONAL response-allowed: BOOLEAN DEFAULT TRUE proposed-quality-of-service: IMPLICIT Integer8 OPTIONAL proposed-dlms-version-number: Integer8 # Always 6? proposed-conformance: Conformance client-max-receive-pdu-size: Unsigned16 } """ TAG: ClassVar[int] = 0x01 # initiateRequest XDLMS-APDU Choice. ENCODING_CONF = a_xdr.EncodingConf([ a_xdr.Attribute( attribute_name="dedicated_key", create_instance=dlms_data.OctetStringData.from_bytes, optional=True, ), a_xdr.Attribute(attribute_name="response_allowed", create_instance=bool, default=True), a_xdr.Attribute( attribute_name="proposed_quality_of_service", create_instance=int_from_bytes, length=1, ), a_xdr.Attribute( attribute_name="proposed_dlms_version_number", create_instance=int_from_bytes, length=1, ), a_xdr.Attribute( attribute_name="rest", create_instance=dlms_data.OctetStringData.from_bytes, length=9, ), ]) proposed_conformance: Conformance proposed_quality_of_service: Optional[int] = attr.ib(default=None) client_max_receive_pdu_size: int = attr.ib(default=65535) proposed_dlms_version_number: int = attr.ib(default=6) response_allowed: bool = attr.ib(default=True) dedicated_key: Optional[bytes] = attr.ib(default=None) @classmethod def from_bytes(cls, _bytes: bytes): # There is weird decoding here since it is mixed X-ADS and BER.... data = bytearray(_bytes) apdu_tag = data.pop(0) if apdu_tag != 0x01: raise ValueError( f"Data is not a InitiateReques APDU, got apdu tag {apdu_tag}") decoder = a_xdr.AXdrDecoder(cls.ENCODING_CONF) object_dict = decoder.decode(data) # Since the initiate request mixes a-xdr and ber encoding we make some pragmatic # one-off handling of that case. rest = bytearray(object_dict.pop("rest").value) # rest contains ber endoced propesed conformance and max reciec pdu conformance_tag = rest[:2] if conformance_tag != b"\x5f\x1f": raise ValueError( f"Didnt receive conformance tag correcly, got {conformance_tag!r}" ) conformance = xdlms.Conformance.from_bytes(data[-5:-2]) max_pdu_size = int.from_bytes(data[-2:], "big") dedicated_key_obj = object_dict.pop("dedicated_key") if dedicated_key_obj: dedicated_key = bytes(dedicated_key_obj.value) else: dedicated_key = None return cls( **object_dict, dedicated_key=dedicated_key, proposed_conformance=conformance, client_max_receive_pdu_size=max_pdu_size, ) def to_bytes(self): # Since the initiate request mixes a-xdr and ber encoding we make some pragmatic # one-off handling of that case. out = bytearray() out.append(self.TAG) if self.dedicated_key: out.append(0x01) out.append(len(self.dedicated_key)) out.extend(self.dedicated_key) else: out.append(0x00) out.append(0x00) out.append(0x00) out.append(0x06) out.extend(b"_\x1f\x04") out.extend(self.proposed_conformance.to_bytes()) out.extend(self.client_max_receive_pdu_size.to_bytes(2, "big")) return bytes(out)
def parse_as_dlms_data(data: bytes): data_decoder = a_xdr.AXdrDecoder(encoding_conf=a_xdr.EncodingConf( attributes=[a_xdr.Sequence(attribute_name="data")])) return data_decoder.decode(data)["data"]
client_logical_address=1, authentication_method=auth, encryption_key=encryption_key, authentication_key=authentication_key, ) port = "/dev/tty.usbserial-A704H991" with public_client(serial_port=port).session() as client: response_data = client.get( cosem.CosemAttribute( interface=enumerations.CosemInterface.DATA, instance=cosem.Obis(0, 0, 0x2B, 1, 0), attribute=2, )) data_decoder = a_xdr.AXdrDecoder(encoding_conf=a_xdr.EncodingConf( attributes=[a_xdr.Sequence(attribute_name="data")])) invocation_counter = data_decoder.decode(response_data)["data"] print(f"meter_initial_invocation_counter = {invocation_counter}") LOAD_PROFILE_BUFFER = cosem.CosemAttribute( interface=enumerations.CosemInterface.PROFILE_GENERIC, instance=cosem.Obis(1, 0, 99, 1, 0), attribute=2, ) CURRENT_ASSOCIATION_OBJECTS = cosem.CosemAttribute( interface=enumerations.CosemInterface.ASSOCIATION_LN, instance=cosem.Obis(0, 0, 40, 0, 0), attribute=2, )