kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40078?usp=email )
Change subject: trx_toolkit/data_msg: Optimize RxMsg/TxMsg parsing ......................................................................
trx_toolkit/data_msg: Optimize RxMsg/TxMsg parsing
Thread all calls related in parsing to be done via C-level and with explicit types. Leverage bytearray ability to cut prefix and suffix efficiently. Replace struct.unpack with custom C-level functions to deserialize big-endian integers. Use raw C pointers to access bytearray data to avoid related py-index overhead.
Change-Id: I661a414bf5091fa6b872831ba911465a8d073397 --- M src/target/trx_toolkit/data_if.pyx M src/target/trx_toolkit/data_msg.pxd M src/target/trx_toolkit/data_msg.pyx 3 files changed, 69 insertions(+), 32 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/78/40078/1
diff --git a/src/target/trx_toolkit/data_if.pyx b/src/target/trx_toolkit/data_if.pyx index c355666..71a1878 100644 --- a/src/target/trx_toolkit/data_if.pyx +++ b/src/target/trx_toolkit/data_if.pyx @@ -65,7 +65,7 @@ # Attempt to parse a TRXD Tx message try: msg = TxMsg() - msg.parse_msg(data) + msg._parse_msg(data) except: log.error("Failed to parse a TRXD Tx message " "from R:%s" % self.desc_remote()) @@ -85,7 +85,7 @@ # Attempt to parse a TRXD Rx message try: msg = RxMsg() - msg.parse_msg(data) + msg._parse_msg(data) except: log.error("Failed to parse a TRXD Rx message " "from R:%s" % self.desc_remote()) diff --git a/src/target/trx_toolkit/data_msg.pxd b/src/target/trx_toolkit/data_msg.pxd index 2823b82..6823397 100644 --- a/src/target/trx_toolkit/data_msg.pxd +++ b/src/target/trx_toolkit/data_msg.pxd @@ -32,8 +32,9 @@
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 parse_hdr(self, bytearray msg) + cdef parse_burst(self, bytearray burst) + cdef int _parse_msg(self, bytearray msg) except -1 cdef _validate(self)
@staticmethod @@ -84,5 +85,5 @@ cpdef validate(self) cdef int gen_mts(self) except -1
- cdef _parse_burst_v0(self, burst) + cdef _parse_burst_v0(self, bytearray 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 5929b47..ac3dc49 100644 --- a/src/target/trx_toolkit/data_msg.pyx +++ b/src/target/trx_toolkit/data_msg.pyx @@ -20,6 +20,7 @@ # GNU General Public License for more details.
from cython cimport final +from libc.stdint cimport int16_t, uint16_t, uint32_t
from cpython cimport array cdef array.array _Bempty = array.array('B', []) @@ -101,7 +102,7 @@ ''' Generate message specific header by appending it to buf. ''' raise NotImplementedError
- cdef parse_hdr(self, msg): + cdef parse_hdr(self, bytearray msg): ''' Parse message specific header. ''' raise NotImplementedError
@@ -109,8 +110,11 @@ ''' Generate message specific burst by appending it to buf. ''' raise NotImplementedError
- cdef parse_burst(self, burst): - ''' Parse message specific burst to burst. ''' + cdef parse_burst(self, bytearray burst): + ''' Parse message specific burst to burst. + + burst bytearray can be trimmed in-place to leave only burst data. + ''' raise NotImplementedError
@abc.abstractmethod @@ -267,35 +271,43 @@ return buf
def parse_msg(self, msg): + if type(msg) is not bytearray: + msg = bytearray(msg) + self._parse_msg(msg) + cdef int _parse_msg(self, bytearray msg) except -1: ''' Parse a TRX DATA message. ''' + cdef uint8_t *msgb = <uint8_t*>PyByteArray_AS_STRING(msg) + cdef Py_ssize_t msgl = len(msg)
# Make sure we have at least common header - if len(msg) < Msg.CHDR_LEN(): + if msgl < Msg.CHDR_LEN(): raise ValueError("Message is to short: missing common header")
# Parse the header version first - self.ver = (msg[0] >> 4) + self.ver = (msgb[0] >> 4) if not (0 <= self.ver <= KNOWN_VERSION_MAX): raise ValueError("Unknown TRXD header version %d" % self.ver)
# Parse TDMA TN and FN - self.tn = (msg[0] & 0x07) - self.fn = struct.unpack(">L", msg[1:5])[0] + self.tn = (msgb[0] & 0x07) + self.fn = _unpack_u32(&msgb[1])
# Make sure we have the whole header, # including the version specific fields - if len(msg) < self.HDR_LEN(): + hlen = self.HDR_LEN() + if msgl < hlen: 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(): + # Extract burst, skipping header + # reuse original msg bytearray for burst data + if msgl == hlen: self.burst = None - return - msg_burst = memoryview(msg)[self.HDR_LEN():] - self.parse_burst(msg_burst) + return 0 + del msg[:hlen] # NOTE bytearray handles prefix delete efficiently + self.parse_burst(msg)
# Constants @@ -379,8 +391,10 @@ # Put power buf.append(self.pwr)
- cdef parse_hdr(self, hdr): + cdef parse_hdr(self, bytearray msg): ''' Parse message specific header part. ''' + cdef uint8_t *hdr = <uint8_t*>PyByteArray_AS_STRING(msg) + # NOTE header lenght is already pre-checked by _parse_msg
# Parse power level self.pwr = hdr[5] @@ -391,20 +405,22 @@ # Copy burst 'as is' return buf.extend(self.burst)
- cdef parse_burst(self, burst): + cdef parse_burst(self, bytearray burst): ''' Parse message specific burst. '''
+ # trim msg down with removing everything besides burst data + # bytearray handles prefix and suffix removal efficiently length = len(burst)
# Distinguish between GSM and EDGE if length >= EDGE_BURST_LEN: if length > EDGE_BURST_LEN: - burst = memoryview(burst)[:EDGE_BURST_LEN] + del burst[EDGE_BURST_LEN:] else: if length > GMSK_BURST_LEN: - burst = memoryview(burst)[:GMSK_BURST_LEN] + del burst[GMSK_BURST_LEN:]
- self.burst = bytearray(burst) + self.burst = burst
def rand_burst(self, length = GMSK_BURST_LEN): ''' Generate a random message specific burst. ''' @@ -688,21 +704,23 @@ # C/I: Carrier-to-Interference ratio (in centiBels) buf += struct.pack(">h", self.ci)
- cdef parse_hdr(self, hdr): + cdef parse_hdr(self, bytearray msg): ''' Parse message specific header part. ''' + cdef uint8_t *hdr = <uint8_t*>PyByteArray_AS_STRING(msg) + # NOTE header lenght is already pre-checked by _parse_msg
# Parse RSSI self.rssi = -(hdr[5])
# Parse ToA (Time of Arrival) - self.toa256 = struct.unpack(">h", hdr[6:8])[0] + self.toa256 = _unpack_s16(&hdr[6])
if self.ver >= 0x01: # Modulation and Training Sequence info self.parse_mts(hdr[8])
# C/I: Carrier-to-Interference ratio (in centiBels) - self.ci = struct.unpack(">h", hdr[9:11])[0] + self.ci = _unpack_s16(&hdr[9])
cdef append_burst_to(self, bytearray buf): ''' Generate message specific burst appending it to buf. ''' @@ -713,7 +731,8 @@ Msg.__sbit2usbit(<int8_t*>self.burst.data.as_chars, len(self.burst), <uint8_t*>&PyByteArray_AS_STRING(buf)[l])
- cdef _parse_burst_v0(self, burst): + + cdef _parse_burst_v0(self, bytearray burst): ''' Parse message specific burst for header version 0. '''
bl = len(burst) @@ -727,15 +746,13 @@ if self.mod_type is None: raise ValueError("Odd burst length %u" % bl)
- return burst[:self.mod_type.bl] + del burst[self.mod_type.bl:]
- cdef parse_burst(self, burst): + cdef parse_burst(self, bytearray burst): ''' Parse message specific burst. '''
- burst = array.array('B', burst) - if self.ver == 0x00: - burst = self._parse_burst_v0(burst) + self._parse_burst_v0(burst)
# Convert unsigned soft-bits to soft-bits self.burst = Msg._usbit2sbit(burst) @@ -762,3 +779,22 @@ <uint8_t*>PyByteArray_AS_STRING(msg.burst))
return msg + + +# ---- misc ---- + +# _unpack_(u|s)(16|32) deserialize big-endian representation of corresponding integer type from memory pointed by b. + +cdef int16_t _unpack_s16(const uint8_t *b): + cdef uint16_t r = 0 + r |= (<uint16_t>b[0]) << 8 + r |= (<uint16_t>b[1]) << 0 + return <int16_t>r + +cdef uint32_t _unpack_u32(const uint8_t *b): + cdef uint32_t r = 0 + r |= (<uint32_t>b[0]) << 24 + r |= (<uint32_t>b[1]) << 16 + r |= (<uint32_t>b[2]) << 8 + r |= (<uint32_t>b[3]) << 0 + return r