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