PLI: return the 7-byte location object, bound every proactive chain
release / release (push) Waiting to run
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:
@@ -146,7 +146,7 @@ class PliDictHttpTests(unittest.TestCase):
|
||||
try:
|
||||
status, resp = self._req('GET', '/api/pli-dict')
|
||||
self.assertEqual(status, 200, resp)
|
||||
self.assertEqual(resp['00'], '130500F1100000')
|
||||
self.assertEqual(resp['00'], '130700F11000010001')
|
||||
self.assertEqual(resp['0A'], '630104')
|
||||
self.assertEqual(resp['03'], '') # the host clock answers it
|
||||
self.assertEqual(resp['02'], '') # no default
|
||||
|
||||
@@ -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), [])
|
||||
|
||||
@@ -201,6 +201,20 @@ class TestProactiveChainOwnership(unittest.TestCase):
|
||||
S._handle_proactive_chain(scc, '910b', max_rounds=3)
|
||||
self.assertEqual(len(self._fetches(scc)), 3)
|
||||
|
||||
def test_every_chain_call_is_bounded_by_default(self):
|
||||
# v3.37.0: the bound is the default, not only the background poll's
|
||||
# choice - a chatty card must not wedge any call site (the manual
|
||||
# STATUS poll and the ENVELOPE chain used to loop forever)
|
||||
scc = FakeChattyScc()
|
||||
S._handle_proactive_chain(scc, '910b')
|
||||
self.assertEqual(len(self._fetches(scc)), S._POLL_CHAIN_MAX)
|
||||
|
||||
def test_the_manual_status_poll_passes_the_bound(self):
|
||||
import inspect
|
||||
src = inspect.getsource(S.PysimHandler._do_POST)
|
||||
branch = src.split("'/api/status-poll'", 1)[1].split('elif self.path', 1)[0]
|
||||
self.assertIn('max_rounds=_POLL_CHAIN_MAX', branch)
|
||||
|
||||
def test_the_chain_stops_when_a_run_owns_the_card(self):
|
||||
scc = FakeChattyScc()
|
||||
S._TEST_RUNNING = True
|
||||
|
||||
Reference in New Issue
Block a user