TERMINAL CAPABILITY: decode the A9 template (TS 102 221 §11.1.19)

- terminal power supply (tag 80): voltage class per Table 6.1, maximum
  current in mA, clock in 0.1 MHz steps ('FF' = not indicated)
- extended logical channels (tag 81) — zero length per §11.1.19.2.2, a
  nonzero length is preserved as raw
- additional interfaces (tag 82 b1 = UICC-CLF)
- eUICC device capabilities: tag 83 per GSMA SGP.22 v2.6 §3.4.2 Table 7
  (LUId/LPDd/LDSd/LUIe-SCWS); tag 84 preserved raw (no pinned SGP.32 spec)
- unknown/private TLVs preserved via raw_tlv
- concise APDU-list summary; decoded rows in the PWA detail view
- v2.1.0; SW cache simtrace-analyser-v55
- tests: 6 real samples from the local trace sessions (49/55/58/61/206/208)
This commit is contained in:
catarrh committed 2026-09-20 00:11:41 +03:00
1 parent 38650b4797
commit 561801a983
9 files changed
+210 -6

No files matched your search

+59
View File
@@ -1079,6 +1079,65 @@ class TestTerminalProfile(unittest.TestCase):
self.assertEqual(tp['summary'], 'handset \u00b7 BIP')
class TestTerminalCapability(unittest.TestCase):
"""TERMINAL CAPABILITY (TS 102 221 §11.1.19) — real samples captured
from the local trace sessions (49/55/58/61/206/208)."""
def _dec(self, body_hex):
raw = '80AA0000%02x%s9000' % (len(body_hex) // 2, body_hex)
return decode_message(bytes.fromhex(raw))
def test_power_supply(self):
# A9 0A 80 03 04 3C FF 81 00 82 01 00 — class C, 60 mA, clock n/a.
r = self._dec('A90A8003043CFF8100820100')
self.assertEqual(r['body']['label'], 'Terminal capability template (A9)')
ps = r['cmd']['power_supply']
self.assertEqual(ps['classes'], ['C'])
self.assertEqual(ps['class_raw'], 0x04)
self.assertEqual(ps['max_current_ma'], 60)
self.assertIsNone(ps['clock_mhz'])
self.assertTrue(r['cmd']['extended_channels'])
self.assertEqual(r['cmd']['interfaces'], [])
self.assertEqual(r['summary'],
'Class C \u00b7 60 mA \u00b7 CLK n/a \u00b7 extended channels')
def test_interfaces_and_euicc(self):
# A9 08 81 00 82 01 01 83 01 87 — UICC-CLF + SGP.22 LPA functions
# (b8 is an RFU bit and must not affect the function list).
r = self._dec('A9088100820101830187')
cmd = r['cmd']
self.assertNotIn('power_supply', cmd)
self.assertEqual(cmd['interfaces'], ['UICC-CLF'])
self.assertEqual(cmd['euicc_sgp22']['raw'], '87')
self.assertEqual(cmd['euicc_sgp22']['functions'], ['LUId', 'LPDd', 'LDSd'])
self.assertEqual(r['summary'],
'extended channels \u00b7 UICC-CLF \u00b7 '
'eUICC: LUId, LPDd, LDSd')
def test_interfaces_only(self):
# A9 05 81 00 82 01 01 — phones that omit the power supply TLV.
r = self._dec('A9058100820101')
self.assertNotIn('power_supply', r['cmd'])
self.assertEqual(r['summary'], 'extended channels \u00b7 UICC-CLF')
def test_extended_channels_nonzero_length(self):
# A UICC shall interpret any length as zero (TS 102 221 §11.1.19.2.2).
r = self._dec('A906810100820101')
self.assertTrue(r['cmd']['extended_channels'])
self.assertEqual(r['cmd']['extended_channels_raw'], '00')
def test_private_tlv_preserved(self):
# Unknown/private TLV ('DD') inside the A9 template must survive.
r = self._dec('A9088100820101DD017F')
self.assertEqual(r['cmd']['raw_tlv'], [{'tag': 'DD', 'value': '7F'}])
self.assertEqual(r['summary'], 'extended channels \u00b7 UICC-CLF')
def test_no_a9_template(self):
# Command data without the A9 template stays undecoded (no crash).
r = self._dec('8003043CFF')
self.assertNotIn('cmd', r)
class TestSummary(unittest.TestCase):
def test_select_path_from_mf(self):
r = decode_message(bytes.fromhex('a0a40804022fe29000'))
+1 -1
View File
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
self.assertEqual(__version__, '2.0.0')
self.assertEqual(__version__, '2.1.0')
def test_status_includes_version(self):
import os