コード例 #1
0
ファイル: connection.py プロジェクト: manqala/dlms-cosem
 def validate_received_invocation_counter(
         self, received_invocation_counter: int) -> None:
     """
     The recevied invocation counter must be larger than the last one we registered.
     """
     if received_invocation_counter <= self.meter_invocation_counter:
         raise exceptions.LocalDlmsProtocolError(
             "Received invocation counter is not larger than the previous "
             "received one. ")
コード例 #2
0
ファイル: dlms_client.py プロジェクト: pwitab/dlms-cosem
    def associate(
        self,
        association_request: Optional[
            acse.ApplicationAssociationRequest] = None,
    ) -> acse.ApplicationAssociationResponse:

        # the aarq can be overridden or the standard one from the connection is used.
        aarq = association_request or self.dlms_connection.get_aarq()

        self.send(aarq)
        response = self.next_event()
        # we could have received an exception from the meter.
        if isinstance(response, xdlms.ExceptionResponse):
            raise exceptions.DlmsClientException(
                f"DLMS Exception: {response.state_error!r}:{response.service_error!r}"
            )
        # the association might not be accepted by the meter
        if isinstance(response, acse.ApplicationAssociationResponse):
            if response.result is not enumerations.AssociationResult.ACCEPTED:
                # there could be an error suppled with the reject.
                extra_error = None
                if response.user_information:
                    if isinstance(response.user_information.content,
                                  ConfirmedServiceError):
                        extra_error = response.user_information.content.error
                raise exceptions.DlmsClientException(
                    f"Unable to perform Association: {response.result!r} and "
                    f"{response.result_source_diagnostics!r}, extra info: {extra_error}"
                )
        else:
            raise exceptions.LocalDlmsProtocolError(
                "Did not receive an AARE after sending AARQ")

        if self.should_send_hls_reply():
            try:
                hls_response = self.send_hls_reply()
            except ActionError as e:
                raise HLSError from e

            if not hls_response:
                raise HLSError("No HLS data in response")

            hls_data = utils.parse_as_dlms_data(hls_response)

            if not hls_response:
                raise HLSError("Did not receive any HLS response data")

            if not self.dlms_connection.hls_response_valid(hls_data):
                raise HLSError(
                    f"Meter did not respond with correct challenge calculation"
                )

        return response
コード例 #3
0
ファイル: connection.py プロジェクト: manqala/dlms-cosem
    def next_event(self):
        """
        Will parse the buffer into an APDU. In lower levels we need the case to get more
        data. But this is not needed in the DLMS connections as it is the lower layers
        responisbility to make sure the data is complete before handing the control
        back to the dlms-layer. In the HDLC case the data is complete when we get the
        last nonsegmented informtion frame. And in the IP case we know the length of
        the data from the IP wrapper so we can keep on trying until we get all data.
        """
        apdu = XDlmsApduFactory.apdu_from_bytes(self.buffer)

        if self.use_protection:
            apdu = self.unprotect(apdu)

        self.update_negotiated_parameters(apdu)

        self.state.process_event(apdu)
        self.clear_buffer()

        if isinstance(apdu, acse.ApplicationAssociationResponseApdu):
            if apdu.result in [
                    enums.AssociationResult.REJECTED_PERMANENT,
                    enums.AssociationResult.REJECTED_TRANSIENT,
            ]:
                # reset the association on a reject
                self.state.process_event(dlms_state.RejectAssociation())

            # we need to start the HLS auth.
            elif apdu.authentication == enums.AuthenticationMechanism.HLS_GMAC:
                self.state.process_event(dlms_state.HlsStart())

        # Handle HLS verification
        if self.state.current_state == dlms_state.HLS_DONE:
            if isinstance(apdu, xdlms.ActionResponseNormalWithData):
                if apdu.status != enums.ActionResultStatus.SUCCESS:
                    self.state.process_event(dlms_state.HlsFailed())
                if self.hls_response_valid(utils.parse_as_dlms_data(
                        apdu.data)):
                    self.state.process_event(dlms_state.HlsSuccess())
                else:
                    self.state.process_event(dlms_state.HlsFailed())
            elif isinstance(apdu, (xdlms.ActionResponseNormalWithError,
                                   xdlms.ActionResponseNormal)):
                self.state.process_event(dlms_state.HlsFailed())

            else:
                raise exceptions.LocalDlmsProtocolError(
                    "Received a non Action response when in HLS DONE")

        return apdu
コード例 #4
0
    def get_hls_reply(self) -> bytes:
        """
        When the meter has enterted the HLS procedure the client firsts sends a reply
        to the server (meter) challenge. It is done with an ActionRequest to the
        current LN Association object in the meter. Method 2, Reply_to_HLS.

        Depending on the HLS type the data looks a bit different

        HLS_GMAC:
            SC + IC + GMAC(SC + AK + Challenge)
        """
        if not self.meter_to_client_challenge:
            raise exceptions.LocalDlmsProtocolError(
                "Meter has not send challenge")
        if not self.global_encryption_key:
            raise ProtectionError(
                "Unable to create GMAC. Missing global_encryption_key")
        if not self.global_authentication_key:
            raise ProtectionError(
                "Unable to create GMAC. Missing global_authentication_key")
        if self.authentication_method == enums.AuthenticationMechanism.HLS_GMAC:
            only_auth_security_control = security.SecurityControlField(
                security_suite=self.security_suite,
                authenticated=True,
                encrypted=False)

            gmac_result = security.gmac(
                security_control=only_auth_security_control,
                system_title=self.client_system_title,
                invocation_counter=self.client_invocation_counter,
                key=self.global_encryption_key,
                auth_key=self.global_authentication_key,
                challenge=self.meter_to_client_challenge,
            )
            return (only_auth_security_control.to_bytes() +
                    self.client_invocation_counter.to_bytes(4, "big") +
                    gmac_result)
        else:
            raise NotImplementedError(
                f"No implementation for HSL: {self.authentication_method!r}")
コード例 #5
0
    def send(self, event) -> bytes:
        """
        Returns the bytes to be sent over the connection and changes the state
        depending on event sent.
        """

        if self.is_pre_established:
            # When we are in a pre established association state starts as READY.
            # Only invalid state change is to send the ReleaseRequestApdu. But it is not
            # possible to close a pre-established association.
            if isinstance(
                    event,
                (acse.ReleaseRequest, acse.ApplicationAssociationRequest)):
                raise exceptions.PreEstablishedAssociationError(
                    f"It is not allowed to send a {type(event)} when the association is"
                    f"pre-established ")

        self.state.process_event(event)
        LOG.debug(f"Preparing to send: {event}")

        if self.use_protection:
            event = self.protect(event)

        # if self.use_blocks:
        #    blocks = self.make_blocks(event)
        #    # TODO: How to handle the subcase of sending blocks?

        LOG.info(f"Sending : {event}")

        out = event.to_bytes()

        if len(out) > self.max_pdu_size:
            raise exceptions.LocalDlmsProtocolError(
                f"PDU size too big. Max PDU size for association is {self.max_pdu_size} "
                f"bytes. PDU to be sent is {len(out)}")
        return out
コード例 #6
0
    def next_event(self):
        """
        Will parse the buffer into an APDU. In lower levels we need the case to get more
        data. But this is not needed in the DLMS connections as it is the lower layers
        responsibility to make sure the data is complete before handing the control
        back to the dlms-layer. In the HDLC case the data is complete when we get the
        last unsegmented information frame. And in the IP case the length is known from
        the IP wrapper element so it is possible to can keep on trying until all data
        is received.
        """
        apdu = XDlmsApduFactory.apdu_from_bytes(self.buffer)

        if isinstance(apdu, acse.ApplicationAssociationResponse):
            # To be able to run the decryption we need to know some things about the
            # meter and that has to be extracted first
            self.update_meter_info(apdu)

        if self.use_protection:
            apdu = self.unprotect(apdu)

        if self.is_pre_established:
            if isinstance(
                    apdu,
                (acse.ApplicationAssociationResponse, acse.ReleaseResponse),
            ):
                raise exceptions.PreEstablishedAssociationError(
                    f"Received a {apdu.__class__.__name__}. In a pre-established "
                    f"association it is not possible to handle ACSE services.")

        self.state.process_event(apdu)
        self.clear_buffer()

        if isinstance(apdu, acse.ApplicationAssociationResponse):
            self.update_negotiated_parameters(apdu)

            if apdu.result in [
                    enums.AssociationResult.REJECTED_PERMANENT,
                    enums.AssociationResult.REJECTED_TRANSIENT,
            ]:
                # reset the association on a reject
                self.state.process_event(dlms_state.RejectAssociation())

            # we need to start the HLS auth.
            elif apdu.authentication == enums.AuthenticationMechanism.HLS_GMAC:
                self.state.process_event(dlms_state.HlsStart())

        # Handle HLS verification
        if self.state.current_state == dlms_state.HLS_DONE:
            if isinstance(apdu, xdlms.ActionResponseNormalWithData):
                if apdu.status != enums.ActionResultStatus.SUCCESS:
                    self.state.process_event(dlms_state.HlsFailed())
                if self.hls_response_valid(utils.parse_as_dlms_data(
                        apdu.data)):
                    self.state.process_event(dlms_state.HlsSuccess())
                else:
                    self.state.process_event(dlms_state.HlsFailed())
            elif isinstance(apdu, (xdlms.ActionResponseNormalWithError,
                                   xdlms.ActionResponseNormal)):
                self.state.process_event(dlms_state.HlsFailed())

            else:
                raise exceptions.LocalDlmsProtocolError(
                    "Received a non Action response when in HLS DONE")

        return apdu