From 35067f8d9d68e6bb4ee8aff27d07390262ecc158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 16 Aug 2026 13:22:01 +0300 Subject: [PATCH] Color-code SW in APDU list + fix empty ISIM record decode - SW column now shows green (success 90/92-9F), blue (pending 61/91/6C), amber (warning 62/63), red (error 64-6F) per ISO 7816-4 / TS 102 221. - _decode_nai: treat all-FF records as empty instead of emitting replacement characters; strip FF padding before the ASCII fallback. --- frontend/index.html | 17 ++++++++++++++++- simtrace2_pysniff/server/decode.py | 4 +++- tests/test_decode.py | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 677b96c..17b7f78 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -111,6 +111,10 @@ body { font: 14px/1.5 system-ui, sans-serif; background: var(--bg); color: var(- .apdu-type.pps { color: var(--yellow); } .apdu-data { font-family: monospace; color: var(--text-dim); word-break: break-all; flex: 1; } .apdu-sw { font-family: monospace; color: var(--text-muted); white-space: nowrap; min-width: 52px; } +.apdu-sw.sw-ok { color: var(--green); } +.apdu-sw.sw-pending { color: var(--accent); } +.apdu-sw.sw-warn { color: var(--warn); } +.apdu-sw.sw-error { color: var(--error); } /* APDU detail panel */ .apdu-detail { display: flex; flex-direction: column; max-height: 40%; overflow-y: auto; border-top: 2px solid var(--accent); background: var(--bg-panel); flex-shrink: 0; } @@ -453,6 +457,17 @@ function sw_text(m) { return d.sw.sw1 + ' ' + d.sw.sw2; } +function sw_class(m) { + const d = m.decoded; + if (!d || !d.sw) return ''; + const s = d.sw.sw1; + if (s === '90' || (s >= '92' && s <= '9f')) return 'sw-ok'; + if (s === '61' || s === '91' || s === '6c') return 'sw-pending'; + if (s === '62' || s === '63') return 'sw-warn'; + if (s >= '64' && s <= '6f') return 'sw-error'; + return ''; +} + // ──────────────────── API ──────────────────── const API = { async _fetch(method, path, body) { @@ -974,7 +989,7 @@ function renderMessages() { return `
${fmt_elapsed(m.elapsed)} ${label} - ${sw_text(m)} + ${sw_text(m)} ${cmd_description(m)}
`; }).join(''); diff --git a/simtrace2_pysniff/server/decode.py b/simtrace2_pysniff/server/decode.py index f22f10b..deed7c9 100644 --- a/simtrace2_pysniff/server/decode.py +++ b/simtrace2_pysniff/server/decode.py @@ -2213,6 +2213,8 @@ def _decode_phase(raw, p1=None): def _decode_nai(raw, p1=None): """ISIM identity files (EF_IMPI/DOMAIN/IMPU): BER-TLV tag 0x80 + ASCII value.""" + if not raw or all(b == 0xff for b in raw): + return {'empty': True} texts = [] for tag, _length, value in parse_tlv(raw): if tag == 0x80: @@ -2221,7 +2223,7 @@ def _decode_nai(raw, p1=None): texts.append(txt) if texts: return {'text': texts[0] if len(texts) == 1 else ', '.join(texts)} - txt = raw.decode('ascii', 'replace').replace('\xff', '').strip() + txt = raw.rstrip(b'\xff').decode('ascii', 'replace').strip() return {'text': txt} if txt else {'raw': raw.hex().upper()} diff --git a/tests/test_decode.py b/tests/test_decode.py index 8c2e5ed..569deb0 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -901,6 +901,20 @@ class TestFileDecoders(unittest.TestCase): self.assertEqual(f['applications'][0]['label'], 'MegaFon') self.assertEqual(f['applications'][0]['aid'], 'A0000000871002FFFFF00189000001FF') + def test_nai_empty_record(self): + from simtrace2_pysniff.server.decode import _decode_file_data + # Unused ISIM record (all FF) must be 'empty', not garbage text. + f = _decode_file_data('6f04', b'\xff' * 75) + self.assertTrue(f['empty']) + self.assertNotIn('text', f) + + def test_nai(self): + from simtrace2_pysniff.server.decode import _decode_file_data + f = _decode_file_data('6f04', bytes.fromhex( + '80357369703a32353030323639333537373333363840696d732e6d6e63' + '3030322e6d63633235302e336770706e6574776f726b2e6f7267')) + self.assertEqual(f['text'], 'sip:250026935773368@ims.mnc002.mcc250.3gppnetwork.org') + def test_plmn_wact(self): from simtrace2_pysniff.server.decode import _decode_file_data # TS 51.011 §10.3.35: 5-byte entries (3 PLMN + 2 access tech).