fix: accept comprehension-required tag variants everywhere (v3.6.40)

The TERMINAL PROFILE chain parsed the SET UP MENU title with 'tag == 0x05'
only, so the live Alfa card's title (CR-set 0x85, 'Alfa Mobile') was
dropped: /api/menu returned items without a title and the PWA showed
'No menu set by the card' - card-specific only because cards pick the tag
variant (this one sends plain 0x8F items and a CR-set title in the same
command).

- _parse_setup_menu_command (pure, tested with the live FETCH bytes)
  replaces the inline parse; the PWA shows a title-less menu as 'STK: Menu'
  instead of 'No menu set by the card' (stkMenuLabel, pure).
- _cmd_tlv is now the single lookup helper: exact tag, then tag ^ 0x80
  (bit 8 is the CR flag, TS 101 220 7.1.1); the old '| 0x80'-only helper and
  the duplicate _cmd_tlv_any are gone.
- Remaining single-style checks fixed: _parse_setup_menu_items (0x8F/0x0F),
  _parse_select_item title (0x85/0x05), _find_sms_tpdu (0x8B/0x0B - the
  plain variant lost the SEND SHORT MESSAGE PoR TPDU), the response
  scripting parsers (R-APDU 0x23/0xA3, executed count 0x80/0x00) and the
  FF21 resource tags (0x81-0x83/0x01-0x03).
- tests/test_tag_variants.py flips the CR flag on every top-level tag of
  real command fixtures and asserts identical parse results for all
  parsers (verified to fail when a single-style lookup is reintroduced).

sw cache simple-v313; tests: 524 Python / 675 Node.
This commit is contained in:
2026-09-29 01:54:39 +03:00
parent 61a52a3f05
commit 904eca1758
9 changed files with 250 additions and 54 deletions
+30
View File
@@ -49,6 +49,7 @@ from pysim_simple_server.server import (
_menu_send_response,
_parse_get_input,
_parse_get_inkey,
_parse_setup_menu_command,
_input_text_tlv,
_validate_input_response,
_pack_gsm7,
@@ -1805,6 +1806,35 @@ class StkInputTests(unittest.TestCase):
self.assertIn('04020103', server.scc._tp.sent[0].upper())
class SetupMenuParseTests(unittest.TestCase):
"""SET UP MENU parsing must accept both TLV tag styles (v3.6.40)."""
# The live Alfa card's SET UP MENU as fetched during the TERMINAL PROFILE
# chain (2026-09-29): the title is the CR-set tag 0x85, the items 0x8F.
LIVE_FETCH = bytes.fromhex(
'd03c810301250082028182850b416c6661204d6f62696c65'
'8f16808112089db0c1c2c0beb9bab82f53657474696e6773'
'8f0881810400d4e5f3f418020021')
def test_live_card_title_and_items_are_parsed(self):
menu = _parse_setup_menu_command(self.LIVE_FETCH)
self.assertEqual(menu['command_number'], 1)
self.assertEqual(menu['title'], 'Alfa Mobile')
self.assertEqual(menu['items'][0], {'id': 0x80, 'text': 'Настройки/Settings'})
self.assertEqual(menu['items'][1],
{'id': 0x81, 'text': 'Test', 'nai': 0x21, 'nai_name': 'DISPLAY TEXT'})
def test_plain_tag_variants_are_accepted(self):
# same command with the plain title tag 0x05 and a plain 0x0F item
raw = bytes.fromhex('d012810301250082028182850b416c6661204d6f62696c650f00')
menu = _parse_setup_menu_command(raw)
self.assertEqual(menu['title'], 'Alfa Mobile')
self.assertEqual(menu['items'], [])
# a command that is not SET UP MENU has no menu
self.assertIsNone(_parse_setup_menu_command(bytes.fromhex('d009810301260182028182')))
self.assertIsNone(_parse_setup_menu_command(b''))
class RamProgressTests(unittest.TestCase):
"""Live RAM-operation progress exposed in /api/status (v3.6.34)."""