<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>