<commit_before>#  Copyright 2012 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from math import ceil
import math

from Rammbock.message import Field, BinaryField
from Rammbock.binary_tools import to_bin_of_length, to_0xhex, to_tbcd_binary, to_tbcd_value, to_bin


class _TemplateField(object):

    has_length = True
    can_be_little_endian = False
    referenced_later = False

    def get_static_length(self):
        if not self.length.static:
            raise Exception('Length of %s is dynamic.' % self._get_name())
        return self.length.value

    def _get_element_value(self, paramdict, name=None):
        return paramdict.get(self._get_name(name), self.default_value)

    def _get_element_value_and_remove_from_params(self, paramdict, name=None):
        wild_card = paramdict.get('*') if not self.referenced_later else None
        return paramdict.pop(self._get_name(name),
            self.default_value or wild_card)

    def encode(self, paramdict, parent, name=None, little_endian=False):
        value = self._get_element_value_and_remove_from_params(paramdict, name)
        if not value and self.referenced_later:
            return PlaceHolderField(self)
        return self._to_field(name, value, parent, little_endian=little_endian)

    def _to_field(self, name, value, parent, little_endian=False):
        field_name, field_value = self._encode_value(value, parent, little_endian=little_endian)
        return Field(self.type, self._get_name(name), field_name, field_value, little_endian=little_endian)

    def decode(self, value, message, name=None, little_endian=False):
        length, aligned_length = self.length.decode_lengths(message, len(value))
        if len(value) < aligned_length:
            raise Exception('Not enough data for %s. Needs %s bytes, given %s' % (self._get_name(name), aligned_length, len(value)))
        return Field(self.type,
                     self._get_name(name),
                     value[:length],
                     aligned_len=aligned_length,
                     little_endian=little_endian and self.can_be_little_endian)

    def validate(self, parent, paramdict, name=None):
        name = name or self.name
        field = parent[name]
        value = field.bytes
        forced_value = self._get_element_value_and_remove_from_params(paramdict, name)
        if not forced_value or forced_value == 'None':
            return []
        elif forced_value.startswith('('):
            return self._validate_pattern(forced_value, value, field)
        return self._validate_exact_match(forced_value, value, field)

    def _validate_pattern(self, forced_pattern, value, field):
        patterns = forced_pattern[1:-1].split('|')
        for pattern in patterns:
            if self._is_match(pattern, value, field._parent):
                return []
        return ['Value of field %s does not match pattern %s!=%s' %
                (field._get_recursive_name(), to_0xhex(value), forced_pattern)]

    def _is_match(self, forced_value, value, parent):
        #TODO: Should pass msg
        forced_binary_val, _ = self._encode_value(forced_value, parent)
        return forced_binary_val == value

    def _validate_exact_match(self, forced_value, value, field):
        if not self._is_match(forced_value, value, field._parent):
            return ['Value of field %s does not match %s!=%s' %
                    (field._get_recursive_name(), self._default_presentation_format(value), forced_value)]
        return []

    def _default_presentation_format(self, value):
        return to_0xhex(value)

    def _get_name(self, name=None):
        return name or self.name or self.type

    def _raise_error_if_no_value(self, value, parent):
        if not value:
            raise AssertionError('Value of %s not set' % self._get_recursive_name(parent))

    def _get_recursive_name(self, parent):
        if not parent:
            return self.name
        return parent._get_recursive_name() + self.name


class PlaceHolderField(object):

    _type = 'referenced_later'
    _parent = None

    def __init__(self, template):
        self.template = template


class UInt(_TemplateField):

    type = 'uint'
    can_be_little_endian = True

    def __init__(self, length, name, default_value=None, align=None):
        self.length = Length(length, align)
        self.name = name
        self.default_value = str(default_value) if default_value and default_value != '""' else None

    def _encode_value(self, value, message, little_endian=False):
        self._raise_error_if_no_value(value, message)
        length, aligned_length = self.length.decode_lengths(message)
        binary = to_bin_of_length(length, value)
        binary = binary[::-1] if little_endian else binary
        return binary, aligned_length


class Char(_TemplateField):

    type = 'char'

    def __init__(self, length, name, default_value=None):
        self.length = Length(length)
        self.name = name
        self.default_value = default_value if default_value and default_value != '""' else None

    def _encode_value(self, value, message, little_endian=False):
        value = value or ''
        length, aligned_length = self.length.find_length_and_set_if_necessary(message, len(value))
        return str(value).ljust(length, '\x00'), aligned_length


class Binary(_TemplateField):

    type = 'binary'

    def __init__(self, length, name, default_value=None):
        self.length = Length(length)
        if not self.length.static:
            raise AssertionError('Binary field length must be static. Length: %s' % length)
        self.name = name
        self.default_value = str(default_value) if default_value and default_value != '""' else None

    def _encode_value(self, value, message, little_endian=False):
        self._raise_error_if_no_value(value, message)
        minimum_binary = to_bin(value)
        length, aligned = self.length.decode_lengths(message, len(minimum_binary))
        binary = to_bin_of_length(self._byte_length(length), value)
        return binary, self._byte_length(aligned)

    def _to_field(self, name, value, parent, little_endian=False):
        field_name, field_value = self._encode_value(value, parent, little_endian=little_endian)
        return BinaryField(self.length.value, self._get_name(name), field_name, field_value, little_endian=little_endian)

    def _byte_length(self, length):
        return int(ceil(length / 8.0))

    def _is_match(self, forced_value, value, message):
        forced_binary_val, _ = self._encode_value(forced_value, message)   # TODO: Should pass msg
        return int(to_0xhex(forced_binary_val), 16) == int(to_0xhex(value), 16)


class TBCD(_TemplateField):

    type = 'tbcd'

    def __init__(self, size, name, default_value):
        self.name = name
        self.length = Length(size)
        self.default_value = str(default_value) if default_value and default_value != '""' else None<commit_msg>add constructor to _TemplateField to reduce duplicate code<commit_after>#  Copyright 2012 <NAME>ens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from math import ceil
import math

from Rammbock.message import Field, BinaryField
from Rammbock.binary_tools import to_bin_of_length, to_0xhex, to_tbcd_binary, to_tbcd_value, to_bin


class _TemplateField(object):

    def __init__(self, name, default_value):
        self._set_default_value(default_value)
        self.name = name

    has_length = True
    can_be_little_endian = False
    referenced_later = False

    def get_static_length(self):
        if not self.length.static:
            raise Exception('Length of %s is dynamic.' % self._get_name())
        return self.length.value

    def _get_element_value(self, paramdict, name=None):
        return paramdict.get(self._get_name(name), self.default_value)

    def _get_element_value_and_remove_from_params(self, paramdict, name=None):
        wild_card = paramdict.get('*') if not self.referenced_later else None
        return paramdict.pop(self._get_name(name),
            self.default_value or wild_card)

    def encode(self, paramdict, parent, name=None, little_endian=False):
        value = self._get_element_value_and_remove_from_params(paramdict, name)
        if not value and self.referenced_later:
            return PlaceHolderField(self)
        return self._to_field(name, value, parent, little_endian=little_endian)

    def _to_field(self, name, value, parent, little_endian=False):
        field_name, field_value = self._encode_value(value, parent, little_endian=little_endian)
        return Field(self.type, self._get_name(name), field_name, field_value, little_endian=little_endian)

    def decode(self, value, message, name=None, little_endian=False):
        length, aligned_length = self.length.decode_lengths(message, len(value))
        if len(value) < aligned_length:
            raise Exception('Not enough data for %s. Needs %s bytes, given %s' % (self._get_name(name), aligned_length, len(value)))
        return Field(self.type,
                     self._get_name(name),
                     value[:length],
                     aligned_len=aligned_length,
                     little_endian=little_endian and self.can_be_little_endian)

    def validate(self, parent, paramdict, name=None):
        name = name or self.name
        field = parent[name]
        value = field.bytes
        forced_value = self._get_element_value_and_remove_from_params(paramdict, name)
        if not forced_value or forced_value == 'None':
            return []
        elif forced_value.startswith('('):
            return self._validate_pattern(forced_value, value, field)
        return self._validate_exact_match(forced_value, value, field)

    def _validate_pattern(self, forced_pattern, value, field):
        patterns = forced_pattern[1:-1].split('|')
        for pattern in patterns:
            if self._is_match(pattern, value, field._parent):
                return []
        return ['Value of field %s does not match pattern %s!=%s' %
                (field._get_recursive_name(), to_0xhex(value), forced_pattern)]

    def _is_match(self, forced_value, value, parent):
        #TODO: Should pass msg
        forced_binary_val, _ = self._encode_value(forced_value, parent)
        return forced_binary_val == value

    def _validate_exact_match(self, forced_value, value, field):
        if not self._is_match(forced_value, value, field._parent):
            return ['Value of field %s does not match %s!=%s' %
                    (field._get_recursive_name(), self._default_presentation_format(value), forced_value)]
        return []

    def _default_presentation_format(self, value):
        return to_0xhex(value)

    def _get_name(self, name=None):
        return name or self.name or self.type

    def _raise_error_if_no_value(self, value, parent):
        if not value:
            raise AssertionError('Value of %s not set' % self._get_recursive_name(parent))

    def _get_recursive_name(self, parent):
        if not parent:
            return self.name
        return parent._get_recursive_name() + self.name

    def _set_default_value(self, value):
        self.default_value = str(value) if value and value != '""' else None


class PlaceHolderField(object):

    _type = 'referenced_later'
    _parent = None

    def __init__(self, template):
        self.template = template


class UInt(_TemplateField):

    type = 'uint'
    can_be_little_endian = True

    def __init__(self, length, name, default_value=None, align=None):
        _TemplateField.__init__(self, name, default_value)
        self.length = Length(length, align)

    def _encode_value(self, value, message, little_endian=False):
        self._raise_error_if_no_value(value, message)
        length, aligned_length = self.length.decode_lengths(message)
        binary = to_bin_of_length(length, value)
        binary = binary[::-1] if little_endian else binary
        return binary, aligned_length


class Char(_TemplateField):

    type = 'char'

    def __init__(self, length, name, default_value=None):
        _TemplateField.__init__(self, name, default_value)
        self.length = Length(length)

    def _encode_value(self, value, message, little_endian=False):
        value = value or ''
        length, aligned_length = self.length.find_length_and_set_if_necessary(message, len(value))
        return str(value).ljust(length, '\x00'), aligned_length


class Binary(_TemplateField):

    type = 'binary'

    def __init__(self, length, name, default_value=None):
        _TemplateField.__init__(self, name, default_value)
        self.length = Length(length)
        if not self.length.static:
            raise AssertionError('Binary field length must be static. Length: %s' % length)

    def _encode_value(self, value, message, little_endian=False):
        self._raise_error_if_no_value(value, message)
        minimum_binary = to_bin(value)
        length, aligned = self.length.decode_lengths(message, len(minimum_binary))
        binary = to_bin_of_length(self._byte_length(length), value)
        return binary, self._byte_length(aligned)

    def _to_field(self, name, value, parent, little_endian=False):
        field_name, field_value = self._encode_value(value, parent, little_endian=little_endian)
        return BinaryField(self.length.value, self._get_name(name), field_name, field_value, little_endian=little_endian)

    def _byte_length(self, length):
        return int(ceil(length / 8.0))

    def _is_match(self, forced_value, value, message):
        forced_binary_val, _ = self._encode_value(forced_value, message)   # TODO: Should pass msg
        return int(to_0xhex(forced_binary_val), 16) == int(to_0xhex(value), 16)


class TBCD(_TemplateField):

    type = 'tbcd'

    def __init__(self, size, name, default_value):
        _TemplateField.__init__(self, name, default_value)
        self.length = Length(size)
{'fraction_non_alphanumeric': 0.09213947596506433, 'fraction_numerical': 0.003200213347556504, 'mean_word_length': 3.3302540415704387, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '6184763', 'n_tokens_mistral': 4542, 'n_tokens_neox': 4376, 'n_words': 1135}