From 31011301ea0239b35a9b8efcaf30f9e3433aaa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Wed, 12 Aug 2026 20:58:15 +0300 Subject: [PATCH] Add PLI qualifier description to PROVIDE LOCAL INFORMATION decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PLI_QUALIFIERS: TS 102 223 qualifier values (Location Info, IMEI, NMR, Date/time, Timing Advance, Access Technology, etc.) - _command_details_name(): appends the qualifier description to PROVIDE LOCAL INFORMATION (type 0x26) in Command Details - decode_cat (FETCH) and decode_tr_command (TR) now use it, so both 'cat_command' and 'response_to' carry the qualifier (e.g. 'PROVIDE LOCAL INFORMATION — IMEI') tests: PLI qualifier cases (TR/FETCH, IMEI/location/date, non-PLI) --- simtrace2_pysniff/server/decode.py | 53 +++++++++++++++++++++++++++--- tests/test_decode.py | 27 ++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/simtrace2_pysniff/server/decode.py b/simtrace2_pysniff/server/decode.py index e3df5a9..1084e50 100644 --- a/simtrace2_pysniff/server/decode.py +++ b/simtrace2_pysniff/server/decode.py @@ -478,6 +478,51 @@ def parse_tlv(data): return tlvs +# TS 102 223 — PROVIDE LOCAL INFORMATION command qualifier values +PLI_QUALIFIERS = { + 0x00: 'Location Info (MCC, MNC, LAC/TAC, Cell ID)', + 0x01: 'IMEI', + 0x02: 'Network Measurement results', + 0x03: 'Date, time and time zone', + 0x04: 'Language setting', + 0x05: 'Timing Advance', + 0x06: 'Access Technology (single)', + 0x08: 'IMEISV', + 0x09: 'Search Mode', + 0x0A: 'Battery charge state', + 0x0C: 'Current WSID', + 0x0D: 'Broadcast Network info', + 0x0E: 'Multiple Access Technologies', + 0x0F: 'Location Info (multi-RAT)', + 0x10: 'NMR (multi-RAT)', + 0x11: 'CSG ID list + HNB name', + 0x12: 'H(e)NB IP address', + 0x13: 'H(e)NB surrounding macrocells', + 0x14: 'Current WLAN identifier', + 0x15: 'Slices information', + 0x16: 'CAG information list', + 0x17: 'Rejected slices information', +} + + +def _command_details_name(value): + """Decode a Command Details TLV value (number, type, qualifier). + + Returns the command type name, with the PLI qualifier description + appended for PROVIDE LOCAL INFORMATION (type 0x26). + """ + if len(value) < 2: + return None + name = CAT_COMMAND_TYPES.get(value[1]) + if not name: + return None + if value[1] == 0x26 and len(value) >= 3: # PROVIDE LOCAL INFORMATION + q = PLI_QUALIFIERS.get(value[2]) + if q: + name = f'{name} — {q}' + return name + + def decode_cat(ins, body): """Decode a CAT (TS 102 223) payload, returning the command/event name. @@ -493,8 +538,8 @@ def decode_cat(ins, body): if tag != 0xD0: continue for t2, _l2, v2 in parse_tlv(value): - if t2 == 0x81 and len(v2) >= 2: - return CAT_COMMAND_TYPES.get(v2[1]) + if t2 == 0x81: + return _command_details_name(v2) elif ins == 0xC2: # ENVELOPE → envelope type return ENVELOPE_TYPES.get(body[0]) return None @@ -508,8 +553,8 @@ def decode_tr_command(body): if not body: return None for tag, _length, value in parse_tlv(body): - if tag == 0x81 and len(value) >= 2: - return CAT_COMMAND_TYPES.get(value[1]) + if tag == 0x81: + return _command_details_name(value) return None diff --git a/tests/test_decode.py b/tests/test_decode.py index 188f47c..e9ac38b 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -58,7 +58,8 @@ class TestCatDecoding(unittest.TestCase): def test_fetch_provide_local_info(self): r = decode_message(bytes.fromhex( '801200000BD0098103012600820281829000')) - self.assertEqual(r['cat_command'], 'PROVIDE LOCAL INFORMATION') + self.assertEqual(r['cat_command'], + 'PROVIDE LOCAL INFORMATION — Location Info (MCC, MNC, LAC/TAC, Cell ID)') def test_envelope_event_download(self): r = decode_message(bytes.fromhex( @@ -138,6 +139,30 @@ class TestTerminalResponse(unittest.TestCase): self.assertEqual(r['response_to'], 'POLL INTERVAL') +class TestPliQualifier(unittest.TestCase): + def test_tr_pli_imei(self): + r = decode_message(bytes.fromhex( + '8014000016810301260102028281030106130952F0991EC57A68009F')) + self.assertEqual(r['response_to'], 'PROVIDE LOCAL INFORMATION — IMEI') + + def test_fetch_pli_location(self): + r = decode_message(bytes.fromhex( + '801200000BD0098103012600820281829000')) + self.assertEqual(r['cat_command'], + 'PROVIDE LOCAL INFORMATION — Location Info (MCC, MNC, LAC/TAC, Cell ID)') + + def test_fetch_pli_date_time(self): + r = decode_message(bytes.fromhex( + '801200000BD0098103012603820281829000')) + self.assertEqual(r['cat_command'], + 'PROVIDE LOCAL INFORMATION — Date, time and time zone') + + def test_non_pli_no_qualifier(self): + r = decode_message(bytes.fromhex( + '801400000C810301250002028281830100910F')) + self.assertEqual(r['response_to'], 'SET UP MENU') + + class TestGetResponseContext(unittest.TestCase): GET_RESPONSE = bytes.fromhex('00C000002962278202782183023F00A50A800171')