Fix EF_SPDI decode crash: parse inner '80' TLVs, emit dict plmns
stk_test1 msg 14036 regression: READ BINARY of EF_SPDI returned
"No decoded data available" — _decode_spdi (added v1.16.0) parsed
the A3 container value as bare 3-byte records (missing the inner
'80' value TLVs) and returned a list of strings, while
_build_summary/_file_summary expects the EF_PLMNwAcT shape
(list of {mcc, mnc} dicts). The resulting TypeError aborted the
whole message decode — this was the source of the 28 "Decode
error ... string indices" messages seen across all sessions.
_decode_spdi now unwraps inner '80' TLVs (bare-records fallback
kept) and always returns {'plmns': [{mcc, mnc}...]}. Replay of all
33 sessions: decode exceptions 28 → 0 (2 remaining undecoded tpdu
are 1-byte truncated capture records, not decodable by nature).
Tests: +3 (standard inner-80 form, bare-records fallback, exact
stk_test1 regression via decode_message). 344 total. Version
1.17.1, SW cache simtrace-analyser-v25.
This commit is contained in:
1 parent
07deab9c26
commit
14445ae38f
7 files changed
+50
-16
No files matched your search
+1
-1
@@ -1901,7 +1901,7 @@ function onFilterChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ──────────────────── Versioning ────────────────────
|
// ──────────────────── Versioning ────────────────────
|
||||||
const PWA_VERSION = '1.17.0';
|
const PWA_VERSION = '1.17.1';
|
||||||
const MIN_SERVER_VERSION = '1.0.0';
|
const MIN_SERVER_VERSION = '1.0.0';
|
||||||
|
|
||||||
function parseVersion(v) {
|
function parseVersion(v) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simtrace-analyser-v24';
|
const CACHE = 'simtrace-analyser-v25';
|
||||||
|
|
||||||
const PRECACHE = [
|
const PRECACHE = [
|
||||||
'index.html',
|
'index.html',
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "simtrace2-pysniff"
|
name = "simtrace2-pysniff"
|
||||||
version = "1.17.0"
|
version = "1.17.1"
|
||||||
description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)"
|
description = "Python-based sniffing tool for SIMtrace2 hardware (SIM card communication sniffer)"
|
||||||
readme = {file = "README.md", content-type = "text/markdown"}
|
readme = {file = "README.md", content-type = "text/markdown"}
|
||||||
license = {text = "GPL-2.0-or-later"}
|
license = {text = "GPL-2.0-or-later"}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from .gsmtap import GsmtapSender, GsmtapReceiver
|
|||||||
from .pcap import PcapWriter
|
from .pcap import PcapWriter
|
||||||
from .dump import FileDumper, format_message
|
from .dump import FileDumper, format_message
|
||||||
|
|
||||||
__version__ = '1.17.0'
|
__version__ = '1.17.1'
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'SniffSession',
|
'SniffSession',
|
||||||
'DeviceDisconnected',
|
'DeviceDisconnected',
|
||||||
|
|||||||
@@ -3243,19 +3243,32 @@ def _decode_smsp(raw, p1=None):
|
|||||||
|
|
||||||
|
|
||||||
def _decode_spdi(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):
|
if all(b == 0xff for b in raw):
|
||||||
return {'empty': True}
|
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):
|
for tag, _length, value in parse_tlv(raw):
|
||||||
if tag == 0xA3:
|
if tag == 0xA3:
|
||||||
plmns = []
|
got = False
|
||||||
for i in range(0, len(value) - 2, 3):
|
for t2, _l2, v2 in parse_tlv(value):
|
||||||
plmn = _decode_plmn(value[i:i + 3])
|
if t2 == 0x80:
|
||||||
if plmn:
|
collect(v2)
|
||||||
plmns.append(f"{plmn['mcc']}-{plmn['mnc']}")
|
got = True
|
||||||
if plmns:
|
if not got:
|
||||||
return {'plmns': plmns if len(plmns) > 1 else plmns[0]}
|
collect(value) # bare records inside A3
|
||||||
return {'raw': raw.hex().upper()}
|
elif tag == 0x80:
|
||||||
|
collect(value)
|
||||||
|
if not plmns:
|
||||||
|
return {'raw': raw.hex().upper()}
|
||||||
|
return {'plmns': plmns}
|
||||||
|
|
||||||
|
|
||||||
def _decode_spn(raw, p1=None):
|
def _decode_spn(raw, p1=None):
|
||||||
|
|||||||
+23
-2
@@ -1976,8 +1976,29 @@ class TestNewFileDecoders(unittest.TestCase):
|
|||||||
|
|
||||||
def test_spdi(self):
|
def test_spdi(self):
|
||||||
from simtrace2_pysniff.server.decode import _decode_spdi
|
from simtrace2_pysniff.server.decode import _decode_spdi
|
||||||
r = _decode_spdi(bytes.fromhex('a30652f01000f110'), p1=None)
|
# Standard form: A3 wrapping inner '80' TLVs, one PLMN each
|
||||||
self.assertEqual(r['plmns'], ['250-01', '001-01'])
|
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):
|
def test_smsp(self):
|
||||||
from simtrace2_pysniff.server.decode import _decode_smsp
|
from simtrace2_pysniff.server.decode import _decode_smsp
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class TestImportEndpoint(unittest.TestCase):
|
|||||||
class TestVersion(unittest.TestCase):
|
class TestVersion(unittest.TestCase):
|
||||||
def test_package_version(self):
|
def test_package_version(self):
|
||||||
from simtrace2_pysniff import __version__
|
from simtrace2_pysniff import __version__
|
||||||
self.assertEqual(__version__, '1.17.0')
|
self.assertEqual(__version__, '1.17.1')
|
||||||
|
|
||||||
def test_status_includes_version(self):
|
def test_status_includes_version(self):
|
||||||
import os
|
import os
|
||||||
|
|||||||
Reference in new issue
Block a user