diff --git a/frontend/index.html b/frontend/index.html index d2a1c6b..60c9398 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1901,7 +1901,7 @@ function onFilterChange() { } // ──────────────────── Versioning ──────────────────── -const PWA_VERSION = '1.17.0'; +const PWA_VERSION = '1.17.1'; const MIN_SERVER_VERSION = '1.0.0'; function parseVersion(v) { diff --git a/frontend/sw.js b/frontend/sw.js index d4faf00..9540a82 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simtrace-analyser-v24'; +const CACHE = 'simtrace-analyser-v25'; const PRECACHE = [ 'index.html', diff --git a/pyproject.toml b/pyproject.toml index 3c7f5a6..7eef658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend" [project] name = "simtrace2-pysniff" -version = "1.17.0" +version = "1.17.1" description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)" readme = {file = "README.md", content-type = "text/markdown"} license = {text = "GPL-2.0-or-later"} diff --git a/simtrace2_pysniff/__init__.py b/simtrace2_pysniff/__init__.py index 9790553..5a2c95a 100644 --- a/simtrace2_pysniff/__init__.py +++ b/simtrace2_pysniff/__init__.py @@ -6,7 +6,7 @@ from .gsmtap import GsmtapSender, GsmtapReceiver from .pcap import PcapWriter from .dump import FileDumper, format_message -__version__ = '1.17.0' +__version__ = '1.17.1' __all__ = [ 'SniffSession', 'DeviceDisconnected', diff --git a/simtrace2_pysniff/server/decode.py b/simtrace2_pysniff/server/decode.py index 98d9125..4a89460 100644 --- a/simtrace2_pysniff/server/decode.py +++ b/simtrace2_pysniff/server/decode.py @@ -3243,19 +3243,32 @@ def _decode_smsp(raw, p1=None): def _decode_spdi(raw, p1=None): - """EF_SPDI (§4.2.66): 'A3' container with PLMN records (§0.3).""" + """EF_SPDI (§4.2.66): 'A3' container (or bare '80' value tags) with + 3-byte PLMN records — same shape as EF_PLMNwAcT for _file_summary.""" if all(b == 0xff for b in raw): return {'empty': True} + plmns = [] + + def collect(value): + for i in range(0, len(value) - 2, 3): + plmn = _decode_plmn(value[i:i + 3]) + if plmn and plmn not in plmns: + plmns.append(plmn) + for tag, _length, value in parse_tlv(raw): if tag == 0xA3: - plmns = [] - for i in range(0, len(value) - 2, 3): - plmn = _decode_plmn(value[i:i + 3]) - if plmn: - plmns.append(f"{plmn['mcc']}-{plmn['mnc']}") - if plmns: - return {'plmns': plmns if len(plmns) > 1 else plmns[0]} - return {'raw': raw.hex().upper()} + got = False + for t2, _l2, v2 in parse_tlv(value): + if t2 == 0x80: + collect(v2) + got = True + if not got: + collect(value) # bare records inside A3 + elif tag == 0x80: + collect(value) + if not plmns: + return {'raw': raw.hex().upper()} + return {'plmns': plmns} def _decode_spn(raw, p1=None): diff --git a/tests/test_decode.py b/tests/test_decode.py index 7cad17b..cd00048 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -1976,8 +1976,29 @@ class TestNewFileDecoders(unittest.TestCase): def test_spdi(self): from simtrace2_pysniff.server.decode import _decode_spdi - r = _decode_spdi(bytes.fromhex('a30652f01000f110'), p1=None) - self.assertEqual(r['plmns'], ['250-01', '001-01']) + # Standard form: A3 wrapping inner '80' TLVs, one PLMN each + r = _decode_spdi(bytes.fromhex('a30a8003' '52f010' '8003' '72f010'), p1=None) + self.assertEqual(r['plmns'], [{'mcc': '250', 'mnc': '01'}, + {'mcc': '270', 'mnc': '01'}]) + + def test_spdi_bare_records_fallback(self): + from simtrace2_pysniff.server.decode import _decode_spdi + r = _decode_spdi(bytes.fromhex('a306' '52f010' '00f110'), p1=None) + self.assertEqual(r['plmns'], [{'mcc': '250', 'mnc': '01'}, + {'mcc': '001', 'mnc': '01'}]) + + def test_spdi_read_binary_regression(self): + # stk_test1 session msg 14036: READ BINARY of EF_SPDI crashed + # _build_summary (plmns were strings, not dicts) → whole message + # decoded as None ("No decoded data available"). + prev = {'sel': {'fid': '6fcd', 'name': 'EF_SPDI', + 'structure': 'transparent'}, + 'ins': 0xA4, 'ins_name': 'SELECT', 'sw1': '61'} + r = decode_message(bytes.fromhex( + '00b000000da305800352f020ffffffffffff9000'), prev=prev) + self.assertEqual(r['file']['plmns'], + [{'mcc': '250', 'mnc': '02'}]) + self.assertIn('250/02', r['summary']) def test_smsp(self): from simtrace2_pysniff.server.decode import _decode_smsp diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py index 7e5ab53..2a32b32 100644 --- a/tests/test_pcap_import.py +++ b/tests/test_pcap_import.py @@ -197,7 +197,7 @@ class TestImportEndpoint(unittest.TestCase): class TestVersion(unittest.TestCase): def test_package_version(self): from simtrace2_pysniff import __version__ - self.assertEqual(__version__, '1.17.0') + self.assertEqual(__version__, '1.17.1') def test_status_includes_version(self): import os