<p>fixeria <strong>merged</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmocom-bb/+/14579">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  pespin: Looks good to me, but someone else must approve
  fixeria: Looks good to me, approved
  Hoernchen: Looks good to me, but someone else must approve

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">trx_toolkit/data_msg.py: introduce header coding version 0x01<br><br>The new version adds the following fields to the TRX2L1 message,<br>keeping the L12TRX message unchanged:<br><br>  +------+-----+-----+-----+--------------------+<br>  | RSSI | ToA | MTS | C/I | soft-bits (254..0) |<br>  +------+-----+-----+-----+--------------------+<br><br>  - MTS (1 octet)  - Modulation and Training Sequence info, and<br>  - C/I (2 octets) - Carrier-to-Interference ratio (big endian).<br><br>== Coding of MTS: Modulation and Training Sequence info<br><br>3GPP TS 45.002 version 15.1.0 defines several modulation types,<br>and a few sets of training sequences for each type. The most<br>common are GMSK and 8-PSK (which is used in EDGE).<br><br>  +-----------------+---------------------------------------+<br>  | 7 6 5 4 3 2 1 0 | bit numbers (value range)             |<br>  +-----------------+---------------------------------------+<br>  | . . . . . X X X | Training Sequence Code (0..7)         |<br>  +-----------------+---------------------------------------+<br>  | . X X X X . . . | Modulation, TS set number (see below) |<br>  +-----------------+---------------------------------------+<br>  | X . . . . . . . | IDLE / nope frame indication (0 or 1) |<br>  +-----------------+---------------------------------------+<br><br>The bit number 7 (MSB) is set to high when either nothing has been<br>detected, or during IDLE frames, so we can deliver noise levels,<br>and avoid clock gaps on the L1 side. Other bits are ignored,<br>and should be set to low (0) in this case.<br><br>== Coding of modulation and TS set number<br><br>GMSK has 4 sets of training sequences (see tables 5.2.3a-d),<br>while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets.<br>Access and Synchronization bursts also have several synch.<br>sequences.<br><br>  +-----------------+---------------------------------------+<br>  | 7 6 5 4 3 2 1 0 | bit numbers (value range)             |<br>  +-----------------+---------------------------------------+<br>  | . 0 0 X X . . . | GMSK, 4 TS sets (0..3)                |<br>  +-----------------+---------------------------------------+<br>  | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1)               |<br>  +-----------------+---------------------------------------+<br>  | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1)               |<br>  +-----------------+---------------------------------------+<br>  | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1)               |<br>  +-----------------+---------------------------------------+<br>  | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1)               |<br>  +-----------------+---------------------------------------+<br>  | . 1 1 1 X . . . | RESERVED (0)                          |<br>  +-----------------+---------------------------------------+<br><br>== C/I: Carrier-to-Interference ratio<br><br>The C/I value is computed from the training sequence of each burst,<br>where we can compare the "ideal" training sequence with the actual<br>training sequence, and then express that difference in centiBels.<br><br>Change-Id: Ie810c5a482d1c908994e8cdd32a2ea641ae7cedd<br>Related: OS#4006, OS#1855<br>---<br>M src/target/trx_toolkit/data_msg.py<br>1 file changed, 362 insertions(+), 33 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/src/target/trx_toolkit/data_msg.py b/src/target/trx_toolkit/data_msg.py</span><br><span>index 5bd04ca..a2996ab 100644</span><br><span>--- a/src/target/trx_toolkit/data_msg.py</span><br><span>+++ b/src/target/trx_toolkit/data_msg.py</span><br><span>@@ -25,8 +25,37 @@</span><br><span> import random</span><br><span> import struct</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+from enum import Enum</span><br><span> from gsm_shared import *</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+class Modulation(Enum):</span><br><span style="color: hsl(120, 100%, 40%);">+        """ Modulation types defined in 3GPP TS 45.002 """</span><br><span style="color: hsl(120, 100%, 40%);">+      ModGMSK  = (0b0000, 148)</span><br><span style="color: hsl(120, 100%, 40%);">+      Mod8PSK  = (0b0100, 444)</span><br><span style="color: hsl(120, 100%, 40%);">+      ModAQPSK = (0b0110, 296)</span><br><span style="color: hsl(120, 100%, 40%);">+      Mod16QAM = (0b1000, 592)</span><br><span style="color: hsl(120, 100%, 40%);">+      Mod32QAM = (0b1010, 740)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, coding, bl):</span><br><span style="color: hsl(120, 100%, 40%);">+               # Coding in TRXD header</span><br><span style="color: hsl(120, 100%, 40%);">+               self.coding = coding</span><br><span style="color: hsl(120, 100%, 40%);">+          # Burst length</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%);">+        @classmethod</span><br><span style="color: hsl(120, 100%, 40%);">+  def pick(self, coding):</span><br><span style="color: hsl(120, 100%, 40%);">+               for mod in list(self):</span><br><span style="color: hsl(120, 100%, 40%);">+                        if mod.coding == coding:</span><br><span style="color: hsl(120, 100%, 40%);">+                              return mod</span><br><span style="color: hsl(120, 100%, 40%);">+            return None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ @classmethod</span><br><span style="color: hsl(120, 100%, 40%);">+  def pick_by_bl(self, bl):</span><br><span style="color: hsl(120, 100%, 40%);">+             for mod in list(self):</span><br><span style="color: hsl(120, 100%, 40%);">+                        if mod.bl == bl:</span><br><span style="color: hsl(120, 100%, 40%);">+                              return mod</span><br><span style="color: hsl(120, 100%, 40%);">+            return None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class DATAMSG:</span><br><span>      """ TRXD (DATA) message codec (common part).</span><br><span> </span><br><span>@@ -92,7 +121,7 @@</span><br><span> </span><br><span>     # NOTE: up to 16 versions can be encoded</span><br><span>     CHDR_VERSION_MAX = 0b1111</span><br><span style="color: hsl(0, 100%, 40%);">-       known_versions = [0x00]</span><br><span style="color: hsl(120, 100%, 40%);">+       known_versions = [0x00, 0x01]</span><br><span> </span><br><span>    # Common constructor</span><br><span>         def __init__(self, fn = None, tn = None, burst = None, ver = 0):</span><br><span>@@ -101,6 +130,12 @@</span><br><span>              self.fn = fn</span><br><span>                 self.tn = tn</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      # The common header length</span><br><span style="color: hsl(120, 100%, 40%);">+    @property</span><br><span style="color: hsl(120, 100%, 40%);">+     def CHDR_LEN(self):</span><br><span style="color: hsl(120, 100%, 40%);">+           # (VER + TN) + FN</span><br><span style="color: hsl(120, 100%, 40%);">+             return 1 + 4</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>       # Generates message specific header</span><br><span>  def gen_hdr(self):</span><br><span>           raise NotImplementedError</span><br><span>@@ -197,12 +232,6 @@</span><br><span>             if not self.ver in self.known_versions:</span><br><span>                      return False</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-                if self.burst is None:</span><br><span style="color: hsl(0, 100%, 40%);">-                  return False</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-            if len(self.burst) not in (GSM_BURST_LEN, EDGE_BURST_LEN):</span><br><span style="color: hsl(0, 100%, 40%);">-                      return False</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>                 if self.fn is None:</span><br><span>                  return False</span><br><span> </span><br><span>@@ -237,7 +266,8 @@</span><br><span>               buf += hdr</span><br><span> </span><br><span>               # Generate burst</span><br><span style="color: hsl(0, 100%, 40%);">-                buf += self.gen_burst()</span><br><span style="color: hsl(120, 100%, 40%);">+               if self.burst is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                    buf += self.gen_burst()</span><br><span> </span><br><span>          # This is a rudiment from (legacy) OpenBTS transceiver,</span><br><span>              # some L1 implementations still expect two dummy bytes.</span><br><span>@@ -248,11 +278,8 @@</span><br><span> </span><br><span>   # Parses a TRX DATA message</span><br><span>  def parse_msg(self, msg):</span><br><span style="color: hsl(0, 100%, 40%);">-               # Calculate message length</span><br><span style="color: hsl(0, 100%, 40%);">-              length = len(msg)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               # Check length</span><br><span style="color: hsl(0, 100%, 40%);">-          if length < (self.HDR_LEN + GSM_BURST_LEN):</span><br><span style="color: hsl(120, 100%, 40%);">+                # Make sure we have at least header</span><br><span style="color: hsl(120, 100%, 40%);">+           if len(msg) < self.HDR_LEN:</span><br><span>                       raise ValueError("Message is to short")</span><br><span> </span><br><span>                # Parse version and TDMA TN</span><br><span>@@ -267,7 +294,10 @@</span><br><span> </span><br><span>               # Copy burst, skipping header</span><br><span>                msg_burst = msg[self.HDR_LEN:]</span><br><span style="color: hsl(0, 100%, 40%);">-          self.parse_burst(msg_burst)</span><br><span style="color: hsl(120, 100%, 40%);">+           if len(msg_burst) > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                     self.parse_burst(msg_burst)</span><br><span style="color: hsl(120, 100%, 40%);">+           else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 self.burst = None</span><br><span> </span><br><span> class DATAMSG_L12TRX(DATAMSG):</span><br><span>      """ L12TRX (L1 -> TRX) message codec.</span><br><span>@@ -276,6 +306,8 @@</span><br><span>        or an Uplink burst on the MS side, and has the following</span><br><span>     message specific fixed-size header preceding the burst bits:</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      == Versions 0x00, 0x01</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>       +-----+--------------------+</span><br><span>         | PWR | hard-bits (1 or 0) |</span><br><span>         +-----+--------------------+</span><br><span>@@ -290,13 +322,26 @@</span><br><span>       """</span><br><span> </span><br><span>       # Constants</span><br><span style="color: hsl(0, 100%, 40%);">-     HDR_LEN = 6</span><br><span>  PWR_MIN = 0x00</span><br><span>       PWR_MAX = 0xff</span><br><span> </span><br><span>   # Specific message fields</span><br><span>    pwr = None</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+        # Calculates header length depending on its version</span><br><span style="color: hsl(120, 100%, 40%);">+   @property</span><br><span style="color: hsl(120, 100%, 40%);">+     def HDR_LEN(self):</span><br><span style="color: hsl(120, 100%, 40%);">+            # Common header length</span><br><span style="color: hsl(120, 100%, 40%);">+                length = self.CHDR_LEN</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              # Message specific header length</span><br><span style="color: hsl(120, 100%, 40%);">+              if self.ver in (0x00, 0x01):</span><br><span style="color: hsl(120, 100%, 40%);">+                  length += 1 # PWR</span><br><span style="color: hsl(120, 100%, 40%);">+             else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 raise IndexError("Unhandled version %u" % self.ver)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               return length</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>      # Validates the message fields</span><br><span>       def validate(self):</span><br><span>          # Validate common fields</span><br><span>@@ -309,6 +354,14 @@</span><br><span>              if self.pwr < self.PWR_MIN or self.pwr > self.PWR_MAX:</span><br><span>                         return False</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+              # FIXME: properly handle IDLE / NOPE indications</span><br><span style="color: hsl(120, 100%, 40%);">+              if self.burst is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                        return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                # FIXME: properly handle IDLE / NOPE indications</span><br><span style="color: hsl(120, 100%, 40%);">+              if len(self.burst) not in (GSM_BURST_LEN, EDGE_BURST_LEN):</span><br><span style="color: hsl(120, 100%, 40%);">+                    return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>               return True</span><br><span> </span><br><span>      # Generates a random power level</span><br><span>@@ -394,16 +447,78 @@</span><br><span>     or a Downlink burst on the MS side, and has the following</span><br><span>    message specific fixed-size header preceding the burst bits:</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      == Version 0x00</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>      +------+-----+--------------------+</span><br><span>          | RSSI | ToA | soft-bits (254..0) |</span><br><span>          +------+-----+--------------------+</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+     == Version 0x01</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%);">+       | RSSI | ToA | MTS | C/I | soft-bits (254..0) |</span><br><span style="color: hsl(120, 100%, 40%);">+       +------+-----+-----+-----+--------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  where:</span><br><span> </span><br><span>     - RSSI (1 octet) - Received Signal Strength Indication</span><br><span>                          encoded without the negative sign.</span><br><span>        - ToA (2 octets) - Timing of Arrival in units of 1/256</span><br><span>                          of symbol (big endian).</span><br><span style="color: hsl(120, 100%, 40%);">+    - MTS (1 octet)  - Modulation and Training Sequence info.</span><br><span style="color: hsl(120, 100%, 40%);">+     - C/I (2 octets) - Carrier-to-Interference ratio (big endian).</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    == Coding of MTS: Modulation and Training Sequence info</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     3GPP TS 45.002 version 15.1.0 defines several modulation types,</span><br><span style="color: hsl(120, 100%, 40%);">+       and a few sets of training sequences for each type. The most</span><br><span style="color: hsl(120, 100%, 40%);">+  common are GMSK and 8-PSK (which is used in EDGE).</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%);">+   | 7 6 5 4 3 2 1 0 | bit numbers (value range)             |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . . . . . X X X | Training Sequence Code (0..7)         |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . X X X X . . . | Modulation, TS set number (see below) |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | X . . . . . . . | IDLE / nope frame indication (0 or 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%);">+       The bit number 7 (MSB) is set to high when either nothing has been</span><br><span style="color: hsl(120, 100%, 40%);">+    detected, or during IDLE frames, so we can deliver noise levels,</span><br><span style="color: hsl(120, 100%, 40%);">+      and avoid clock gaps on the L1 side. Other bits are ignored,</span><br><span style="color: hsl(120, 100%, 40%);">+  and should be set to low (0) in this case. L16 shall be set to 0x00.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        == Coding of modulation and TS set number</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   GMSK has 4 sets of training sequences (see tables 5.2.3a-d),</span><br><span style="color: hsl(120, 100%, 40%);">+  while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets.</span><br><span style="color: hsl(120, 100%, 40%);">+ Access and Synchronization bursts also have several synch.</span><br><span style="color: hsl(120, 100%, 40%);">+    sequences.</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%);">+   | 7 6 5 4 3 2 1 0 | bit numbers (value range)             |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 0 0 X X . . . | GMSK, 4 TS sets (0..3)                |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1)               |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1)               |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1)               |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1)               |</span><br><span style="color: hsl(120, 100%, 40%);">+   +-----------------+---------------------------------------+</span><br><span style="color: hsl(120, 100%, 40%);">+   | . 1 1 1 X . . . | RESERVED (0)                          |</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/I: Carrier-to-Interference ratio</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       The C/I value can be computed from the training sequence of each</span><br><span style="color: hsl(120, 100%, 40%);">+      burst, where we can compare the "ideal" training sequence with</span><br><span style="color: hsl(120, 100%, 40%);">+      the actual training sequence and then express that in centiBels.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    == Coding of the burst bits</span><br><span> </span><br><span>      Unlike to be transmitted bursts, the received bursts are designated</span><br><span>  using the soft-bits notation, so the receiver can indicate its</span><br><span>@@ -416,9 +531,6 @@</span><br><span> </span><br><span>     """</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-  # Constants</span><br><span style="color: hsl(0, 100%, 40%);">-     HDR_LEN = 8</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>  # rxlev2dbm(0..63) gives us [-110..-47], plus -10 dbm for noise</span><br><span>      RSSI_MIN = -120</span><br><span>      RSSI_MAX = -47</span><br><span>@@ -427,11 +539,81 @@</span><br><span>       TOA256_MIN = -32768</span><br><span>  TOA256_MAX = 32767</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+        # TSC (Training Sequence Code) range</span><br><span style="color: hsl(120, 100%, 40%);">+  TSC_RANGE = range(0, 8)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     # C/I range (in centiBels)</span><br><span style="color: hsl(120, 100%, 40%);">+    CI_MIN = -1280</span><br><span style="color: hsl(120, 100%, 40%);">+        CI_MAX = 1280</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       # IDLE frame / nope detection indicator</span><br><span style="color: hsl(120, 100%, 40%);">+       NOPE_IND = (1 << 7)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  # Specific message fields</span><br><span>    rssi = None</span><br><span>  toa256 = None</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-       # Validates the message fields</span><br><span style="color: hsl(120, 100%, 40%);">+        # Version 0x01 specific (default values)</span><br><span style="color: hsl(120, 100%, 40%);">+      mod_type = Modulation.ModGMSK</span><br><span style="color: hsl(120, 100%, 40%);">+ nope_ind = False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    tsc_set = None</span><br><span style="color: hsl(120, 100%, 40%);">+        tsc = None</span><br><span style="color: hsl(120, 100%, 40%);">+    ci = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   # Calculates header length depending on its version</span><br><span style="color: hsl(120, 100%, 40%);">+   @property</span><br><span style="color: hsl(120, 100%, 40%);">+     def HDR_LEN(self):</span><br><span style="color: hsl(120, 100%, 40%);">+            # Common header length</span><br><span style="color: hsl(120, 100%, 40%);">+                length = self.CHDR_LEN</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              # Message specific header length</span><br><span style="color: hsl(120, 100%, 40%);">+              if self.ver == 0x00:</span><br><span style="color: hsl(120, 100%, 40%);">+                  # RSSI + ToA</span><br><span style="color: hsl(120, 100%, 40%);">+                  length += 1 + 2</span><br><span style="color: hsl(120, 100%, 40%);">+               elif self.ver == 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                        # RSSI + ToA + TS + C/I</span><br><span style="color: hsl(120, 100%, 40%);">+                       length += 1 + 2 + 1 + 2</span><br><span style="color: hsl(120, 100%, 40%);">+               else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 raise IndexError("Unhandled version %u" % self.ver)</span><br><span style="color: hsl(120, 100%, 40%);">+</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 _validate_burst_v0(self):</span><br><span style="color: hsl(120, 100%, 40%);">+         # Burst is mandatory</span><br><span style="color: hsl(120, 100%, 40%);">+          if self.burst is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                        return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                # ... and can be either of GSM (GMSK) or EDGE (8-PSK)</span><br><span style="color: hsl(120, 100%, 40%);">+         if len(self.burst) not in (GSM_BURST_LEN, EDGE_BURST_LEN):</span><br><span style="color: hsl(120, 100%, 40%);">+                    return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                return True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def _validate_burst_v1(self):</span><br><span style="color: hsl(120, 100%, 40%);">+         # Burst is omitted in case of an IDLE / NOPE indication</span><br><span style="color: hsl(120, 100%, 40%);">+               if self.nope_ind and self.burst is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                      return True</span><br><span style="color: hsl(120, 100%, 40%);">+           if self.nope_ind and self.burst is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                  return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                if self.burst is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                        return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                # Burst length depends on modulation type</span><br><span style="color: hsl(120, 100%, 40%);">+             if len(self.burst) != self.mod_type.bl:</span><br><span style="color: hsl(120, 100%, 40%);">+                       return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                return True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Validates the burst</span><br><span style="color: hsl(120, 100%, 40%);">+ def validate_burst(self):</span><br><span style="color: hsl(120, 100%, 40%);">+             if self.ver == 0x00:</span><br><span style="color: hsl(120, 100%, 40%);">+                  return self._validate_burst_v0()</span><br><span style="color: hsl(120, 100%, 40%);">+              elif self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                     return self._validate_burst_v1()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    # Validates the message header fields</span><br><span>        def validate(self):</span><br><span>          # Validate common fields</span><br><span>             if not DATAMSG.validate(self):</span><br><span>@@ -449,6 +631,35 @@</span><br><span>                if self.toa256 < self.TOA256_MIN or self.toa256 > self.TOA256_MAX:</span><br><span>                     return False</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+              if self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                       if type(self.mod_type) is not Modulation:</span><br><span style="color: hsl(120, 100%, 40%);">+                             return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.tsc_set is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                              return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.mod_type is Modulation.ModGMSK:</span><br><span style="color: hsl(120, 100%, 40%);">+                               if self.tsc_set not in range(0, 4):</span><br><span style="color: hsl(120, 100%, 40%);">+                                   return False</span><br><span style="color: hsl(120, 100%, 40%);">+                  else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         if self.tsc_set not in range(0, 2):</span><br><span style="color: hsl(120, 100%, 40%);">+                                   return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.tsc is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                          return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.tsc not in self.TSC_RANGE:</span><br><span style="color: hsl(120, 100%, 40%);">+                            return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.ci is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                           return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                        if self.ci < self.CI_MIN or self.ci > self.CI_MAX:</span><br><span style="color: hsl(120, 100%, 40%);">+                              return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                if not self.validate_burst():</span><br><span style="color: hsl(120, 100%, 40%);">+                 return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>               return True</span><br><span> </span><br><span>      # Generates a random RSSI value</span><br><span>@@ -477,6 +688,17 @@</span><br><span>               self.rssi = self.rand_rssi()</span><br><span>                 self.toa256 = self.rand_toa256()</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+          if self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                       self.mod_type = random.choice(list(Modulation))</span><br><span style="color: hsl(120, 100%, 40%);">+                       if self.mod_type is Modulation.ModGMSK:</span><br><span style="color: hsl(120, 100%, 40%);">+                               self.tsc_set = random.randint(0, 3)</span><br><span style="color: hsl(120, 100%, 40%);">+                   else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         self.tsc_set = random.randint(0, 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.tsc = random.choice(self.TSC_RANGE)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                    # C/I: Carrier-to-Interference ratio</span><br><span style="color: hsl(120, 100%, 40%);">+                  self.ci = random.randint(self.CI_MIN, self.CI_MAX)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         # Generates human-readable header description</span><br><span>        def desc_hdr(self):</span><br><span>          # Describe the common part</span><br><span>@@ -488,9 +710,61 @@</span><br><span>            if self.toa256 is not None:</span><br><span>                  result += ("toa256=%d " % self.toa256)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+          if self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                       if not self.nope_ind:</span><br><span style="color: hsl(120, 100%, 40%);">+                         if self.mod_type is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                                 result += ("%s " % self.mod_type)</span><br><span style="color: hsl(120, 100%, 40%);">+                           if self.tsc_set is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                                  result += ("set=%u " % self.tsc_set)</span><br><span style="color: hsl(120, 100%, 40%);">+                                if self.tsc is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                                      result += ("tsc=%u " % self.tsc)</span><br><span style="color: hsl(120, 100%, 40%);">+                            if self.ci is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+                                       result += ("C/I=%d cB " % self.ci)</span><br><span style="color: hsl(120, 100%, 40%);">+                  else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         result += "(IDLE / NOPE IND) "</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>           # Strip useless whitespace and return</span><br><span>                return result.strip()</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+     # Encodes Modulation and Training Sequence info</span><br><span style="color: hsl(120, 100%, 40%);">+       def gen_mts(self):</span><br><span style="color: hsl(120, 100%, 40%);">+            # IDLE / nope indication has no MTS info</span><br><span style="color: hsl(120, 100%, 40%);">+              if self.nope_ind:</span><br><span style="color: hsl(120, 100%, 40%);">+                     return self.NOPE_IND</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                # TSC: . . . . . X X X</span><br><span style="color: hsl(120, 100%, 40%);">+                mts = self.tsc & 0b111</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          # MTS: . X X X X . . .</span><br><span style="color: hsl(120, 100%, 40%);">+                mts |= self.mod_type.coding << 3</span><br><span style="color: hsl(120, 100%, 40%);">+                mts |= self.tsc_set << 3</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              return mts</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  # Parses Modulation and Training Sequence info</span><br><span style="color: hsl(120, 100%, 40%);">+        def parse_mts(self, mts):</span><br><span style="color: hsl(120, 100%, 40%);">+             # IDLE / nope indication has no MTS info</span><br><span style="color: hsl(120, 100%, 40%);">+              self.nope_ind = (mts & self.NOPE_IND) > 0</span><br><span style="color: hsl(120, 100%, 40%);">+              if self.nope_ind:</span><br><span style="color: hsl(120, 100%, 40%);">+                     self.mod_type = None</span><br><span style="color: hsl(120, 100%, 40%);">+                  self.tsc_set = None</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.tsc = None</span><br><span style="color: hsl(120, 100%, 40%);">+                       return</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              # TSC: . . . . . X X X</span><br><span style="color: hsl(120, 100%, 40%);">+                self.tsc = mts & 0b111</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          # MTS: . X X X X . . .</span><br><span style="color: hsl(120, 100%, 40%);">+                mts = (mts >> 3) & 0b1111</span><br><span style="color: hsl(120, 100%, 40%);">+           if (mts & 0b1100) > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                 # Mask: . . . . M M M S</span><br><span style="color: hsl(120, 100%, 40%);">+                       self.mod_type = Modulation.pick(mts & 0b1110)</span><br><span style="color: hsl(120, 100%, 40%);">+                     self.tsc_set = mts & 0b1</span><br><span style="color: hsl(120, 100%, 40%);">+          else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 # GMSK: . . . . 0 0 S S</span><br><span style="color: hsl(120, 100%, 40%);">+                       self.mod_type = Modulation.ModGMSK</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.tsc_set = mts & 0b11</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>      # Generates message specific header part</span><br><span>     def gen_hdr(self):</span><br><span>           # Allocate an empty byte-array</span><br><span>@@ -503,6 +777,17 @@</span><br><span>                # Big endian, 2 bytes (int32_t)</span><br><span>              buf += struct.pack(">h", self.toa256)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+                if self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                       # Modulation and Training Sequence info</span><br><span style="color: hsl(120, 100%, 40%);">+                       mts = self.gen_mts()</span><br><span style="color: hsl(120, 100%, 40%);">+                  buf.append(mts)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                     # C/I: Carrier-to-Interference ratio (in centiBels)</span><br><span style="color: hsl(120, 100%, 40%);">+                   if not self.nope_ind:</span><br><span style="color: hsl(120, 100%, 40%);">+                         buf += struct.pack(">h", self.ci)</span><br><span style="color: hsl(120, 100%, 40%);">+                        else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         buf += bytearray(2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>                return buf</span><br><span> </span><br><span>       # Parses message specific header part</span><br><span>@@ -513,6 +798,16 @@</span><br><span>                 # Parse ToA (Time of Arrival)</span><br><span>                self.toa256 = struct.unpack(">h", hdr[6:8])[0]</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+               if self.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                       # Modulation and Training Sequence info</span><br><span style="color: hsl(120, 100%, 40%);">+                       self.parse_mts(hdr[8])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                      # C/I: Carrier-to-Interference ratio (in centiBels)</span><br><span style="color: hsl(120, 100%, 40%);">+                   if not self.nope_ind:</span><br><span style="color: hsl(120, 100%, 40%);">+                         self.ci = struct.unpack(">h", hdr[9:11])[0]</span><br><span style="color: hsl(120, 100%, 40%);">+                      else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         self.ci = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>     # Generates message specific burst</span><br><span>   def gen_burst(self):</span><br><span>                 # Convert soft-bits to unsigned soft-bits</span><br><span>@@ -521,26 +816,38 @@</span><br><span>            # Encode to bytes</span><br><span>            return bytearray(burst_usbits)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    # Parses message specific burst for header version 0</span><br><span style="color: hsl(120, 100%, 40%);">+  def _parse_burst_v0(self, burst):</span><br><span style="color: hsl(120, 100%, 40%);">+             bl = len(burst)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             # We need to guess modulation by the length of burst</span><br><span style="color: hsl(120, 100%, 40%);">+          self.mod_type = Modulation.pick_by_bl(bl)</span><br><span style="color: hsl(120, 100%, 40%);">+             if self.mod_type is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                     # Some old transceivers append two dummy bytes</span><br><span style="color: hsl(120, 100%, 40%);">+                        self.mod_type = Modulation.pick_by_bl(bl - 2)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               if self.mod_type is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                     raise ValueError("Odd burst length")</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              return burst[:self.mod_type.bl]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>    # Parses message specific burst</span><br><span>      def parse_burst(self, burst):</span><br><span style="color: hsl(0, 100%, 40%);">-           length = len(burst)</span><br><span style="color: hsl(120, 100%, 40%);">+           burst = list(burst)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-         # Distinguish between GSM and EDGE</span><br><span style="color: hsl(0, 100%, 40%);">-              if length >= EDGE_BURST_LEN:</span><br><span style="color: hsl(0, 100%, 40%);">-                 burst_usbits = list(burst[:EDGE_BURST_LEN])</span><br><span style="color: hsl(0, 100%, 40%);">-             else:</span><br><span style="color: hsl(0, 100%, 40%);">-                   burst_usbits = list(burst[:GSM_BURST_LEN])</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.ver == 0x00:</span><br><span style="color: hsl(120, 100%, 40%);">+                  burst = self._parse_burst_v0(burst)</span><br><span> </span><br><span>              # Convert unsigned soft-bits to soft-bits</span><br><span style="color: hsl(0, 100%, 40%);">-               burst_sbits = self.usbit2sbit(burst_usbits)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-             # Save</span><br><span style="color: hsl(0, 100%, 40%);">-          self.burst = burst_sbits</span><br><span style="color: hsl(120, 100%, 40%);">+              self.burst = self.usbit2sbit(burst)</span><br><span> </span><br><span>      # Generate a random message specific burst</span><br><span style="color: hsl(0, 100%, 40%);">-      def rand_burst(self, length = GSM_BURST_LEN):</span><br><span style="color: hsl(120, 100%, 40%);">+ def rand_burst(self, length = None):</span><br><span>                 self.burst = []</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+           if length is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                    length = self.mod_type.bl</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          for i in range(length):</span><br><span>                      sbit = random.randint(-127, 127)</span><br><span>                     self.burst.append(sbit)</span><br><span>@@ -697,12 +1004,20 @@</span><br><span>             assert(msg_l12trx_dec.tn == msg_l12trx.tn)</span><br><span>           assert(msg_trx2l1_dec.fn == msg_trx2l1.fn)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+                # Match version specific fields</span><br><span style="color: hsl(120, 100%, 40%);">+               if msg_trx2l1.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                 assert(msg_trx2l1_dec.nope_ind == msg_trx2l1.nope_ind)</span><br><span style="color: hsl(120, 100%, 40%);">+                        assert(msg_trx2l1_dec.mod_type == msg_trx2l1.mod_type)</span><br><span style="color: hsl(120, 100%, 40%);">+                        assert(msg_trx2l1_dec.tsc_set == msg_trx2l1.tsc_set)</span><br><span style="color: hsl(120, 100%, 40%);">+                  assert(msg_trx2l1_dec.tsc == msg_trx2l1.tsc)</span><br><span style="color: hsl(120, 100%, 40%);">+                  assert(msg_trx2l1_dec.ci == msg_trx2l1.ci)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          log.info("Check header version %u coding: OK" % ver)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>             # Compare bursts</span><br><span>             assert(msg_l12trx_dec.burst == msg_l12trx.burst)</span><br><span>             assert(msg_trx2l1_dec.burst == msg_trx2l1.burst)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-            log.info("Check header version %u coding: OK" % ver)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>               msg_trx2l1_gen = msg_l12trx.gen_trx2l1()</span><br><span>             msg_l12trx_gen = msg_trx2l1.gen_l12trx()</span><br><span> </span><br><span>@@ -717,4 +1032,18 @@</span><br><span>                 assert(msg_trx2l1_gen.tn == msg_l12trx.tn)</span><br><span>           assert(msg_l12trx_gen.fn == msg_trx2l1.fn)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-          log.info("Verify direct transformation: OK")</span><br><span style="color: hsl(120, 100%, 40%);">+                log.info("Verify version %u direct transformation: OK" % ver)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             # Verify NOPE indication coding</span><br><span style="color: hsl(120, 100%, 40%);">+               if msg_trx2l1.ver >= 0x01:</span><br><span style="color: hsl(120, 100%, 40%);">+                 msg_trx2l1 = DATAMSG_TRX2L1(ver = ver)</span><br><span style="color: hsl(120, 100%, 40%);">+                        msg_trx2l1.nope_ind = True</span><br><span style="color: hsl(120, 100%, 40%);">+                    msg_trx2l1.rand_hdr()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                       msg_trx2l1_dec = DATAMSG_TRX2L1()</span><br><span style="color: hsl(120, 100%, 40%);">+                     msg_trx2l1_dec.parse_msg(msg_trx2l1.gen_msg())</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                      assert(msg_trx2l1.nope_ind == msg_trx2l1_dec.nope_ind)</span><br><span style="color: hsl(120, 100%, 40%);">+                        assert(msg_trx2l1.burst == msg_trx2l1_dec.burst)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                    log.info("Verify version %u NOPE indication coding: OK" % ver)</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmocom-bb/+/14579">change 14579</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/+/14579"/><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: Ie810c5a482d1c908994e8cdd32a2ea641ae7cedd </div>
<div style="display:none"> Gerrit-Change-Number: 14579 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: fixeria <axilirator@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: Hoernchen <ewild@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: fixeria <axilirator@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@gnumonks.org> </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>