#####################################################################
# binary.py
#
# (c) Copyright 2021, Benjamin Parzella. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#####################################################################
"""SECS binary variable type."""
from .base import Base
[docs]class Binary(Base):
"""Secs type for binary data."""
format_code = 0o10
text_code = "B"
preferred_types = [bytes, bytearray]
def __init__(self, value=None, count=-1):
"""
Initialize a binary secs variable.
:param value: initial value
:type value: string/integer
:param count: number of items this value
:type count: integer
"""
super().__init__()
self.value = bytearray()
self.count = count
if value is not None:
self.set(value)
def __repr__(self):
"""Generate textual representation for an object of this class."""
if len(self.value) == 0:
return f"<{self.text_code}>"
data = " ".join(f"0x{c:x}" for c in self.value)
return f"<{self.text_code} {data.strip()}>"
def __len__(self):
"""Get the length."""
return len(self.value)
def __getitem__(self, key):
"""Get an item using the indexer operator."""
if key >= self.count:
raise IndexError(f"Index {key} out of bounds ({self.count})")
if key >= len(self.value):
return 0
return self.value[key]
def __setitem__(self, key, item):
"""Set an item using the indexer operator."""
if key >= self.count:
raise IndexError(f"Index {key} out of bounds ({self.count})")
if key >= len(self.value):
while key >= len(self.value):
self.value.append(0)
self.value[key] = item
def __eq__(self, other):
"""Check equality with other object."""
if isinstance(other, Base):
if other.is_dynamic:
return other.value.value == self.value
return other.value == self.value
return other == self.value
def __hash__(self):
"""Get data item for hashing."""
return hash(bytes(self.value))
@staticmethod
def _check_single_item_support(value):
if isinstance(value, bool):
return True
if isinstance(value, int):
if 0 <= value <= 255:
return True
return False
return False
[docs] def supports_value(self, value) -> bool:
"""
Check if the current instance supports the provided value.
:param value: value to test
:type value: any
"""
if isinstance(value, (list, tuple)):
return self._supports_value_list(value)
if isinstance(value, bytearray):
return self._supports_value_bytearray(value)
if isinstance(value, bytes):
return self._supports_value_bytes(value)
if isinstance(value, str):
return self._supports_value_str(value)
return self._check_single_item_support(value)
def _supports_value_list(self, value) -> bool:
if self.count > 0 and len(value) > self.count:
return False
for item in value:
if not self._check_single_item_support(item):
return False
return True
def _supports_value_bytearray(self, value) -> bool:
if self.count > 0 and len(value) > self.count:
return False
return True
def _supports_value_bytes(self, value) -> bool:
if self.count > 0 and len(value) > self.count:
return False
return True
def _supports_value_str(self, value) -> bool:
if self.count > 0 and len(value) > self.count:
return False
try:
value.encode('ascii')
except UnicodeEncodeError:
return False
return True
[docs] def set(self, value):
"""
Set the internal value to the provided value.
:param value: new value
:type value: string/integer
"""
if value is None:
return
if isinstance(value, bytes):
value = bytearray(value)
elif isinstance(value, str):
value = bytearray(value.encode('ascii'))
elif isinstance(value, (list, tuple)):
value = bytearray(value)
elif isinstance(value, bytearray):
pass
elif isinstance(value, int):
if 0 <= value <= 255:
value = bytearray([value])
else:
raise ValueError(
f"Value {value} of type {type(value).__name__} is out of range for {self.__class__.__name__}")
else:
raise TypeError(f"Unsupported type {type(value).__name__} for {self.__class__.__name__}")
if 0 < self.count < len(value):
raise ValueError(f"Value longer than {self.count} chars ({len(value)} chars)")
self.value = value
[docs] def get(self):
"""
Return the internal value.
:returns: internal value
:rtype: list/integer
"""
if len(self.value) == 1:
return self.value[0]
return bytes(self.value)
[docs] def encode(self):
"""
Encode the value to secs data.
:returns: encoded data bytes
:rtype: string
"""
result = self.encode_item_header(len(self.value) if self.value is not None else 0)
if self.value is not None:
result += bytes(self.value)
return result
[docs] def decode(self, data, start=0):
"""
Decode the secs byte data to the value.
:param data: encoded data bytes
:type data: string
:param start: start position of value the data
:type start: integer
:returns: new start position
:rtype: integer
"""
(text_pos, _, length) = self.decode_item_header(data, start)
# string
result = None
if length > 0:
result = data[text_pos:text_pos + length]
self.set(result)
return text_pos + length