Source code for secsgem.secs.variables.base

#####################################################################
# base.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 variable base type."""

from __future__ import annotations


[docs] class Base: """Base class for SECS variables. Due to the python types, wrapper classes for variables are required. If constructor is called with Base or subclass only the value is copied. """ format_code = -1 text_code = "" preferred_types: list[type] | None def __init__(self, value=None): """Initialize a secs variable.""" self.value = value
[docs] def set(self, value): """Set the internal value to the provided value. Args: value: new value """ raise NotImplementedError("Function set not implemented on " + self.__class__.__name__)
[docs] def encode_item_header(self, length): """Encode item header depending on the number of length bytes required. Args: length: number of bytes in data Returns: encoded item header bytes """ if length < 0: raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too small {length}") if length > 0xFFFFFF: raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too big {length}") if length > 0xFFFF: length_bytes = 3 format_byte = (self.format_code << 2) | length_bytes return bytes( bytearray((format_byte, (length & 0xFF0000) >> 16, (length & 0x00FF00) >> 8, (length & 0x0000FF))), ) if length > 0xFF: length_bytes = 2 format_byte = (self.format_code << 2) | length_bytes return bytes(bytearray((format_byte, (length & 0x00FF00) >> 8, (length & 0x0000FF)))) length_bytes = 1 format_byte = (self.format_code << 2) | length_bytes return bytes(bytearray((format_byte, (length & 0x0000FF))))
[docs] def decode_item_header(self, data, text_pos=0) -> tuple[int, int, int]: """Encode item header depending on the number of length bytes required. Args: data: encoded data text_pos: start of item header in data Returns: start position for next item, format code, length item of data """ if len(data) == 0: raise ValueError(f"Decoding for {self.__class__.__name__} without any text") # parse format byte format_byte = bytearray(data)[text_pos] format_code = (format_byte & 0b11111100) >> 2 length_bytes = format_byte & 0b00000011 text_pos += 1 # read 1-3 length bytes length = 0 for _ in range(length_bytes): length <<= 8 length += bytearray(data)[text_pos] text_pos += 1 if 0 <= self.format_code != format_code: raise ValueError( f"Decoding data for {self.__class__.__name__} ({self.format_code}) has invalid format {format_code}", ) return text_pos, format_code, length
@property def is_dynamic(self) -> bool: """Check if this instance is Dynamic or derived.""" return False @property def preferred_type(self): """Get the preferred type for this variable.""" return self.preferred_types[0]