diff --git a/frontend/index.html b/frontend/index.html
index 6fbc192..92666a9 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2706,7 +2706,7 @@ function onFilterChange() {
}
// ──────────────────── Versioning ────────────────────
-const PWA_VERSION = '2.1.0';
+const PWA_VERSION = '2.1.1';
const MIN_SERVER_VERSION = '1.0.0';
function parseVersion(v) {
diff --git a/frontend/sw.js b/frontend/sw.js
index 547250b..49585dd 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simtrace-analyser-v55';
+const CACHE = 'simtrace-analyser-v56';
const PRECACHE = [
'index.html',
diff --git a/pyproject.toml b/pyproject.toml
index 967b6ea..1939d7d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "simtrace-analyser"
-version = "2.1.0"
+version = "2.1.1"
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"}
diff --git a/simtrace_analyser/decode.py b/simtrace_analyser/decode.py
index bedc37c..c1ef11e 100644
--- a/simtrace_analyser/decode.py
+++ b/simtrace_analyser/decode.py
@@ -17,18 +17,18 @@ SW_NAMES = {
(0x62, 0x83): 'Warning — selected file deactivated',
(0x62, 0x84): 'Warning — FCI not formatted per ISO',
(0x62, 0x85): 'Warning — selected file in termination state',
+ (0x62, 0x86): 'No input data available from a sensor on the card',
(0x62, 0xf1): 'More data available',
(0x62, 0xf2): 'More data available and proactive command pending',
(0x62, 0xf3): 'Response data available',
(0x63, 0x00): 'Warning — no information',
+ (0x63, 0x81): 'File filled up by the last write',
(0x63, 0xf1): 'More data expected',
(0x63, 0xf2): 'More data expected and proactive command pending',
(0x64, 0x00): 'Execution error — no information',
(0x64, 0x01): 'Execution error — immediate response required',
(0x65, 0x00): 'Execution error — no information, NV memory changed',
(0x65, 0x81): 'Execution error — memory failure',
- (0x66, 0x00): 'Security error — no information',
- (0x66, 0x81): 'Reserved for security-related issues',
(0x67, 0x00): 'Checking error — wrong length',
(0x68, 0x00): 'Checking error — no information',
(0x68, 0x81): 'Logical channel not supported',
@@ -1030,8 +1030,8 @@ REFRESH_MODES = {
0x04: 'UICC Reset',
0x05: 'NAA Application Reset',
0x06: 'NAA Session Reset',
- 0x07: 'Reserved (Steering of Roaming)',
- 0x08: 'Reserved (Steering of Roaming for I-WLAN)',
+ 0x07: 'Steering of Roaming',
+ 0x08: 'Steering of Roaming for I-WLAN',
0x09: 'eUICC Profile State Change',
0x0A: 'Application Update',
}
@@ -1053,9 +1053,8 @@ ENVELOPE_TYPES = {
0xDE: 'ENVELOPE CONTAINER',
0xDF: 'PROSE REPORT',
0xE0: '5G PROSE REPORT',
- 0xE1: 'Reserved for 3GPP',
- 0xE2: 'Reserved for 3GPP',
- 0xE3: 'Reserved for 3GPP',
+ # 'E1'-'E3': reserved for 3GPP (for future usage) per TS 31.111 §9.1 —
+ # no name assigned yet, so they decode as unknown.
0xE4: 'GSMA',
}
@@ -1100,12 +1099,14 @@ PLI_QUALIFIERS = {
0x02: 'Network Measurement results',
0x03: 'Date, time and time zone',
0x04: 'Language setting',
+ # ETSI TS 102 223: "Reserved for GSM" — defined by TS 31.111 §8.6.
0x05: 'Timing Advance',
0x06: 'Access Technology (single)',
0x07: 'ESN of the terminal',
0x08: 'IMEISV',
0x09: 'Search Mode',
0x0A: 'Battery charge state',
+ 0x0B: 'MEID of the terminal',
0x0C: 'Current WSID',
0x0D: 'Broadcast Network info',
0x0E: 'Multiple Access Technologies',
@@ -1410,7 +1411,8 @@ TR_RESULTS = {
0x25: 'Interaction with call control by NAA, temporary problem',
0x26: 'Launch browser generic error',
0x27: 'MMS temporary problem',
- # '28'-'29' reserved for 3GPP
+ 0x28: 'Bearer Independent Protocol temporary error',
+ # '29' reserved for 3GPP (for future usage)
0x30: 'Command beyond ME capabilities',
0x31: 'Command type not understood by ME',
0x32: 'Command data not understood by ME',
diff --git a/simtrace_analyser/version.py b/simtrace_analyser/version.py
index 9a35ae8..6d05c04 100644
--- a/simtrace_analyser/version.py
+++ b/simtrace_analyser/version.py
@@ -1,3 +1,3 @@
"""Single source of truth for the SIMtrace Analyser version."""
-__version__ = '2.1.0'
+__version__ = '2.1.1'
diff --git a/tests/test_decode.py b/tests/test_decode.py
index 85d4a07..2853dd0 100644
--- a/tests/test_decode.py
+++ b/tests/test_decode.py
@@ -136,6 +136,12 @@ class TestCommandTypeTables(unittest.TestCase):
self.assertEqual(ENVELOPE_TYPES[0xD3], 'MENU SELECTION')
self.assertEqual(ENVELOPE_TYPES[0xD6], 'EVENT DOWNLOAD')
+ def test_unassigned_envelope_tags_unnamed(self):
+ # TS 31.111 §9.1: 'E1'-'E3' reserved for 3GPP (for future usage);
+ # no name exists, so no placeholder label may be shown.
+ for tag in (0xE1, 0xE2, 0xE3):
+ self.assertNotIn(tag, ENVELOPE_TYPES)
+
class TestChangeAndFidiDecoding(unittest.TestCase):
def test_change_reset_assert(self):
@@ -215,6 +221,13 @@ class TestPliQualifier(unittest.TestCase):
self.assertEqual(r['cat_command'], 'PROVIDE LOCAL INFORMATION')
self.assertEqual(r['cmd']['qualifier'], '0x03 Date, time and time zone')
+ def test_fetch_pli_meid(self):
+ # '0B' = MEID of the terminal (TS 102 223 §8.6).
+ r = decode_message(bytes.fromhex(
+ '801200000BD009810301260B820281829000'))
+ self.assertEqual(r['cat_command'], 'PROVIDE LOCAL INFORMATION')
+ self.assertEqual(r['cmd']['qualifier'], '0x0B MEID of the terminal')
+
def test_non_pli_no_qualifier(self):
r = decode_message(bytes.fromhex(
'801400000C810301250002028281830100910F'))
@@ -1757,6 +1770,23 @@ class TestSwWrongLength(unittest.TestCase):
from simtrace_analyser.decode import decode_sw
self.assertIsNone(decode_sw(bytes.fromhex('6205'))['name'])
+ def test_62_86_sensor_warning(self):
+ from simtrace_analyser.decode import decode_sw
+ self.assertEqual(decode_sw(bytes.fromhex('6286'))['name'],
+ 'No input data available from a sensor on the card')
+
+ def test_63_81_file_filled_warning(self):
+ from simtrace_analyser.decode import decode_sw
+ self.assertEqual(decode_sw(bytes.fromhex('6381'))['name'],
+ 'File filled up by the last write')
+
+ def test_66_class_not_fabricated(self):
+ # No '66xx' row exists in ISO 7816-4:2005 Table 6 or TS 102 221
+ # tables 10.9/10.10 — the decoder must not invent a name.
+ from simtrace_analyser.decode import decode_sw
+ self.assertIsNone(decode_sw(bytes.fromhex('6600'))['name'])
+ self.assertIsNone(decode_sw(bytes.fromhex('6681'))['name'])
+
class TestStatusFcp(unittest.TestCase):
def test_status_body_is_fcp(self):
@@ -1849,6 +1879,32 @@ class TestRefresh(unittest.TestCase):
self.assertEqual(r['cmd']['qualifier'], '0x01 File Change Notification')
self.assertEqual(r['cmd']['file_list'], ['6F07', '6F20'])
+ def test_steering_of_roaming(self):
+ # ETSI "Reserved for GSM/3G" — TS 31.111 §8.6 defines the mode.
+ r = decode_message(bytes.fromhex('801200000bd0098103010107820281829000'))
+ self.assertEqual(r['cmd']['qualifier'], '0x07 Steering of Roaming')
+
+ def test_steering_of_roaming_iwlan(self):
+ r = decode_message(bytes.fromhex('801200000bd0098103010108820281829000'))
+ self.assertEqual(r['cmd']['qualifier'],
+ '0x08 Steering of Roaming for I-WLAN')
+
+
+class TestNoReservedLabels(unittest.TestCase):
+ # ETSI 'Reserved for GSM/3GPP' values must be resolved via the sibling
+ # spec or left unnamed — never surfaced as a 'Reserved ...' label.
+
+ def test_no_reserved_labels(self):
+ from simtrace_analyser import decode as d
+ for name in ('SW_NAMES', 'REFRESH_MODES', 'ENVELOPE_TYPES',
+ 'TR_RESULTS', 'PLI_QUALIFIERS', 'CAT_COMMAND_TYPES',
+ 'EVENT_TYPES'):
+ table = getattr(d, name)
+ for value in table.values():
+ if isinstance(value, str):
+ self.assertFalse(value.startswith('Reserved'),
+ f'{name}: {value}')
+
class TestIdleModeText(unittest.TestCase):
"""SET UP IDLE MODE TEXT (TS 102 223 §6.6.13) proactive command."""
@@ -2005,6 +2061,13 @@ class TestSpecRegistry(unittest.TestCase):
self.assertEqual(TR_RESULTS[0x3C], 'Frames error')
self.assertEqual(TR_RESULTS[0x3D], 'MMS error')
+ def test_tr_result_bip_temporary(self):
+ # '28' per TS 102 223 §8.12 (timer in Additional information).
+ from simtrace_analyser.decode import TR_RESULTS
+ self.assertEqual(TR_RESULTS[0x28],
+ 'Bearer Independent Protocol temporary error')
+ self.assertNotIn(0x29, TR_RESULTS)
+
def test_envelope_types_extended(self):
from simtrace_analyser.decode import ENVELOPE_TYPES
self.assertEqual(ENVELOPE_TYPES[0xD9], 'USSD DOWNLOAD')
diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py
index 8c4dedb..890504b 100644
--- a/tests/test_pcap_import.py
+++ b/tests/test_pcap_import.py
@@ -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.1.0')
+ self.assertEqual(__version__, '2.1.1')
def test_status_includes_version(self):
import os