v3.23.1: the PLI dictionary defaults and the PLI codec tag fixes
An unconfigured PLI dictionary answered result-ok with no data object,
which confuses applications that expect data. The dictionary now has a
base layer of sensible defaults for the qualifiers the pinned ETSI specs
define (TS 102 223 8.x, tags per TS 101 220 V18.3.0 Table 7.23):
- location as the test network 001/01 + LAC 0000 (8.19),
- the classic test IMEI 490154203237518 as F-padded BCD (8.20) and its
IMEISV (8.74),
- language 'en' (8.45), Timing Advance (2E/AE), E-UTRAN (8.61; UTRAN +
E-UTRAN for the multiple-technology qualifier 0E),
- automatic search mode (8.75), a full battery (8.76).
An override set through /api/pli-dict wins, and clearing a field restores
its default (the PWA's Save re-renders the effective values; the GET returns
the override or the default for the full qualifier set). The date/time
qualifier stays empty so the host clock keeps answering it; the
3GPP/3GPP2-specific qualifiers (NMR, ESN, MEID, CSG/HNB, WLAN, slices,
CAG, ...) stay empty - the ETSI specs define no contents for them, and a
fabricated object would mislead the application more than no answer.
Verifying the values against the pinned tag table exposed pre-existing bugs
in the PWA's PLI inline codecs, fixed here:
- Timing Advance used C6 (a 3GPP2 tag) -> 2E/AE;
- IMEISV used 94 (the IMEI tag) -> 62/E2;
- Network Search Mode used AD (the Language tag) -> 65/E5, and its labels
were swapped ('00' Manual / '01' Automatic per 8.75);
- the battery field was a percentage where 8.76 is an enum ('00' very low
... '04' full, 'FE'/'FF');
- encImeiPli padded the odd 15th digit with 0 instead of F (TS 124 008).
Docs: help EN/RU, api.md, the guide. Tests: the defaults + the resolution
order (Python), the HTTP endpoint's defaults/override/clearing, the codec
tags/labels and a server-defaults drift guard (Node). 846 frontend +
829 Python; sw cache simple-v414.
This commit is contained in:
@@ -632,9 +632,17 @@ class TestProactiveDecode(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
import pysim_simple_server.server as srv
|
||||
# the class-wide location override must not leak into other tests
|
||||
# (the PLI dictionary is module state; v3.23.1)
|
||||
self._pli_saved = dict(srv._PLI_DATA)
|
||||
srv._PROACTIVE_SESSION_START = 1234.0
|
||||
srv._PLI_DATA[0x00] = '93055210011000'
|
||||
|
||||
def tearDown(self):
|
||||
import pysim_simple_server.server as srv
|
||||
srv._PLI_DATA.clear()
|
||||
srv._PLI_DATA.update(self._pli_saved)
|
||||
|
||||
@staticmethod
|
||||
def _cmd_raw(cmd_type, qualifier, extras=b''):
|
||||
"""A D0-wrapped proactive command (header TLVs + extras)."""
|
||||
@@ -895,17 +903,56 @@ class TestProactiveDecode(unittest.TestCase):
|
||||
self.assertEqual(tr.hex(), '810301260082028281030100130752f01000ff0001')
|
||||
|
||||
def test_build_tr_pli_empty_other_qualifier_sends_no_data(self):
|
||||
# a qualifier with neither an override nor a built-in default (02
|
||||
# NMR) sends no data object (v3.23.1: the common ones have defaults)
|
||||
import pysim_simple_server.server as srv
|
||||
saved = srv._PLI_DATA.get(0x0a)
|
||||
srv._PLI_DATA[0x0a] = ' '
|
||||
saved = srv._PLI_DATA.get(0x02)
|
||||
srv._PLI_DATA[0x02] = ' '
|
||||
try:
|
||||
tr = _build_tr(None, 1, 0x26, 0x81, 0x82, 0x0a)
|
||||
data = srv._pli_data_hex(0x0a)
|
||||
tr = _build_tr(None, 1, 0x26, 0x81, 0x82, 0x02)
|
||||
data = srv._pli_data_hex(0x02)
|
||||
finally:
|
||||
srv._PLI_DATA[0x0a] = saved
|
||||
srv._PLI_DATA[0x02] = saved
|
||||
self.assertEqual(tr.hex(), '810301260082028281030100')
|
||||
self.assertEqual(data, '')
|
||||
|
||||
def test_pli_defaults_cover_the_common_qualifiers(self):
|
||||
# v3.23.1: the dictionary's base layer - the objects the pinned specs
|
||||
# define (TS 102 223 8.x, tags per TS 101 220 V18.3.0 Table 7.23)
|
||||
import pysim_simple_server.server as srv
|
||||
d = srv.PLI_DEFAULTS
|
||||
self.assertEqual(d[0x00], '930500F1100000') # 001/01 + LAC 0000
|
||||
self.assertEqual(d[0x01], '940894104502237315F8') # IMEI, F pad
|
||||
self.assertEqual(d[0x04], 'AD02656E') # 'en'
|
||||
self.assertEqual(d[0x05], 'AE020000') # Timing Advance
|
||||
self.assertEqual(d[0x06], 'BF0108') # E-UTRAN
|
||||
self.assertEqual(d[0x08], 'E2089410450223731580') # IMEISV
|
||||
self.assertEqual(d[0x09], 'E50101') # automatic
|
||||
self.assertEqual(d[0x0A], '630104') # battery full
|
||||
self.assertEqual(d[0x0E], 'BF020308') # UTRAN + E-UTRAN
|
||||
# the date/time stays dynamic; the 3GPP/3GPP2 qualifiers stay empty
|
||||
self.assertNotIn(0x03, d)
|
||||
for q in (0x02, 0x07, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16, 0x17, 0x1A):
|
||||
self.assertNotIn(q, d)
|
||||
# every default is a well-formed TLV (a short-form length that
|
||||
# matches the value)
|
||||
for q, v in d.items():
|
||||
self.assertEqual(len(v) % 2, 0, q)
|
||||
self.assertEqual(int(v[2:4], 16) * 2, len(v) - 4, q)
|
||||
# the resolution order: override wins, clearing restores the default,
|
||||
# the clock answers 03 while empty
|
||||
saved = dict(srv._PLI_DATA)
|
||||
try:
|
||||
srv._PLI_DATA[0x01] = '94080000000000000000'
|
||||
self.assertEqual(srv._pli_data_hex(0x01), '94080000000000000000')
|
||||
srv._PLI_DATA.pop(0x01, None)
|
||||
self.assertEqual(srv._pli_data_hex(0x01), d[0x01])
|
||||
self.assertRegex(srv._pli_data_hex(0x03), r'^2607[0-9A-F]{14}$')
|
||||
finally:
|
||||
srv._PLI_DATA.clear()
|
||||
srv._PLI_DATA.update(saved)
|
||||
|
||||
def test_decode_cmd_empty_raw(self):
|
||||
self.assertEqual(_decode_cmd(0x26, b'', None), [])
|
||||
self.assertEqual(_decode_cmd(0x03, None, None), [])
|
||||
|
||||
Reference in New Issue
Block a user