<commit_before>import logging
import re

from PyCRC.CRC16 import CRC16

from .objects import MBusObject, MBusObjectV2_2, CosemObject
from .exceptions import ParseError, InvalidChecksumError
from .obis_references import GAS_METER_READING

logger = logging.getLogger(__name__)


class TelegramParser(object):

    def __init__(self, telegram_specification):
        """
        :param telegram_specification: determines how the telegram is parsed
        :type telegram_specification: dict
        """
        self.telegram_specification = telegram_specification

    def _find_line_parser(self, line_value):
        for obis_reference, parser in self.telegram_specification.items():
            if re.search(obis_reference, line_value):
                return obis_reference, parser

        return None, None

    def parse(self, line_values):
        telegram = {}

        for line_value in line_values:
            # TODO temporarily strip newline characters.
            line_value = line_value.strip()

            obis_reference, dsmr_object = self.parse_line(line_value)

            telegram[obis_reference] = dsmr_object

        return telegram

    def parse_line(self, line_value):
        logger.debug('Parsing line \'%s\'', line_value)

        obis_reference, parser = self._find_line_parser(line_value)

        if not parser:
            logger.warning("No line class found for: '%s'", line_value)
            return None, None

        return obis_reference, parser.parse(line_value)


class TelegramParserV4(TelegramParser):

    @staticmethod
    def validate_telegram_checksum(line_values):
        """
        :type line_values: list
        :raises ParseError:
        :raises InvalidChecksumError:
        """

        full_telegram = ''.join(line_values)

        # Extract the bytes that count towards the checksum.
        checksum_contents = re.search(r'\/.+\!', full_telegram, re.DOTALL)

        # Extract the hexadecimal checksum value itself.
        checksum_hex = re.search(r'((?<=\!)[0-9A-Z]{4}(?=\r\n))+', full_telegram)

        if not checksum_contents or not checksum_hex:
            raise ParseError(
                'Failed to perform CRC validation because the telegram is '
                'incomplete. The checksum and/or content values are missing.'
            )

        calculated_crc = CRC16().calculate(checksum_contents.group(0))
        expected_crc = checksum_hex.group(0)
        expected_crc = int(expected_crc, base=16)

        if calculated_crc != expected_crc:
            raise InvalidChecksumError(
                "Invalid telegram. The CRC checksum '{}' does not match the "
                "expected '{}'".format(
                    calculated_crc,
                    expected_crc
                )<commit_msg>Fixed CRC calculation<commit_after>import logging
import re

from PyCRC.CRC16 import CRC16

from .objects import MBusObject, MBusObjectV2_2, CosemObject
from .exceptions import ParseError, InvalidChecksumError
from .obis_references import GAS_METER_READING

logger = logging.getLogger(__name__)


class TelegramParser(object):

    def __init__(self, telegram_specification):
        """
        :param telegram_specification: determines how the telegram is parsed
        :type telegram_specification: dict
        """
        self.telegram_specification = telegram_specification

    def _find_line_parser(self, line_value):
        for obis_reference, parser in self.telegram_specification.items():
            if re.search(obis_reference, line_value):
                return obis_reference, parser

        return None, None

    def parse(self, line_values):
        telegram = {}

        for line_value in line_values:
            # TODO temporarily strip newline characters.
            line_value = line_value.strip()

            obis_reference, dsmr_object = self.parse_line(line_value)

            telegram[obis_reference] = dsmr_object

        return telegram

    def parse_line(self, line_value):
        logger.debug('Parsing line \'%s\'', line_value)

        obis_reference, parser = self._find_line_parser(line_value)

        if not parser:
            logger.warning("No line class found for: '%s'", line_value)
            return None, None

        return obis_reference, parser.parse(line_value)


class TelegramParserV4(TelegramParser):

    @staticmethod
    def validate_telegram_checksum(line_values):
        """
        :type line_values: list
        :raises ParseError:
        :raises InvalidChecksumError:
        """

        full_telegram = '\r\n'.join(line_values)

        # Extract the bytes that count towards the checksum.
        contents = re.search(r'(\/.+\!)([0-9A-Z]{4})', full_telegram, re.DOTALL)

        if not contents:
            raise ParseError(
                'Failed to perform CRC validation because the telegram is '
                'incomplete. The checksum and/or content values are missing.'
            )

        calculated_crc = CRC16().calculate(contents.group(1))
        expected_crc = contents.group(2)
        expected_crc = int(expected_crc, base=16)

        if calculated_crc != expected_crc:
            raise InvalidChecksumError(
                "Invalid telegram. The CRC checksum '{}' does not match the "
                "expected '{}'".format(
                    calculated_crc,
                    expected_crc
                )
{'fraction_non_alphanumeric': 0.08181818181818182, 'fraction_numerical': 0.005936920222634508, 'mean_word_length': 2.444728434504792, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1034715', 'n_tokens_mistral': 1563, 'n_tokens_neox': 1500, 'n_words': 376}