Show T=15 as PPS capability, not a transport protocol

T=15 in the ATR is a Protocol-and-Parameter-Selection capability,
not a data protocol like T=0/T=1. Previously 'Protocols: T=0, T=15'
was misleading.

- _decode_atr: exclude T=15 from protocols, set pps=True instead
- TCK presence: only-t0 rule now also requires no PPS (T=15 implies TCK)
- PWA ATR detail: 'Protocol: T=0' + 'PPS: supported' rows
- +1 test with the real UICC ATR (3B 9F 95 80 1F C7 ...)
- Bump 1.22.2, SW cache v33->v34
This commit is contained in:
catarrh committed 2026-09-05 23:20:16 +03:00
1 parent 6376fb1591
commit 6bdbbea209
7 files changed
+29 -7

No files matched your search

+5 -2
View File
@@ -1450,7 +1450,10 @@ function atrPpsRows(decoded) {
rows.push(['Convention', decoded.convention || 'unknown']);
if (decoded.ts) rows.push(['TS', '0x' + decoded.ts]);
if (decoded.t0) rows.push(['T0', '0x' + decoded.t0 + ' — ' + decoded.historical_len + ' historical bytes']);
if (decoded.protocols) rows.push(['Protocols', decoded.protocols.join(', ')]);
if (decoded.protocols && decoded.protocols.length) {
rows.push([decoded.protocols.length > 1 ? 'Protocols' : 'Protocol', decoded.protocols.join(', ')]);
}
if (decoded.pps) rows.push(['PPS', 'supported']);
if (decoded.interface) {
for (const ifc of decoded.interface) {
rows.push([ifc.name, ifaceValue(ifc)]);
@@ -2511,7 +2514,7 @@ function onFilterChange() {
}
// ──────────────────── Versioning ────────────────────
const PWA_VERSION = '1.22.1';
const PWA_VERSION = '1.22.2';
const MIN_SERVER_VERSION = '1.0.0';
function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v33';
const CACHE = 'simtrace-analyser-v34';
const PRECACHE = [
'index.html',
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "simtrace2-pysniff"
version = "1.22.1"
version = "1.22.2"
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"}
+1 -1
View File
@@ -6,7 +6,7 @@ from .gsmtap import GsmtapSender, GsmtapReceiver
from .pcap import PcapWriter
from .dump import FileDumper, format_message
__version__ = '1.22.1'
__version__ = '1.22.2'
__all__ = [
'SniffSession',
'DeviceDisconnected',
+7 -1
View File
@@ -4177,6 +4177,7 @@ def _decode_atr(data):
interface = []
protocols = []
pps = False
flags = t0 & 0xF0
i = 1
level = 1
@@ -4229,6 +4230,9 @@ def _decode_atr(data):
b = body[i]
i += 1
cur_t = b & 0x0F
if cur_t == 15:
pps = True
else:
protocols.append(_T_PROTOCOLS.get(cur_t, f'T={cur_t}'))
flags = b & 0xF0
level += 1
@@ -4239,6 +4243,8 @@ def _decode_atr(data):
result['interface'] = interface
if protocols:
result['protocols'] = protocols
if pps:
result['pps'] = True
k = result['historical_len']
if k and i + k <= len(body):
@@ -4246,7 +4252,7 @@ def _decode_atr(data):
i += k
# TCK present unless only T=0 is proposed (ISO 7816-3 §8.2.5).
only_t0 = not protocols or protocols == ['T=0']
only_t0 = (not protocols or protocols == ['T=0']) and not pps
if not only_t0 and i < len(body):
tck = body[i]
check = 0
+13
View File
@@ -898,6 +898,19 @@ class TestAtrPps(unittest.TestCase):
self.assertEqual(r['historical']['category'], 'status information (life cycle)')
self.assertEqual(r['historical']['life_cycle'], 'operational state (activated)')
def test_atr_t15_is_pps_capability(self):
from simtrace2_pysniff.server.decode import _decode_atr
# Real UICC ATR: T0=9F (TA1+TD1, 15 hist bytes), TA1=95, TD1=80 (T=0),
# TD2=1F (T=15 → PPS capability), TA3=C7 (clock stop + classes A/B/C).
r = _decode_atr(bytes.fromhex('3B9F95801FC78031E073FE21136364035B83079000F7'))
self.assertEqual(r['protocols'], ['T=0'])
self.assertTrue(r['pps'])
ta3 = r['interface'][1]
self.assertEqual(ta3['name'], 'TA3')
self.assertEqual(ta3['clock_stop'], 'no preference')
self.assertEqual(ta3['classes'], ['A', 'B', 'C'])
self.assertTrue(r['tck_valid'])
def test_pps(self):
from simtrace2_pysniff.server.decode import _decode_pps
# FF 10 11 xx: PPS0=10 (T=0, PPS1 present), PPS1=11 (Fi=1,Di=1), PCK=XOR(FF,10,11)=FE
+1 -1
View File
@@ -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__, '1.22.1')
self.assertEqual(__version__, '1.22.2')
def test_status_includes_version(self):
import os