<p>fixeria has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/c/osmocom-bb/+/23135">View Change</a></p><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, 765 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/35/23135/1</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..e3cd600</span><br><span>--- /dev/null</span><br><span>+++ b/src/target/trx_toolkit/codec.py</span><br><span>@@ -0,0 +1,294 @@</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 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%);">+               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 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_dec: Callable[[dict, bytes], bool]</span><br><span style="color: hsl(120, 100%, 40%);">+   get_pres_enc: Callable[[dict], bool]</span><br><span style="color: hsl(120, 100%, 40%);">+</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_dec = lambda vals, data: True</span><br><span style="color: hsl(120, 100%, 40%);">+           self.get_pres_enc = lambda vals: True</span><br><span style="color: hsl(120, 100%, 40%);">+         # Field takes it's 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_dec(vals, data) 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_enc(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 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%);">+         vals.update({ f.name : (blob >> f.offset) & f.mask for f in self._fields })</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.get_val(vals) & f.mask) << f.offset</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%);">+       def __init__(self, name: str, bl: int) -> None:</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%);">+                # Ensure proper length</span><br><span style="color: hsl(120, 100%, 40%);">+                if self.bl < 1:</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%);">+   def get_val(self, vals: dict) -> Any:</span><br><span style="color: hsl(120, 100%, 40%);">+              return vals[self.name]</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(Codec):</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.check_len = check_len</span><br><span style="color: hsl(120, 100%, 40%);">+            self.content: dict = { }</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.content[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.content[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.content[key]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       def from_bytes(self, data: bytes, offset: int = 0) -> int:</span><br><span style="color: hsl(120, 100%, 40%);">+         self.content = { } # forget the old content</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(self.content, 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%);">+                return offset</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%);">+               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(self.content)</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%);">+             return b''.join([proc(f) for f in self.STRUCT])</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..0499f3a</span><br><span>--- /dev/null</span><br><span>+++ b/src/target/trx_toolkit/test_codec.py</span><br><span>@@ -0,0 +1,471 @@</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_enc()'):</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_enc = 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_dec()'):</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_dec = 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 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%);">+  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%);">+        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%);">+            def from_bytes(s, data):</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%);">+         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(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(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(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(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(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(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(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(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(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(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%);">+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_dec = lambda v, _: bool(v['flag'])</span><br><span style="color: hsl(120, 100%, 40%);">+           self.STRUCT[-1].get_pres_enc = lambda v: bool(v['flag'])</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.content = { '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.content, { })</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          pdu.content = { '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.content = { '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.content = { '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.content, { })</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          content = { '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.content, content)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              content = { '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.content, content)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              content = { '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.content, content)</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: 1 </div>
<div style="display:none"> Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>