kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40075?usp=email )
Change subject: trx_toolkit/data_msg: Switch *Msg to cdef classes ......................................................................
trx_toolkit/data_msg: Switch *Msg to cdef classes
- Put fields into the object struct; fields are now accessed directly via that C-level struct instead of via __dict__ lookup - Type .burst explicitly: it is bytearray for TxMsg and array.array[int8_t] for RxMsg(*) - switch to invoke several internal *Msg functions via C-level where appropriate. - cimport instead of import *Msg at the users - type-annotate users appropriately - switch Modulation .bl and .coding to be cdef-only, as the only users of those fields are *Msg classes, and they now access Modulation via C.
(*) it was also considered to use either array.array[uint8_t] or uint8_t[::1] memoryview for tx burst. Unfortunately that is not good for performance as converting explicit types, e.g. bytearray, to memoryview on a call is relatively costly, and also because cython generates suboptimal code with lots of overhead when passing/returning arrays. This way we use concrete type bytearray for tx bursts which does not have those problems. For rx burst, we cannot use bytearray because existing semantic requires rx_burst[i] to return signed int8_t, which bytearray does not provide. So we use the least bad array.array[int8_t] for that.
Change-Id: Icb63ec0a0d899e352400915bb163a3e2f354c280 --- M src/target/trx_toolkit/_fake_trx.pyx M src/target/trx_toolkit/burst_fwd.pxd M src/target/trx_toolkit/burst_fwd.pyx M src/target/trx_toolkit/data_msg.pxd M src/target/trx_toolkit/data_msg.pyx M src/target/trx_toolkit/transceiver.pxd M src/target/trx_toolkit/transceiver.pyx 7 files changed, 142 insertions(+), 74 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/75/40075/1
diff --git a/src/target/trx_toolkit/_fake_trx.pyx b/src/target/trx_toolkit/_fake_trx.pyx index fd12863..5c55486 100644 --- a/src/target/trx_toolkit/_fake_trx.pyx +++ b/src/target/trx_toolkit/_fake_trx.pyx @@ -24,7 +24,7 @@ from burst_fwd cimport BurstForwarder from _clck_gen cimport CLCKGen from transceiver cimport Transceiver -from data_msg cimport Modulation, ModGMSK +from data_msg cimport Modulation, ModGMSK, TxMsg, RxMsg from udp_link cimport _raise_oserr import gsm_shared cdef object TrainingSeqGMSK = gsm_shared.TrainingSeqGMSK @@ -198,7 +198,7 @@
return False
- cdef _handle_data_msg_v1(self, src_msg, msg): + cdef _handle_data_msg_v1(self, TxMsg src_msg, RxMsg msg): # C/I (Carrier-to-Interference ratio) msg.ci = self.ci()
@@ -218,7 +218,7 @@ # Takes (partially initialized) TRXD Rx message, # simulates RF path parameters (such as RSSI), # and sends towards the L1 - cdef handle_data_msg(self, Transceiver src_trx, src_msg, msg): + cdef handle_data_msg(self, Transceiver src_trx, TxMsg src_msg, RxMsg msg): if self.rf_muted: msg.nope_ind = True elif not msg.nope_ind: diff --git a/src/target/trx_toolkit/burst_fwd.pxd b/src/target/trx_toolkit/burst_fwd.pxd index 43b788c..486faa1 100644 --- a/src/target/trx_toolkit/burst_fwd.pxd +++ b/src/target/trx_toolkit/burst_fwd.pxd @@ -1,8 +1,9 @@ # cython: language_level=3
+from data_msg cimport TxMsg from transceiver cimport Transceiver
cdef class BurstForwarder: cdef list[Transceiver] trx_list
- cdef forward_msg(self, Transceiver src_trx, rx_msg) + cdef forward_msg(self, Transceiver src_trx, TxMsg rx_msg) diff --git a/src/target/trx_toolkit/burst_fwd.pyx b/src/target/trx_toolkit/burst_fwd.pyx index 4c8a0ef..d13f784 100644 --- a/src/target/trx_toolkit/burst_fwd.pyx +++ b/src/target/trx_toolkit/burst_fwd.pyx @@ -43,7 +43,7 @@ def __cinit__(self, list trx_list): self.trx_list = trx_list
- cdef forward_msg(self, Transceiver src_trx, rx_msg): + cdef forward_msg(self, Transceiver src_trx, TxMsg rx_msg): # Originating Transceiver may use frequency hopping, # so let's precalculate its Tx frequency in advance tx_freq = src_trx.get_tx_freq(rx_msg.fn) diff --git a/src/target/trx_toolkit/data_msg.pxd b/src/target/trx_toolkit/data_msg.pxd index a1b2192..cbdca4b 100644 --- a/src/target/trx_toolkit/data_msg.pxd +++ b/src/target/trx_toolkit/data_msg.pxd @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- # cython: language_level=3
+from cython cimport final +from cpython cimport array +from libc.stdint cimport uint8_t + cdef class Modulation: - cdef readonly int coding # Coding in TRXD header - cdef readonly int bl # Burst length + cdef int coding # Coding in TRXD header + cdef int bl # Burst length
@staticmethod cdef Modulation pick(int coding) # -> Modulation | None @@ -13,3 +17,54 @@
cdef Modulation ModGMSK + + +cdef class Msg: + cdef readonly int ver + cdef readonly object fn # int | None + cdef readonly object tn # int | None + cdef public bint nope_ind + + + @staticmethod + cdef int CHDR_LEN() noexcept + cdef int HDR_LEN(self) except -1 + + cdef append_hdr_to(self, bytearray buf) + cdef append_burst_to(self, bytearray buf) + cdef parse_hdr(self, msg) + cdef parse_burst(self, burst) + cdef _validate(self) + + +@final +cdef class TxMsg(Msg): + cdef public bytearray burst # | None + cdef readonly object pwr # int | None + + cpdef validate(self) + + +@final +cdef class RxMsg(Msg): + cdef public array.array burst # array.array[b] | None + + cdef public object rssi # int | None + cdef public object toa256 # int | None + + # Version 0x01 specific + cdef public Modulation mod_type # Modulation | None + + cdef public object tsc_set # int | None + cdef public object tsc # int | None + cdef public object ci # int | None + + + cdef _validate_burst_v0(self) + cdef _validate_burst_v1(self) + cdef validate_burst(self) + cpdef validate(self) + cdef int gen_mts(self) except -1 + + cdef _parse_burst_v0(self, burst) + cdef int parse_mts(self, uint8_t mts) except -1 diff --git a/src/target/trx_toolkit/data_msg.pyx b/src/target/trx_toolkit/data_msg.pyx index 4b59fce..02194ba 100644 --- a/src/target/trx_toolkit/data_msg.pyx +++ b/src/target/trx_toolkit/data_msg.pyx @@ -18,6 +18,8 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details.
+from cython cimport final + import random import struct import abc @@ -70,39 +72,42 @@
cdef int KNOWN_VERSION_MAX = 1 # 0, 1
-class Msg(abc.ABC): +cdef class Msg: ''' TRXD (DATA) message coding API (common part). '''
# NOTE: up to 16 versions can be encoded CHDR_VERSION_MAX = 0b1111 KNOWN_VERSIONS = tuple(range(KNOWN_VERSION_MAX+1))
- def __init__(self, fn = None, tn = None, ver = 0): - self.burst = None # bytes|bytearray for ubit, array[b] for sbit, array[B] for usbit + def __cinit__(self, fn = -1, tn = -1, int ver = 0): self.ver = ver self.fn = fn self.tn = tn + self.nope_ind = False
- @property - def CHDR_LEN(self): + @staticmethod + cdef int CHDR_LEN() noexcept: ''' The common header length. ''' return 1 + 4 # (VER + TN) + FN
- @abc.abstractmethod - def append_hdr_to(self, buf): + cdef int HDR_LEN(self) except -1: + raise NotImplementedError + + cdef append_hdr_to(self, bytearray buf): ''' Generate message specific header by appending it to buf. ''' + raise NotImplementedError
- @abc.abstractmethod - def parse_hdr(self, hdr): + cdef parse_hdr(self, msg): ''' Parse message specific header. ''' + raise NotImplementedError
- @abc.abstractmethod - def append_burst_to(self, buf): + cdef append_burst_to(self, bytearray buf): ''' Generate message specific burst by appending it to buf. ''' + raise NotImplementedError
- @abc.abstractmethod - def parse_burst(self, burst): - ''' Parse message specific burst. ''' + cdef parse_burst(self, burst): + ''' Parse message specific burst to burst. ''' + raise NotImplementedError
@abc.abstractmethod def rand_burst(self): @@ -165,7 +170,7 @@ _tab_ubit2sbit = array('b', [-127 if b else 127 for b in range(0x100)])
- def validate(self): + cdef _validate(self): ''' Validate the message fields (throws ValueError). '''
if not (0 <= self.ver <= KNOWN_VERSION_MAX): @@ -187,7 +192,7 @@ ''' Generate a TRX DATA message. '''
# Validate all the fields - self.validate() + self._validate()
# Allocate an empty byte-array buf = bytearray() @@ -216,7 +221,7 @@ ''' Parse a TRX DATA message. '''
# Make sure we have at least common header - if len(msg) < self.CHDR_LEN: + if len(msg) < Msg.CHDR_LEN(): raise ValueError("Message is to short: missing common header")
# Parse the header version first @@ -230,17 +235,17 @@
# Make sure we have the whole header, # including the version specific fields - if len(msg) < self.HDR_LEN: + if len(msg) < self.HDR_LEN(): raise ValueError("Message is to short: missing version specific header")
# Specific message part self.parse_hdr(msg)
# Copy burst, skipping header - if len(msg) == self.HDR_LEN: + if len(msg) == self.HDR_LEN(): self.burst = None return - msg_burst = memoryview(msg)[self.HDR_LEN:] + msg_burst = memoryview(msg)[self.HDR_LEN():] self.parse_burst(msg_burst)
@@ -248,18 +253,19 @@ cdef int PWR_MIN = 0x00 cdef int PWR_MAX = 0xff
-class TxMsg(Msg): +@final +cdef class TxMsg(Msg): ''' Tx (L1 -> TRX) message coding API. '''
- # Specific message fields - pwr = None + def __cinit__(self): + self.burst = None + self.pwr = None
- @property - def HDR_LEN(self): + cdef int HDR_LEN(self) except -1: ''' Calculate header length depending on its version. '''
# Common header length - length = self.CHDR_LEN + length = Msg.CHDR_LEN()
# Message specific header length if self.ver in (0x00, 0x01): @@ -269,11 +275,11 @@
return length
- def validate(self): + cpdef validate(self): ''' Validate the message fields (throws ValueError). '''
# Validate common fields - Msg.validate(self) + Msg._validate(self)
if self.pwr is None: raise ValueError("Tx Attenuation level is not set") @@ -318,25 +324,25 @@ # Strip useless whitespace and return return result.strip()
- def append_hdr_to(self, buf): + cdef append_hdr_to(self, bytearray buf): ''' Generate message specific header by appending it to buf. '''
# Put power buf.append(self.pwr)
- def parse_hdr(self, hdr): + cdef parse_hdr(self, hdr): ''' Parse message specific header part. '''
# Parse power level self.pwr = hdr[5]
- def append_burst_to(self, buf): + cdef append_burst_to(self, bytearray buf): ''' Generate message specific burst by appending it to buf. '''
# Copy burst 'as is' return buf.extend(self.burst)
- def parse_burst(self, burst): + cdef parse_burst(self, burst): ''' Parse message specific burst. '''
length = len(burst) @@ -389,27 +395,28 @@ # IDLE frame / nope detection indicator cdef int NOPE_IND = (1 << 7)
-class RxMsg(Msg): + +@final +cdef class RxMsg(Msg): ''' Rx (TRX -> L1) message coding API. '''
- # Specific message fields - rssi = None - toa256 = None + def __cinit__(self): + self.rssi = None + self.toa256 = None
- # Version 0x01 specific (default values) - mod_type = ModGMSK - nope_ind = False + # Version 0x01 specific (default values) + self.mod_type = ModGMSK + self.nope_ind = False
- tsc_set = None - tsc = None - ci = None + self.tsc_set = None + self.tsc = None + self.ci = None
- @property - def HDR_LEN(self): + cdef int HDR_LEN(self) except -1: ''' Calculate header length depending on its version. '''
# Common header length - length = self.CHDR_LEN + length = Msg.CHDR_LEN()
# Message specific header length if self.ver == 0x00: @@ -423,7 +430,7 @@
return length
- def _validate_burst_v0(self): + cdef _validate_burst_v0(self): # Burst is mandatory if self.burst is None: raise ValueError("Rx burst bits are not set") @@ -432,7 +439,7 @@ if len(self.burst) not in (GMSK_BURST_LEN, EDGE_BURST_LEN): raise ValueError("Rx burst has odd length %u" % len(self.burst))
- def _validate_burst_v1(self): + cdef _validate_burst_v1(self): # Burst is omitted in case of an IDLE / NOPE indication if self.nope_ind and self.burst is None: return @@ -443,10 +450,11 @@ raise ValueError("Rx burst bits are not set")
# Burst length depends on modulation type + assert self.mod_type is not None if len(self.burst) != self.mod_type.bl: raise ValueError("Rx burst has odd length %u" % len(self.burst))
- def validate_burst(self): + cdef validate_burst(self): ''' Validate the burst (throws ValueError). '''
if self.ver == 0x00: @@ -454,11 +462,11 @@ elif self.ver >= 0x01: self._validate_burst_v1()
- def validate(self): + cpdef validate(self): ''' Validate the message header fields (throws ValueError). '''
# Validate common fields - Msg.validate(self) + Msg._validate(self)
if self.rssi is None: raise ValueError("RSSI is not set") @@ -481,10 +489,10 @@ raise ValueError("TSC set is not set")
if self.mod_type is ModGMSK: - if self.tsc_set not in range(0, 4): + if not (0 <= self.tsc_set < 4): raise ValueError("TSC set %d is out of range" % self.tsc_set) else: - if self.tsc_set not in range(0, 2): + if not (0 <= self.tsc_set < 2): raise ValueError("TSC set %d is out of range" % self.tsc_set)
if self.tsc is None: @@ -571,7 +579,7 @@ # Strip useless whitespace and return return result.strip()
- def gen_mts(self): + cdef int gen_mts(self) except -1: ''' Encode Modulation and Training Sequence info. '''
# IDLE / nope indication has no MTS info @@ -582,12 +590,13 @@ mts = self.tsc & 0b111
# MTS: . X X X X . . . + assert self.mod_type is not None mts |= self.mod_type.coding << 3 mts |= self.tsc_set << 3
return mts
- def parse_mts(self, mts): + cdef int parse_mts(self, uint8_t mts) except -1: ''' Parse Modulation and Training Sequence info. '''
# IDLE / nope indication has no MTS info @@ -596,7 +605,7 @@ self.mod_type = None self.tsc_set = None self.tsc = None - return + return 0
# TSC: . . . . . X X X self.tsc = mts & 0b111 @@ -612,7 +621,7 @@ self.mod_type = ModGMSK self.tsc_set = mts & 0b11
- def append_hdr_to(self, buf): + cdef append_hdr_to(self, bytearray buf): ''' Generate message specific header by appending it to buf. '''
# Put RSSI @@ -630,7 +639,7 @@ # C/I: Carrier-to-Interference ratio (in centiBels) buf += struct.pack(">h", self.ci)
- def parse_hdr(self, hdr): + cdef parse_hdr(self, hdr): ''' Parse message specific header part. '''
# Parse RSSI @@ -646,13 +655,13 @@ # C/I: Carrier-to-Interference ratio (in centiBels) self.ci = struct.unpack(">h", hdr[9:11])[0]
- def append_burst_to(self, buf): + cdef append_burst_to(self, bytearray buf): ''' Generate message specific burst appending it to buf. '''
# Convert soft-bits to unsigned soft-bits buf.extend( self.sbit2usbit(self.burst) ) # XXX copies; can probably remove them with numpy
- def _parse_burst_v0(self, burst): + cdef _parse_burst_v0(self, burst): ''' Parse message specific burst for header version 0. '''
bl = len(burst) @@ -668,7 +677,7 @@
return burst[:self.mod_type.bl]
- def parse_burst(self, burst): + cdef parse_burst(self, burst): ''' Parse message specific burst. '''
burst = array('B', burst) diff --git a/src/target/trx_toolkit/transceiver.pxd b/src/target/trx_toolkit/transceiver.pxd index 74ee982..2fb2fb7 100644 --- a/src/target/trx_toolkit/transceiver.pxd +++ b/src/target/trx_toolkit/transceiver.pxd @@ -1,6 +1,7 @@ # cython: language_level=3
from data_if cimport DATAInterface +from data_msg cimport TxMsg, RxMsg from burst_fwd cimport BurstForwarder from _clck_gen cimport CLCKGen
@@ -41,13 +42,13 @@ object ctrl_if # CTRLInterfaceTRX object clck_if # UDPLink
- list _tx_queue # [] of TxMsg + list[TxMsg] _tx_queue
cdef get_rx_freq(self, fn) cpdef get_tx_freq(self, fn)
- cdef recv_data_msg(self) - cdef send_data_msg(self, msg) - cdef handle_data_msg(self, Transceiver src_trx, src_msg, msg) + cdef TxMsg recv_data_msg(self) + cdef send_data_msg(self, RxMsg msg) + cdef handle_data_msg(self, Transceiver src_trx, TxMsg src_msg, RxMsg msg) cdef clck_tick(self, BurstForwarder fwd, fn) diff --git a/src/target/trx_toolkit/transceiver.pyx b/src/target/trx_toolkit/transceiver.pyx index 19bdd09..e852eda 100644 --- a/src/target/trx_toolkit/transceiver.pyx +++ b/src/target/trx_toolkit/transceiver.pyx @@ -274,7 +274,7 @@ log.info("Stopping clock generator") self.clck_gen.stop()
- cdef recv_data_msg(self): + cdef TxMsg recv_data_msg(self): # -> TxMsg | None # Read and parse data from socket msg = self.data_if.recv_tx_msg() if not msg: @@ -290,17 +290,19 @@ self._tx_queue.append(msg) return msg
- cdef send_data_msg(self, msg): + cdef send_data_msg(self, RxMsg msg): # TODO: make legacy mode configurable (via argv?) self.data_if.send_msg(msg, legacy = True)
- cdef handle_data_msg(self, Transceiver src_trx, src_msg, msg): + cdef handle_data_msg(self, Transceiver src_trx, TxMsg src_msg, RxMsg msg): self.send_data_msg(msg)
cdef clck_tick(self, BurstForwarder fwd, fn): if not self.running: return
+ cdef TxMsg msg + drop = [] emit = [] wait = []