Add PLI qualifier description to PROVIDE LOCAL INFORMATION decode

- 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)
This commit is contained in:
catarrh committed 2026-08-12 20:58:15 +03:00
1 parent f30fe41d75
commit 31011301ea
2 files changed
+75 -5

No files matched your search

+49 -4
View File
@@ -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
+26 -1
View File
@@ -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')