<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmocom-bb/+/23135">View Change</a></p><div style="white-space:pre-wrap">Approvals:
pespin: Looks good to me, but someone else must approve
laforge: Looks good to me, approved; Verified
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">trx_toolkit: check in simple declarative codec<br><br>Change-Id: I7ff46b278c59af3720ee7f3950ea5a8b2f1313e1<br>Related: OS#4006, SYS#4895<br>---<br>A src/target/trx_toolkit/codec.py<br>A src/target/trx_toolkit/test_codec.py<br>2 files changed, 1,000 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/src/target/trx_toolkit/codec.py b/src/target/trx_toolkit/codec.py</span><br><span>new file mode 100644</span><br><span>index 0000000..7a42c9b</span><br><span>--- /dev/null</span><br><span>+++ b/src/target/trx_toolkit/codec.py</span><br><span>@@ -0,0 +1,408 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# -*- coding: utf-8 -*-</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+'''</span><br><span style="color: hsl(120, 100%, 40%);">+Very simple (performance oriented) declarative message codec.</span><br><span style="color: hsl(120, 100%, 40%);">+Inspired by Pycrate and Scapy.</span><br><span style="color: hsl(120, 100%, 40%);">+'''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# TRX Toolkit</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# All Rights Reserved</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software; you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+# the Free Software Foundation; either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+# (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License along</span><br><span style="color: hsl(120, 100%, 40%);">+# with this program; if not, write to the Free Software Foundation, Inc.,</span><br><span style="color: hsl(120, 100%, 40%);">+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from typing import Optional, Callable, Tuple, Any</span><br><span style="color: hsl(120, 100%, 40%);">+import abc</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class ProtocolError(Exception):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Error in a protocol definition. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class DecodeError(Exception):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Error during decoding of a field/message. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class EncodeError(Exception):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Error during encoding of a field/message. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Codec(abc.ABC):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Base class providing encoding and decoding API. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @abc.abstractmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def from_bytes(self, vals: dict, data: bytes) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Decode value(s) from the given buffer of bytes. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @abc.abstractmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Encode value(s) into bytes. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Field(Codec):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Base class representing one field in a Message. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default length (0 means the whole buffer)</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN: int = 0</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_PARAMS: dict = { }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Presence of a field during decoding and encoding</span><br><span style="color: hsl(120, 100%, 40%);">+ get_pres: Callable[[dict], bool]</span><br><span style="color: hsl(120, 100%, 40%);">+ # Length of a field for self.from_bytes()</span><br><span style="color: hsl(120, 100%, 40%);">+ get_len: Callable[[dict, bytes], int]</span><br><span style="color: hsl(120, 100%, 40%);">+ # Value of a field for self.to_bytes()</span><br><span style="color: hsl(120, 100%, 40%);">+ get_val: Callable[[dict], Any]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, name: str, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.name = name</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self.len = kw.get('len', self.DEF_LEN)</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.len == 0: # flexible field</span><br><span style="color: hsl(120, 100%, 40%);">+ self.get_len = lambda _, data: len(data)</span><br><span style="color: hsl(120, 100%, 40%);">+ else: # fixed length</span><br><span style="color: hsl(120, 100%, 40%);">+ self.get_len = lambda vals, _: self.len</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Field is unconditionally present by default</span><br><span style="color: hsl(120, 100%, 40%);">+ self.get_pres = lambda vals: True</span><br><span style="color: hsl(120, 100%, 40%);">+ # Field takes its value from the given dict by default</span><br><span style="color: hsl(120, 100%, 40%);">+ self.get_val = lambda vals: vals[self.name]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Additional parameters for derived field types</span><br><span style="color: hsl(120, 100%, 40%);">+ self.p = { key : kw.get(key, self.DEF_PARAMS[key])</span><br><span style="color: hsl(120, 100%, 40%);">+ for key in self.DEF_PARAMS }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def from_bytes(self, vals: dict, data: bytes) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.get_pres(vals) is False:</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0</span><br><span style="color: hsl(120, 100%, 40%);">+ length = self.get_len(vals, data)</span><br><span style="color: hsl(120, 100%, 40%);">+ if len(data) < length:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise DecodeError('Short read')</span><br><span style="color: hsl(120, 100%, 40%);">+ self._from_bytes(vals, data[:length])</span><br><span style="color: hsl(120, 100%, 40%);">+ return length</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.get_pres(vals) is False:</span><br><span style="color: hsl(120, 100%, 40%);">+ return b''</span><br><span style="color: hsl(120, 100%, 40%);">+ data = self._to_bytes(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.len > 0 and len(data) != self.len:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise EncodeError('Field length mismatch')</span><br><span style="color: hsl(120, 100%, 40%);">+ return data</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @abc.abstractmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Decode value(s) from the given buffer of bytes. '''</span><br><span style="color: hsl(120, 100%, 40%);">+ raise NotImplementedError</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @abc.abstractmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Encode value(s) into bytes. '''</span><br><span style="color: hsl(120, 100%, 40%);">+ raise NotImplementedError</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Buf(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' A sequence of octets. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = data</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ # TODO: handle len(self.get_val()) < self.get_len()</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.get_val(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Spare(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Spare filling for RFU fields or padding. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_PARAMS = {</span><br><span style="color: hsl(120, 100%, 40%);">+ 'filler' : b'\x00',</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ pass # Just ignore it</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.p['filler'] * self.get_len(vals, b'')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' An integer field: unsigned, N bits, big endian. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Uint8 by default</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_PARAMS = {</span><br><span style="color: hsl(120, 100%, 40%);">+ 'offset' : 0,</span><br><span style="color: hsl(120, 100%, 40%);">+ 'mult' : 1,</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Big endian, unsigned</span><br><span style="color: hsl(120, 100%, 40%);">+ SIGN = False</span><br><span style="color: hsl(120, 100%, 40%);">+ BO = 'big'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ val = int.from_bytes(data, self.BO, signed=self.SIGN)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = val * self.p['mult'] + self.p['offset']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ val = (self.get_val(vals) - self.p['offset']) // self.p['mult']</span><br><span style="color: hsl(120, 100%, 40%);">+ return val.to_bytes(self.len, self.BO, signed=self.SIGN)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint16BE(Uint):</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 16 // 8</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint16LE(Uint16BE):</span><br><span style="color: hsl(120, 100%, 40%);">+ BO = 'little'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint32BE(Uint):</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 32 // 8</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint32LE(Uint32BE):</span><br><span style="color: hsl(120, 100%, 40%);">+ BO = 'little'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Int(Uint):</span><br><span style="color: hsl(120, 100%, 40%);">+ SIGN = True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Int16BE(Int):</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 16 // 8</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Int16LE(Int16BE):</span><br><span style="color: hsl(120, 100%, 40%);">+ BO = 'little'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Int32BE(Int):</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 32 // 8</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Int32LE(Int32BE):</span><br><span style="color: hsl(120, 100%, 40%);">+ BO = 'little'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class BitFieldSet(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' A set of bit-fields. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_PARAMS = {</span><br><span style="color: hsl(120, 100%, 40%);">+ # Default field order (MSB first)</span><br><span style="color: hsl(120, 100%, 40%);">+ 'order' : 'big',</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # To be defined by derived types</span><br><span style="color: hsl(120, 100%, 40%);">+ STRUCT: Tuple['BitField', ...] = ()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ Field.__init__(self, self.__class__.__name__, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self._fields = kw.get('set', self.STRUCT)</span><br><span style="color: hsl(120, 100%, 40%);">+ if type(self._fields) is not tuple:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ProtocolError('Expected a tuple')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # LSB first is basically reversed order</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.p['order'] in ('little', 'lsb'):</span><br><span style="color: hsl(120, 100%, 40%);">+ self._fields = self._fields[::-1]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Calculate the overall field length</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.len == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+ bl_sum = sum([f.bl for f in self._fields])</span><br><span style="color: hsl(120, 100%, 40%);">+ self.len = bl_sum // 8</span><br><span style="color: hsl(120, 100%, 40%);">+ if bl_sum % 8 > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.len += 1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Re-define self.get_len() since we always know the length</span><br><span style="color: hsl(120, 100%, 40%);">+ self.get_len = lambda vals, data: self.len</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Pre-calculate offset and mask for each field</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = self.len * 8</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in self._fields:</span><br><span style="color: hsl(120, 100%, 40%);">+ if f.bl > offset:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ProtocolError(f, 'BitFieldSet overflow')</span><br><span style="color: hsl(120, 100%, 40%);">+ f.offset = offset - f.bl</span><br><span style="color: hsl(120, 100%, 40%);">+ f.mask = 2 ** f.bl - 1</span><br><span style="color: hsl(120, 100%, 40%);">+ offset -= f.bl</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ blob = int.from_bytes(data, byteorder='big') # intentionally using 'big' here</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in self._fields:</span><br><span style="color: hsl(120, 100%, 40%);">+ f.dec_val(vals, blob)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ blob = 0x00</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in self._fields: # TODO: use functools.reduce()?</span><br><span style="color: hsl(120, 100%, 40%);">+ blob |= f.enc_val(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ return blob.to_bytes(self.len, byteorder='big')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class BitField:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' One field in a BitFieldSet. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Special fields for BitFieldSet</span><br><span style="color: hsl(120, 100%, 40%);">+ offset: int = 0</span><br><span style="color: hsl(120, 100%, 40%);">+ mask: int = 0</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ class Spare:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Spare filling in a BitFieldSet. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, bl: int) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.name = None</span><br><span style="color: hsl(120, 100%, 40%);">+ self.bl = bl</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def enc_val(self, vals: dict) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def dec_val(self, vals: dict, blob: int) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ pass # Just ignore it</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, name: str, bl: int, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ if bl < 1: # Ensure proper length</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ProtocolError('Incorrect bit-field length')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self.name = name</span><br><span style="color: hsl(120, 100%, 40%);">+ self.bl = bl</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # (Optional) fixed value for encoding and decoding</span><br><span style="color: hsl(120, 100%, 40%);">+ self.val: Optional[int] = kw.get('val', None)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def enc_val(self, vals: dict) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.val is None:</span><br><span style="color: hsl(120, 100%, 40%);">+ val = vals[self.name]</span><br><span style="color: hsl(120, 100%, 40%);">+ else:</span><br><span style="color: hsl(120, 100%, 40%);">+ val = self.val</span><br><span style="color: hsl(120, 100%, 40%);">+ return (val & self.mask) << self.offset</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def dec_val(self, vals: dict, blob: int) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = (blob >> self.offset) & self.mask</span><br><span style="color: hsl(120, 100%, 40%);">+ if (self.val is not None) and (vals[self.name] != self.val):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise DecodeError('Unexpected value %d, expected %d'</span><br><span style="color: hsl(120, 100%, 40%);">+ % (vals[self.name], self.val))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Envelope:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' A group of related fields. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ STRUCT: Tuple[Codec, ...] = ()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, check_len: bool = True):</span><br><span style="color: hsl(120, 100%, 40%);">+ # TODO: ensure uniqueue field names in self.STRUCT</span><br><span style="color: hsl(120, 100%, 40%);">+ self.c: dict = { }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.check_len = check_len</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __getitem__(self, key: str) -> Any:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.c[key]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __setitem__(self, key: str, val: Any) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.c[key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __delitem__(self, key: str) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ del self.c[key]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def check(self, vals: dict) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Check the content before encoding and after decoding.</span><br><span style="color: hsl(120, 100%, 40%);">+ Raise exceptions (e.g. ValueError) if something is wrong.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ Do not assert for every possible error (e.g. a negative value</span><br><span style="color: hsl(120, 100%, 40%);">+ for a Uint field) if an exception will be thrown by the field's</span><br><span style="color: hsl(120, 100%, 40%);">+ to_bytes() method anyway. Only additional constraints here.</span><br><span style="color: hsl(120, 100%, 40%);">+ '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def from_bytes(self, data: bytes) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.c.clear() # forget the old content</span><br><span style="color: hsl(120, 100%, 40%);">+ return self._from_bytes(self.c, data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def to_bytes(self) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self._to_bytes(self.c)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes, offset: int = 0) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+ try: # Fields throw exceptions</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in self.STRUCT:</span><br><span style="color: hsl(120, 100%, 40%);">+ offset += f.from_bytes(vals, data[offset:])</span><br><span style="color: hsl(120, 100%, 40%);">+ except Exception as e:</span><br><span style="color: hsl(120, 100%, 40%);">+ # Add contextual info</span><br><span style="color: hsl(120, 100%, 40%);">+ raise DecodeError(self, f, offset) from e</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.check_len and len(data) != offset:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise DecodeError(self, 'Unhandled tail octets: %s'</span><br><span style="color: hsl(120, 100%, 40%);">+ % data[offset:].hex())</span><br><span style="color: hsl(120, 100%, 40%);">+ self.check(vals) # Check the content after decoding (raises exceptions)</span><br><span style="color: hsl(120, 100%, 40%);">+ return offset</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ def proc(f: Codec):</span><br><span style="color: hsl(120, 100%, 40%);">+ try: # Fields throw exceptions</span><br><span style="color: hsl(120, 100%, 40%);">+ return f.to_bytes(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ except Exception as e:</span><br><span style="color: hsl(120, 100%, 40%);">+ # Add contextual info</span><br><span style="color: hsl(120, 100%, 40%);">+ raise EncodeError(self, f) from e</span><br><span style="color: hsl(120, 100%, 40%);">+ self.check(vals) # Check the content before encoding (raises exceptions)</span><br><span style="color: hsl(120, 100%, 40%);">+ return b''.join([proc(f) for f in self.STRUCT])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ class F(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Field wrapper. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, e: 'Envelope', name: str, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ Field.__init__(self, name, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.e = e</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = { }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.e._from_bytes(vals[self.name], data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.e._to_bytes(self.get_val(vals))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def f(self, name: str, **kw) -> Field:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.F(self, name, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Sequence:</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' A sequence of repeating elements (e.g. TLVs). '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # The item of sequence</span><br><span style="color: hsl(120, 100%, 40%);">+ ITEM: Optional[Envelope] = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ if (self.ITEM is None) and ('item' not in kw):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ProtocolError('Missing Sequence item')</span><br><span style="color: hsl(120, 100%, 40%);">+ self._item = kw.get('item', self.ITEM) # type: Envelope</span><br><span style="color: hsl(120, 100%, 40%);">+ self._item.check_len = False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def from_bytes(self, data: bytes) -> list:</span><br><span style="color: hsl(120, 100%, 40%);">+ proc = self._item._from_bytes</span><br><span style="color: hsl(120, 100%, 40%);">+ vseq, offset = [], 0</span><br><span style="color: hsl(120, 100%, 40%);">+ length = len(data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ while offset < length:</span><br><span style="color: hsl(120, 100%, 40%);">+ vseq.append({ }) # new item of sequence</span><br><span style="color: hsl(120, 100%, 40%);">+ offset += proc(vseq[-1], data[offset:])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return vseq</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def to_bytes(self, vseq: list) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ proc = self._item._to_bytes</span><br><span style="color: hsl(120, 100%, 40%);">+ return b''.join([proc(v) for v in vseq])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ class F(Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ ''' Field wrapper. '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, s: 'Sequence', name: str, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ Field.__init__(self, name, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.s = s</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = self.s.from_bytes(data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.s.to_bytes(self.get_val(vals))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def f(self, name: str, **kw) -> Field:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.F(self, name, **kw)</span><br><span>diff --git a/src/target/trx_toolkit/test_codec.py b/src/target/trx_toolkit/test_codec.py</span><br><span>new file mode 100644</span><br><span>index 0000000..e0649d8</span><br><span>--- /dev/null</span><br><span>+++ b/src/target/trx_toolkit/test_codec.py</span><br><span>@@ -0,0 +1,592 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/usr/bin/env python3</span><br><span style="color: hsl(120, 100%, 40%);">+# -*- coding: utf-8 -*-</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+'''</span><br><span style="color: hsl(120, 100%, 40%);">+Unit tests for declarative message codec.</span><br><span style="color: hsl(120, 100%, 40%);">+'''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# All Rights Reserved</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software; you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+# the Free Software Foundation; either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+# (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License along</span><br><span style="color: hsl(120, 100%, 40%);">+# with this program; if not, write to the Free Software Foundation, Inc.,</span><br><span style="color: hsl(120, 100%, 40%);">+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import unittest</span><br><span style="color: hsl(120, 100%, 40%);">+import struct</span><br><span style="color: hsl(120, 100%, 40%);">+import codec</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class TestField(codec.Field):</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_PARAMS = { 'key' : 0xde }</span><br><span style="color: hsl(120, 100%, 40%);">+ DEF_LEN = 4</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @staticmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def xor(data: bytes, key: int = 0x00):</span><br><span style="color: hsl(120, 100%, 40%);">+ return bytes([x ^ key for x in data])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _from_bytes(self, vals: dict, data: bytes) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals[self.name] = self.xor(data, self.p['key'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _to_bytes(self, vals: dict) -> bytes:</span><br><span style="color: hsl(120, 100%, 40%);">+ return self.xor(self.get_val(vals), self.p['key'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Field(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ MAGIC = b'\xde\xad\xbe\xef'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'magic' : self.MAGIC, 'other' : 'unrelated' }</span><br><span style="color: hsl(120, 100%, 40%);">+ encoded_de = TestField.xor(self.MAGIC, 0xde)</span><br><span style="color: hsl(120, 100%, 40%);">+ encoded_88 = TestField.xor(self.MAGIC, 0x88)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('default length=4, default key=0xde'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes(vals), encoded_de)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('different length=2, default key=0xde'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', len=2)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals['magic'] = vals['magic'][:2]</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes(vals), encoded_de[:2])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('EncodeError due to length mismatch'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', len=8)</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.EncodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.to_bytes(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ encoded_de = TestField.xor(self.MAGIC, 0xde) + b'\xff' * 60</span><br><span style="color: hsl(120, 100%, 40%);">+ encoded_88 = TestField.xor(self.MAGIC, 0x88) + b'\xff' * 60</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'magic' : 'overrien', 'other' : 'unchanged' }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('default length=4, default key=0xde'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic')</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, encoded_de)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['other'], 'unchanged')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(self.MAGIC))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('default length=4, different key=0x88'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', key=0x88)</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, encoded_88)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['other'], 'unchanged')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(self.MAGIC))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('different length=2, default key=0xde'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', len=2)</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, encoded_de)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['other'], 'unchanged')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], self.MAGIC[:2])</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, 2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('full length, different key=0x88'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', len=0, key=0x88)</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, encoded_88)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['other'], 'unchanged')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], self.MAGIC + b'\x77' * 60)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(encoded_88))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('DecodeError due to short read'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', len=4)</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_get_pres(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'magic' : self.MAGIC }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('to_bytes() for a non-existing field'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('not-there')</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(KeyError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.to_bytes(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('to_bytes() for a field with get_pres()'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', key=0x00)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.get_pres = lambda v: not v['omit']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ data = field.to_bytes({ **vals, 'omit' : False })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(data, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ data = field.to_bytes({ **vals, 'omit' : True })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(data, b'')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('from_bytes() for a field with get_pres()'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', key=0x00)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.get_pres = lambda v: not v['omit']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'omit' : False }</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(self.MAGIC))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'omit' : True }</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertFalse('magic' in vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, 0)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_get_len(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'len' : 32, 'unrelated' : 'foo' }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', key=0x00)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.get_len = lambda v, _: v['len']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('not enough octets in the buffer: 16 < 32'):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\xff' * 16)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('more than enough octets in the buffer'):</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, b'\xff' * 64)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['magic'], b'\xff' * 32)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, 32)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('length field does not exist'):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(KeyError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes({ }, b'\xff' * 64)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_get_val(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = TestField('magic', key=0x00, len=0)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.get_val = lambda v: v.get('val', self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('value is present in the dict'):</span><br><span style="color: hsl(120, 100%, 40%);">+ data = field.to_bytes({ 'val' : b'\xd0\xde' })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(data, b'\xd0\xde')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('value is not present in the dict'):</span><br><span style="color: hsl(120, 100%, 40%);">+ data = field.to_bytes({ })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(data, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Buf(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ MAGIC = b'\xde\xad' * 4</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'buf' : self.MAGIC }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('with no length constraints'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf') # default: len=0</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes(vals), self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('with length constraints'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf', len=len(self.MAGIC))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes(vals), self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('EncodeError due to length mismatch'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf', len=4)</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.EncodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.to_bytes(vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('with no length constraints'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf') # default: len=0</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['buf'], self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(self.MAGIC))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('with length constraints'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf', len=2)</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(vals, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['buf'], self.MAGIC[:2])</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(self.MAGIC[:2]))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('DecodeError due to not enough bytes'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Buf('buf', len=64)</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, self.MAGIC)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Spare(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ # Fixed length with custom filler</span><br><span style="color: hsl(120, 100%, 40%);">+ SAA = codec.Spare('pad', len=4, filler=b'\xaa')</span><br><span style="color: hsl(120, 100%, 40%);">+ # Auto-calculated length with custom filler</span><br><span style="color: hsl(120, 100%, 40%);">+ SFF = codec.Spare('pad', filler=b'\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ SFF.get_len = lambda v, _: v['len']</span><br><span style="color: hsl(120, 100%, 40%);">+ # Fixed length with default filler</span><br><span style="color: hsl(120, 100%, 40%);">+ S00 = codec.Spare('pad', len=2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.SFF.to_bytes({ 'len' : 8 }), b'\xff' * 8)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.SAA.to_bytes({ }), b'\xaa' * 4)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S00.to_bytes({ }), b'\x00' * 2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.S00.from_bytes({ }, b'\x00') # Short read</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.SFF.from_bytes({ 'len' : 8 }, b'\xff' * 8), 8)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.SAA.from_bytes({ }, b'\xaa' * 64), 4)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S00.from_bytes({ }, b'\x00' * 64), 2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Uint(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ def _test_uint(self, field, fmt, vals):</span><br><span style="color: hsl(120, 100%, 40%);">+ for i in vals:</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('to_bytes()'):</span><br><span style="color: hsl(120, 100%, 40%);">+ val = field.to_bytes({ field.name : i })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(val, struct.pack(fmt, i))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('from_bytes()'):</span><br><span style="color: hsl(120, 100%, 40%);">+ data, parsed = struct.pack(fmt, i), { }</span><br><span style="color: hsl(120, 100%, 40%);">+ offset = field.from_bytes(parsed, data)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(offset, len(data))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(parsed[field.name], i)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_uint8(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Uint('foo'), 'B', range(2 ** 8))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_int8(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Int('foo'), 'b', range(-128, 128))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_uint16(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = (0, 65, 128, 255, 512, 1023, 2 ** 16 - 1)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Uint16BE('foo'), '>H', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Uint16LE('foo'), '<H', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_int16(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = (-32767, -16384, 0, 16384, 32767)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Int16BE('foo'), '>h', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Int16LE('foo'), '<h', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_uint32(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = (0, 33, 255, 1024, 1337, 4099, 2 ** 32 - 1)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Uint32BE('foo'), '>I', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Uint32LE('foo'), '<I', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_int32(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = (-2147483647, 0, 2147483647)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Int32BE('foo'), '>i', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._test_uint(codec.Int32LE('foo'), '<i', vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_offset_mult(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('encode / decode with offset=5'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Uint('foo', offset=5)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 10 }), b'\x05')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 5 }), b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'foo' : 'overriden' }</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['foo'], 260)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['foo'], 5)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('encode / decode with mult=2'):</span><br><span style="color: hsl(120, 100%, 40%);">+ field = codec.Uint('foo', mult=2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 0 }), b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 3 }), b'\x01')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 32 }), b'\x10')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(field.to_bytes({ 'foo' : 64 }), b'\x20')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'foo' : 'overriden' }</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['foo'], 0 * 2)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\x0f')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['foo'], 15 * 2)</span><br><span style="color: hsl(120, 100%, 40%);">+ field.from_bytes(vals, b'\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(vals['foo'], 255 * 2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class BitFieldSet(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ S16 = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4a', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f8', bl=8),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4b', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ S8M = codec.BitFieldSet(order='msb', set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f1', bl=1),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f3', bl=3),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ S8L = codec.BitFieldSet(order='lsb', set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f1', bl=1),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f3', bl=3),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ S8V = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4, val=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f1', bl=1, val=0),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f3', bl=3),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ S8P = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField.Spare(bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @staticmethod</span><br><span style="color: hsl(120, 100%, 40%);">+ def from_bytes(s: codec.BitFieldSet, data: bytes) -> dict:</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { }</span><br><span style="color: hsl(120, 100%, 40%);">+ s.from_bytes(vals, data)</span><br><span style="color: hsl(120, 100%, 40%);">+ return vals</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_len_auto(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('1 + 2 = 3 bits => 1 octet (with padding)'):</span><br><span style="color: hsl(120, 100%, 40%);">+ s = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f1', bl=1),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f2', bl=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(s.len, 1)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('4 + 2 + 2 = 8 bits => 1 octet'):</span><br><span style="color: hsl(120, 100%, 40%);">+ s = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f2a', bl=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f2b', bl=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(s.len, 1)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('12 + 4 + 2 = 18 bits => 3 octets (with padding)'):</span><br><span style="color: hsl(120, 100%, 40%);">+ s = codec.BitFieldSet(set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f12', bl=12),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f2', bl=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(s.len, 3)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_overflow(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.ProtocolError):</span><br><span style="color: hsl(120, 100%, 40%);">+ s = codec.BitFieldSet(len=1, set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f6', bl=6),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('f4', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ ))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_offset_mask(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ calc = lambda s: [(f.name, f.offset, f.mask) for f in s._fields]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('16 bit total (MSB): f4a + f8 + f4b'):</span><br><span style="color: hsl(120, 100%, 40%);">+ om = [('f4a', 8 + 4, 0x0f), ('f8', 4, 0xff), ('f4b', 0, 0x0f)]</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(len(self.S16._fields), 3)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(calc(self.S16), om)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (MSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ om = [('f4', 1 + 3, 0x0f), ('f1', 3, 0x01), ('f3', 0, 0x07)]</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(len(self.S8M._fields), 3)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(calc(self.S8M), om)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (LSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ om = [('f3', 1 + 4, 0x07), ('f1', 4, 0x01), ('f4', 0, 0x0f)]</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(len(self.S8L._fields), 3)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(calc(self.S8L), om)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (LSB): s4 + f4'):</span><br><span style="color: hsl(120, 100%, 40%);">+ om = [(None, 4, 0x0f), ('f4', 0, 0x0f)]</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(len(self.S8P._fields), 2)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(calc(self.S8P), om)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('16 bit total (MSB): f4a + f8 + f4b'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x0f, 'f8' : 0xff, 'f4b' : 0x0f }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S16.to_bytes(vals), b'\xff\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x00, 'f8' : 0x00, 'f4b' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S16.to_bytes(vals), b'\x00\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x0f, 'f8' : 0x00, 'f4b' : 0x0f }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S16.to_bytes(vals), b'\xf0\x0f')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x00, 'f8' : 0xff, 'f4b' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S16.to_bytes(vals), b'\x0f\xf0')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (MSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x01, 'f3' : 0x07 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8M.to_bytes(vals), b'\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x00, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8M.to_bytes(vals), b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8M.to_bytes(vals), b'\xf0')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (LSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x01, 'f3' : 0x07 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8L.to_bytes(vals), b'\xff')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x00, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8L.to_bytes(vals), b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8L.to_bytes(vals), b'\x0f')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pad = b'\xff' * 64</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('16 bit total (MSB): f4a + f8 + f4b'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x0f, 'f8' : 0xff, 'f4b' : 0x0f }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S16, b'\xff\xff' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x00, 'f8' : 0x00, 'f4b' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S16, b'\x00\x00' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x0f, 'f8' : 0x00, 'f4b' : 0x0f }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S16, b'\xf0\x0f' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4a' : 0x00, 'f8' : 0xff, 'f4b' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S16, b'\x0f\xf0' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (MSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x01, 'f3' : 0x07 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8M, b'\xff' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x00, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8M, b'\x00' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8M, b'\xf0' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('8 bit total (LSB): f4 + f1 + f3'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x01, 'f3' : 0x07 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8L, b'\xff' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x00, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8L, b'\x00' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 0x0f, 'f1' : 0x00, 'f3' : 0x00 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8L, b'\x0f' + pad), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes_val(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('fixed values in absence of user-supplied values'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f3' : 0x00 } # | { 'f4' : 2, 'f1' : 0 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8V.to_bytes(vals), b'\x20')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.subTest('fixed values take precedence'):</span><br><span style="color: hsl(120, 100%, 40%);">+ vals = { 'f4' : 1, 'f1' : 1, 'f3' : 0 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8V.to_bytes(vals), b'\x20')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes_val(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.S8V.from_bytes({ }, b'\xf0') # 'f4': 15 vs 2</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.S8V.from_bytes({ }, b'\x08') # 'f1': 1 vs 0</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Field 'f3' takes any value, no exceptions shall be raised</span><br><span style="color: hsl(120, 100%, 40%);">+ for i in range(8):</span><br><span style="color: hsl(120, 100%, 40%);">+ data, vals = bytes([0x20 + i]), { 'f4' : 2, 'f1' : 0, 'f3' : i }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8V, data), vals)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes_spare(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8P.to_bytes({ 'f4' : 0x00 }), b'\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8P.to_bytes({ 'f4' : 0x0f }), b'\x0f')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.S8P.to_bytes({ 'f4' : 0xff }), b'\x0f')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes_spare(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8P, b'\x00'), { 'f4' : 0x00 })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8P, b'\x0f'), { 'f4' : 0x0f })</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(self.from_bytes(self.S8P, b'\xff'), { 'f4' : 0x0f })</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class TestPDU(codec.Envelope):</span><br><span style="color: hsl(120, 100%, 40%);">+ STRUCT = (</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitFieldSet(len=2, set=(</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('ver', bl=4),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.BitField('flag', bl=1),</span><br><span style="color: hsl(120, 100%, 40%);">+ )),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Uint16BE('len'),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Buf('data'),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Buf('tail', len=2),</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, *args, **kw):</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Envelope.__init__(self, *args, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.STRUCT[-3].get_val = lambda v: len(v['data'])</span><br><span style="color: hsl(120, 100%, 40%);">+ self.STRUCT[-2].get_len = lambda v, _: v['len']</span><br><span style="color: hsl(120, 100%, 40%);">+ self.STRUCT[-1].get_pres = lambda v: bool(v['flag'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def check(self, vals: dict) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ if not vals['ver'] in (0, 1, 2):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError('Unknown version %d' % vals['ver'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Envelope(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_rest_octets(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU(check_len=False)</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\x00' * 64)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.DecodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU(check_len=True)</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\x00' * 64) # 'len' : 0</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_field_raises(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU()</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(codec.EncodeError):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.c = { 'ver' : 0, 'flag' : 1, 'data' : b'\xff' * 16 }</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.to_bytes() # KeyError: 'tail' not found</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # No content in the new instances</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.c, { })</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.c = { 'ver' : 0, 'flag' : 1, 'data' : b'', 'tail' : b'\xde\xbe' }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.to_bytes(), b'\x08\x00\x00\x00' + b'\xde\xbe')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.c = { 'ver' : 1, 'flag' : 0, 'data' : b'\xff' * 15 }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.to_bytes(), b'\x10\x00\x00\x0f' + b'\xff' * 15)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.c = { 'ver' : 2, 'flag' : 1, 'data' : b'\xf0', 'tail' : b'\xbe\xed' }</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.to_bytes(), b'\x28\x00\x00\x01\xf0\xbe\xed')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # No content in the new instances</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.c, { })</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ c = { 'ver' : 0, 'flag' : 1, 'len' : 0, 'data' : b'', 'tail' : b'\xde\xbe' }</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\x08\x00\x00\x00' + b'\xde\xbe')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.c, c)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ c = { 'ver' : 1, 'flag' : 0, 'len' : 15, 'data' : b'\xff' * 15 }</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\x10\x00\x00\x0f' + b'\xff' * 15)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.c, c)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ c = { 'ver' : 2, 'flag' : 1, 'len' : 1, 'data' : b'\xf0', 'tail' : b'\xbe\xed' }</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\x28\x00\x00\x01\xf0\xbe\xed')</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(pdu.c, c)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes_check(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.c = { 'ver' : 8, 'flag' : 1, 'data' : b'', 'tail' : b'\xde\xbe' }</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(ValueError):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.to_bytes()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes_check(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu = TestPDU()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ with self.assertRaises(ValueError):</span><br><span style="color: hsl(120, 100%, 40%);">+ pdu.from_bytes(b'\xf0\x00\x00\x00')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class Sequence(unittest.TestCase):</span><br><span style="color: hsl(120, 100%, 40%);">+ class TLV(codec.Envelope):</span><br><span style="color: hsl(120, 100%, 40%);">+ STRUCT = (</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Uint('T'),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Uint('L'),</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Buf('V'),</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, *args, **kw) -> None:</span><br><span style="color: hsl(120, 100%, 40%);">+ codec.Envelope.__init__(self, *args, **kw)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.STRUCT[-2].get_val = lambda v: len(v['V'])</span><br><span style="color: hsl(120, 100%, 40%);">+ self.STRUCT[-1].get_len = lambda v, _: v['L']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Sequence of TLVs</span><br><span style="color: hsl(120, 100%, 40%);">+ SEQ = codec.Sequence(item=TLV())</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ Vseq, Bseq = [</span><br><span style="color: hsl(120, 100%, 40%);">+ { 'T' : 0xde, 'L' : 4, 'V' : b'\xde\xad\xbe\xef' },</span><br><span style="color: hsl(120, 100%, 40%);">+ { 'T' : 0xbe, 'L' : 2, 'V' : b'\xbe\xef' },</span><br><span style="color: hsl(120, 100%, 40%);">+ { 'T' : 0xbe, 'L' : 2, 'V' : b'\xef\xbe' },</span><br><span style="color: hsl(120, 100%, 40%);">+ { 'T' : 0x00, 'L' : 0, 'V' : b'' },</span><br><span style="color: hsl(120, 100%, 40%);">+ ], b''.join([</span><br><span style="color: hsl(120, 100%, 40%);">+ b'\xde\x04\xde\xad\xbe\xef',</span><br><span style="color: hsl(120, 100%, 40%);">+ b'\xbe\x02\xbe\xef',</span><br><span style="color: hsl(120, 100%, 40%);">+ b'\xbe\x02\xef\xbe',</span><br><span style="color: hsl(120, 100%, 40%);">+ b'\x00\x00',</span><br><span style="color: hsl(120, 100%, 40%);">+ ])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_to_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ res = self.SEQ.to_bytes(self.Vseq)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(res, self.Bseq)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def test_from_bytes(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ res = self.SEQ.from_bytes(self.Bseq)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.assertEqual(res, self.Vseq)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+if __name__ == '__main__':</span><br><span style="color: hsl(120, 100%, 40%);">+ unittest.main()</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmocom-bb/+/23135">change 23135</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmocom-bb/+/23135"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: osmocom-bb </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I7ff46b278c59af3720ee7f3950ea5a8b2f1313e1 </div>
<div style="display:none"> Gerrit-Change-Number: 23135 </div>
<div style="display:none"> Gerrit-PatchSet: 4 </div>
<div style="display:none"> Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: tnt <tnt@246tNt.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>