v3.21.0: the test-runner tolerance batch + the proactive cleanup action
Five reported issues, plus the action that makes an explicit drain possible: - on_fail is honoured by every step failure: an expectation with no pending command (or a FETCH that returns nothing) fails as a Command/Fetch check at the step's level instead of raising past it; the default still stops the run, on_fail "warning" continues (the tolerant consume-if-pending form). - the run cleanup answers ok (0x00) and keeps draining while the TR answers 91XX (bounded); a cancel is only sent when a script asks for it - it tells the card the user aborted the session and pushes assertive applets onto an error path. - new `proactive` action: STATUS rounds consume every announced command (respond/first TERMINAL RESPONSE results, attempts/interval_ms bound) until the card answers 9000; an optional require asserts at least one drained command (an empty drain fails it), the final SW is the step's SW check and a still-pending command is handed to the next step. PWA editor + summary + validation, EN/RU help, docs, action-kind drift test. - the PoR response form follows the command's TAR: _tar_is_rm (preset role entries + the TS 101 220 Annex D ISD/RFM allocations) decides; a non-RM TAR's reply is always raw (TS 102 226 4) - no fabricated status word even when the bytes happen to parse (01 00 0B 91 ... used to read as SW 000B). - a secured data too short for the compact structure (a 2-byte applet reply) is recovered by _por_header_parse (the decode minus the compact parse; a malformed packet or a failed RC/CC/DS check still yields no por) and reported raw, so short applet replies are assertable via por.data. Docs: test-scripts.md, api.md, help EN/RU, repo AGENTS. sw cache simple-v396. Tests: 825 frontend + 755 Python green.
This commit is contained in:
@@ -25,6 +25,7 @@ from pysim_simple_server.server import (
|
||||
_build_tr,
|
||||
_decode_cmd,
|
||||
_decode_por,
|
||||
_tar_is_rm,
|
||||
_decode_tr,
|
||||
_log_proactive,
|
||||
_ota_reference,
|
||||
@@ -535,6 +536,59 @@ class TestDecodePor(unittest.TestCase):
|
||||
self.assertEqual(r['decoded']['last_status_word'], '6a86')
|
||||
self.assertEqual(r['secured_data'], '016A86')
|
||||
|
||||
def test_non_rm_tar_is_application_data(self):
|
||||
# v3.21.0: the response form follows the command's TAR. A non-RM TAR
|
||||
# answers with application data (TS 102 226 4); `01 00 0B 91 ...`
|
||||
# happens to parse as a compact response (count 1, SW 000B) and must
|
||||
# not be split into a fabricated status word (live 2026-10-05).
|
||||
raw = '027100000e0aaf4d010000000001000001000B919733525088F4'
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw, rm=False)
|
||||
self.assertEqual(r['response_type'], 'raw')
|
||||
self.assertEqual(r['response_status'], 'por_ok')
|
||||
self.assertEqual(r['secured_data'], '01000B919733525088F4')
|
||||
self.assertEqual(r['decoded']['last_response_data'], '01000B919733525088F4')
|
||||
self.assertEqual(r['decoded']['last_status_word'], '')
|
||||
self.assertIsNone(r['decoded']['number_of_commands'])
|
||||
# without the TAR context the same bytes still parse as compact
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw)
|
||||
self.assertEqual(r['response_type'], 'compact')
|
||||
self.assertEqual(r['decoded']['last_status_word'], '000b')
|
||||
|
||||
def test_short_applet_reply_falls_back_to_raw(self):
|
||||
# a 2-byte secured data cannot be a compact response (TS 102 226 5.1.2
|
||||
# Table 5.1 needs a count plus two status bytes): pySim's decode raises
|
||||
# and the packet is reported raw instead of "undecodable"
|
||||
raw = '027100000e0aaf4d01000000000100000400'
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw)
|
||||
self.assertEqual(r['response_type'], 'raw')
|
||||
self.assertEqual(r['response_status'], 'por_ok')
|
||||
self.assertEqual(r['secured_data'], '0400')
|
||||
self.assertEqual(r['decoded']['last_response_data'], '0400')
|
||||
|
||||
|
||||
class TestTarIsRm(unittest.TestCase):
|
||||
"""v3.21.0: the response form follows the command's TAR - the preset's
|
||||
role entries plus the standard ISD/RFM allocations of TS 101 220 Annex D."""
|
||||
|
||||
def test_standard_allocations(self):
|
||||
for tar in ('000000', 'B00000', 'B00001', 'B00010', 'B20100',
|
||||
'B00120', 'B00130', 'B00140'):
|
||||
self.assertTrue(_tar_is_rm(None, tar), tar)
|
||||
|
||||
def test_application_allocations_are_not_rm(self):
|
||||
# the Annex D application TARs (USAT interpreter, multiplexing, CASD,
|
||||
# OMA) and any custom TAR: application-specific data (TS 102 226 4)
|
||||
for tar in ('AF4D01', 'B20000', 'B20200', 'B20201', 'B20202',
|
||||
'B20203', 'B00200'):
|
||||
self.assertFalse(_tar_is_rm(None, tar), tar)
|
||||
|
||||
def test_preset_roles_win(self):
|
||||
preset = {'tars': [{'role': 'isd', 'tar': 'A11300', 'msl': '16'},
|
||||
{'role': '', 'tar': 'AF4D01', 'msl': '16'}]}
|
||||
self.assertTrue(_tar_is_rm(preset, 'A11300'))
|
||||
self.assertFalse(_tar_is_rm(preset, 'AF4D01'))
|
||||
self.assertIsNone(_tar_is_rm(preset, ''))
|
||||
|
||||
|
||||
class TestPresetCounterSeed(unittest.TestCase):
|
||||
"""v3.20.0: the effective counter of an operation - explicit `cntr` wins,
|
||||
|
||||
Reference in New Issue
Block a user