PLI: return the 7-byte location object, bound every proactive chain
release / release (push) Waiting to run

The PLI answer used the 5-byte form 13 05 <MCC/MNC> <LAC> with LAC 0000
and no Cell ID.  That is the *Network Rejection event download* form
(TS 31.111 8.19.1); a PLI location for GERAN is 7 bytes - MCC/MNC, LAC
and Cell ID.  A card applet that re-reads its location (the Alfa
MultiIMSI applet, after a *LOCI status change) treated it as empty and
re-requested PLI on every STATUS tick.

The default is now 13 07 00F110 0001 0001: PLMN 001/01 (the test
network), LAC 0001, Cell ID 0001.  _build_tr injects it after the
Result; the TR data is pinned by a test.

While verifying a live PLI, the manual /api/status-poll chain turned
out to be unbounded: it looped on the PLI storm while holding
_CARD_LOCK, so the request never returned and every card operation
waited.  _handle_proactive_chain's max_rounds now defaults to
_POLL_CHAIN_MAX (the background poll always passed it; the manual poll,
ENVELOPE, menu and TP chains did not), and the manual poll passes it
explicitly.  A card that re-queues a command can no longer wedge the
card lock from any call site.

Tests: the PLI default + the built TR, the default chain bound and the
manual-poll call pin.  1061 Python + 1069 frontend; sw cache simple-v477.
This commit is contained in:
2026-10-24 01:51:35 +03:00
parent 643c6609f3
commit 513f16d9d2
9 changed files with 69 additions and 17 deletions
+17 -1
View File
@@ -1041,7 +1041,10 @@ class TestProactiveDecode(unittest.TestCase):
# 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], '130500F1100000') # 001/01 + LAC 0000
# the 7-byte PLI form: 001/01 + LAC 0001 + Cell ID 0001 (length 05
# is the Network Rejection event form, TS 31.111 8.19.1)
self.assertEqual(d[0x00], '130700F11000010001')
self.assertEqual(len(bytes.fromhex(d[0x00])), 9)
self.assertEqual(d[0x01], '140894104502237315F8') # IMEI, F pad
self.assertEqual(d[0x04], '2D02656E') # 'en'
self.assertEqual(d[0x05], '2E020000') # Timing Advance
@@ -1073,6 +1076,19 @@ class TestProactiveDecode(unittest.TestCase):
srv._PLI_DATA.clear()
srv._PLI_DATA.update(saved)
def test_the_pli_tr_carries_the_7_byte_location(self):
# the TR builder injects the location object after the Result; a
# 5-byte object (no Cell ID) is the Network Rejection form, not a PLI
import pysim_simple_server.server as srv
saved = srv._PLI_DATA.pop(0x00, None)
try:
tr = srv._build_tr(object(), 1, 0x26, 0x81, 0x82, 0x00)
finally:
if saved is not None:
srv._PLI_DATA[0x00] = saved
self.assertTrue(tr.startswith(bytes.fromhex('810301260002028281030100')))
self.assertTrue(tr.endswith(bytes.fromhex('130700F11000010001')))
def test_decode_cmd_empty_raw(self):
self.assertEqual(_decode_cmd(0x26, b'', None), [])
self.assertEqual(_decode_cmd(0x03, None, None), [])