v1.9.2: PPS CLK upshift + exact CLK-edge projection (new reader fixture)
- handle_pps(): flag the first post-PPS character; _read_byte_clk() re-measures the CLK period there and accounts the measurement's 16 edges. The Alcor reader upclocks 4.0 -> 4.8 MHz at PPS (FI/DI still 512/32), so the ATR measurement was stale: 64 vs 53.33 samples/ETU, no post-PPS APDU framed. - _wait_clk_rising(): project to the n-th edge (skip to target - half a period, take one edge) instead of "skip n-2 cycles + 2 edges", which lost ~1 edge per call on fractional periods and slid the samples ~0.5 ETU early by the parity bit. xiaomi 7698 -> 2, 4gmodem 7218 -> 0, S21p_sim2 394 -> 0 CHKSUM lines (previously misread as dirty-wire noise). - CONCAT gate: EMBEDDED_CLA excludes the structural 0x4x/0x5x classes (payload false hit on the Alcor secured packet); mirrored in tools/vs_reader.py and covered by a new vector in tests/test_gsmtap.py. - tools/capture_baseline.sh: add the alcor_reader_sample fixture; baseline regenerated (APDU counts and RESULT: OK unchanged everywhere, 0 CONCAT). - 75/75 unit tests. README/AGENTS updated.
This commit is contained in:
1 parent
18ee720fda
commit
3bf5cee222
7 files changed
+184
-104
No files matched your search
@@ -18,6 +18,7 @@ Every change to `pd.py` must be followed by running the full test suite:
|
||||
for trace in test_8_raw16 test_7_raw16 xiaomi_phone_sample \
|
||||
samsung_phone_sample \
|
||||
xiaomi_mi_a1_coldboot_sample 4gmodem_coldboot_sample \
|
||||
alcor_reader_sample \
|
||||
samsung_A55_coldboot_new_cable samsung_A55_coldboot_new_cable_sim2 \
|
||||
samsung_S21p_coldboot_new_cable samsung_S21p_coldboot_new_cable_sim2; do
|
||||
# Run sigrok-cli and vs_reader as shown below
|
||||
@@ -308,6 +309,54 @@ Known limitation: no on-disk fixture reproduces the exact live failure (all
|
||||
real captures contain a 372 ATR), so end-to-end proof needs a re-captured
|
||||
`.sr` from the failing phone; the unit tests cover the loop-escape logic.
|
||||
|
||||
### v1.9.2: PPS CLK upshift + exact CLK-edge projection (new reader support)
|
||||
|
||||
Two timing bugs surfaced by `examples/alcor_reader_sample.sr`, a PC/SC reader
|
||||
that changes the CLK frequency at PPS: 4.0 MHz at the ATR, 4.8 MHz after
|
||||
(raise-to-raise 4.000 -> 3.333 samples at 16 MHz), with FI/DI still 512/32 =
|
||||
16 CLK/bit. Before the fix the whole post-PPS session framed nothing.
|
||||
|
||||
- **Stale samples-per-clock after PPS.** The native/CLK-sync path assumed
|
||||
"the CLK period is unchanged by PPS" and kept the period measured at the
|
||||
ATR, so the post-PPS ETU was 16 x 4.0 = 64 samples instead of the real
|
||||
16 x 3.333 = 53.33 -- every byte was mis-sampled. `handle_pps()` now sets
|
||||
`_clk_remeasure_pending`; `_read_byte_clk()` consumes it in the first
|
||||
post-PPS character (CLK is guaranteed to be running inside a frame),
|
||||
re-runs `_measure_clock_period()` and accounts the measurement's 16 edges
|
||||
as the first edges of the frame (`bit_samples` refreshed).
|
||||
- **Edge-skip deficit on fractional periods.** `_wait_clk_rising()` used to
|
||||
"skip (n - 2) cycles by samples, then take 2 edges". With a period that is
|
||||
a fraction of a sample the skip under-covers by ~1 edge per call: simulated
|
||||
against the real trace, 144 edges were consumed where 152 were requested,
|
||||
so the samples slid ~0.5 ETU early by the parity bit -- in 513/513 CHKSUM
|
||||
cases the "parity" sample read the last data bit. It now projects n
|
||||
periods ahead of the edge it is sitting on, skips to (target - half a
|
||||
period) and takes one edge: exact for any period, non-accumulating, one
|
||||
wait() per call. Without a period estimate it waits each edge exactly.
|
||||
- **CONCAT detector precision.** The Alcor GET RESPONSE payload (a
|
||||
secured-packet byte run "40 70 70 61 15" + an SW-like byte 21 later)
|
||||
false-hit the embedded-exchange scan through the structural 0x4x CLA that
|
||||
`plausible_cla()` tolerates for resync robustness. The detector's CLA set
|
||||
now excludes 0x4x/0x5x (no CAT traffic uses them; every tuned
|
||||
true-positive vector uses 0x00/0x01/0x08/0x80), mirrored in
|
||||
`tools/vs_reader.py` with a regression vector in `tests/test_gsmtap.py`.
|
||||
|
||||
Effect on the suite (baseline regenerated; APDU counts and `RESULT: OK`
|
||||
unchanged everywhere, 0 CONCAT):
|
||||
|
||||
| trace | CHKSUM lines before | after |
|
||||
|---|---|---|
|
||||
| alcor_reader_sample (new fixture) | no framing at all (0 APDUs) | 2 (1 idle artifact), 48 APDUs |
|
||||
| xiaomi_phone_sample | 7698 | 2 (1 event) |
|
||||
| 4gmodem_coldboot | 7218 | 0 |
|
||||
| samsung_S21p_coldboot_new_cable_sim2 | 394 | 0 |
|
||||
| test_8 / test_7 / others | 0 | 0 |
|
||||
|
||||
The xiaomi/4gmodem/S21p_sim2 counts had been read as dirty-wire parity
|
||||
noise -- they were this decoder bug. `tools/capture_baseline.sh` gains the
|
||||
alcor fixture (local-only, like every `examples/` trace). 75/75 unit tests.
|
||||
VERSION -> 1.9.2.
|
||||
|
||||
### v1.9.1: T=0 NULL is not an SW1 (fixes bogus "60 61" APDU endings)
|
||||
|
||||
The "scan for SW1" loops accepted any byte with high nibble `0x6` as a
|
||||
@@ -704,7 +753,7 @@ append the measured CLK frequency when known
|
||||
|
||||
### Versioning
|
||||
|
||||
The decoder version is defined in `pd.py` as `VERSION = '1.9.1'`.
|
||||
The decoder version is defined in `pd.py` as `VERSION = '1.9.2'`.
|
||||
The version is printed to the log on decoder startup.
|
||||
|
||||
### Testing after decoder changes
|
||||
|
||||
@@ -488,6 +488,7 @@ re-frame eliminated the last multi-exchange swallow family on
|
||||
| test_7 | constant | ~43 | 372 (standard) | Direct | PC/SC reader, warm resets |
|
||||
| xiaomi | gated/stopped | ~53 | recovered from DATA | Direct | Phone, mid-session |
|
||||
| samsung | gated/stopped | ~72 | 16 (F=512/D=32) | Direct | Phone, non-standard F/D, STK menu |
|
||||
| alcor_reader | constant | ~53 | 16 (F=512/D=32) | Direct | PC/SC reader, CLK 4.0 MHz at ATR -> 4.8 MHz after PPS |
|
||||
|
||||
The decoder auto-detects the ETU from the trace and selects the appropriate
|
||||
bit reader (CLK-sync for constant CLK, edge-list for gated/stopped CLK).
|
||||
|
||||
@@ -28,7 +28,7 @@ from .gsmtap_stream import (GsmtapStreamSender,
|
||||
GSMTAP_SIM_RST_EVENT, GSMTAP_SIM_VCC_EVENT,
|
||||
GSMTAP_FLAG_BAD_FCS)
|
||||
|
||||
VERSION = '1.9.1'
|
||||
VERSION = '1.9.2'
|
||||
|
||||
|
||||
|
||||
@@ -217,10 +217,15 @@ def is_desynced(packet):
|
||||
# data region, followed by its P3 data bytes and an interior status word.
|
||||
# These constants bound detector precision (empirically tuned on all 10
|
||||
# example traces: 0 hits on clean traces, every Samsung/xiaomi family hit).
|
||||
# A swallowed header's CLA is validated with the same structural rule as the
|
||||
# decoder itself (see plausible_cla): interindustry classes including the
|
||||
# logical-channel numbers 0x01-0x03 are legitimate first bytes of a command.
|
||||
EMBEDDED_CLA = frozenset(cla for cla in range(256) if plausible_cla(cla))
|
||||
# A swallowed header's CLA is a real command class byte: interindustry
|
||||
# classes including the logical-channel numbers 0x01-0x03. The structural
|
||||
# 0x4x/0x5x forms that plausible_cla() tolerates for resync robustness are
|
||||
# excluded here -- no CAT traffic uses them, and accepting them makes payload
|
||||
# bytes false-hit (Alcor secured-packet payload: "40 70 70 61 15" + an
|
||||
# SW-like byte 21 later).
|
||||
EMBEDDED_CLA = frozenset(
|
||||
cla for cla in range(256)
|
||||
if plausible_cla(cla) and (cla & 0xF0) not in (0x40, 0x50))
|
||||
# Commands that actually occur on these cards' bus (T=0 + STK/CAT).
|
||||
EMBEDDED_INS = frozenset((
|
||||
0x12, 0x14, 0x20, 0x44, 0x70, 0x88,
|
||||
@@ -535,6 +540,12 @@ class Decoder(srd.Decoder):
|
||||
# Forces _measure_etu() on the next decode_step() cycle so the
|
||||
# decoder re-locks to the new ETU from the live DATA line.
|
||||
self._pps_speed_changed = False
|
||||
# Set by handle_pps() on the native path: some readers also change
|
||||
# the CLK frequency at the PPS speed switch (Alcor reader: 4.0 ->
|
||||
# 4.8 MHz with FI/DI 512/32), which invalidates the samples-per-clock
|
||||
# period measured at the ATR. _read_byte_clk() re-measures it inside
|
||||
# the first post-PPS character, where CLK is guaranteed to be running.
|
||||
self._clk_remeasure_pending = False
|
||||
# Count of times the ATR hunt failed to find a valid TS byte --
|
||||
# bounds the "keep hunting" loop so a mid-session sniffer (no ATR
|
||||
# ever present) eventually falls back to T=0 parsing instead of
|
||||
@@ -1208,28 +1219,33 @@ class Decoder(srd.Decoder):
|
||||
'''Wait for n CLK rising edges. Returns False on end-of-capture.
|
||||
|
||||
CLK runs continuously for the whole of a character (Clock Stop only
|
||||
happens between characters), so within a byte it is safe to skip most
|
||||
of the way by samples and then land on the exact rising edge. This
|
||||
keeps the edge-exactness of CLK-edge timing without paying one wait()
|
||||
per CLK cycle. Lands within +/-1 CLK cycle of the target edge, which
|
||||
is far inside the stable region of a bit (a bit is held for the whole
|
||||
ETU).'''
|
||||
happens between characters). The n-th edge is found by projecting
|
||||
n periods ahead of the edge we are sitting on and landing on the
|
||||
nearest CLK edge: skip to (target - half a period), then take one
|
||||
edge. The projection keeps the edge count exact for ANY clock
|
||||
period -- "skip (n - 2) cycles, then take 2 edges" loses ~1 edge per
|
||||
call when the period is a fraction of a sample (e.g. 3.333 samples
|
||||
at 4.8 MHz / 16 MHz), and the deficit accumulates across the byte
|
||||
(the parity sample then reads the last data bit). Without a period
|
||||
estimate, fall back to waiting each edge exactly.'''
|
||||
if n <= 0:
|
||||
return True
|
||||
if self._samples_per_clock and n > 2:
|
||||
# Skip (n - 2) cycles by samples, then take the last 2 edges
|
||||
# exactly. Undershooting by a cycle is harmless (the bit is
|
||||
# stable for the full ETU), and the final two edge waits re-align
|
||||
# us to a genuine CLK edge.
|
||||
skip = int((n - 2) * self._samples_per_clock)
|
||||
spc = self._samples_per_clock
|
||||
if spc:
|
||||
target = self.samplenum + n * spc
|
||||
skip = int(target - 0.5 * spc - self.samplenum)
|
||||
if skip > 0:
|
||||
self.wait({'skip': skip})
|
||||
n = 2
|
||||
for _ in range(n):
|
||||
pins = self.wait({self.CLK_IDX: 'r'})
|
||||
if pins is None:
|
||||
self._eof = True
|
||||
return False
|
||||
else:
|
||||
for _ in range(n - 1):
|
||||
pins = self.wait({self.CLK_IDX: 'r'})
|
||||
if pins is None:
|
||||
self._eof = True
|
||||
return False
|
||||
pins = self.wait({self.CLK_IDX: 'r'})
|
||||
if pins is None:
|
||||
self._eof = True
|
||||
return False
|
||||
return True
|
||||
|
||||
def _read_byte_clk(self):
|
||||
@@ -1257,6 +1273,16 @@ class Decoder(srd.Decoder):
|
||||
bs = self.clock_skip
|
||||
self.bits = [0] # start bit (always low)
|
||||
prev_edges = 0
|
||||
if self._clk_remeasure_pending:
|
||||
# A PPS may also change the CLK frequency (see handle_pps).
|
||||
# CLK runs for the whole frame here; the measurement consumes
|
||||
# exactly 16 rising edges, accounted as the first edges of the
|
||||
# frame so the bit loop stays edge-exact.
|
||||
self._clk_remeasure_pending = False
|
||||
self._measure_clock_period()
|
||||
if not self._eof:
|
||||
prev_edges = 16
|
||||
self.bit_samples = self._compute_bit_samples()
|
||||
for i in range(9):
|
||||
# bit centres at (1.5 + i) ETU from the start fall
|
||||
total = int(round((1.5 + i) * bs))
|
||||
@@ -2041,28 +2067,17 @@ class Decoder(srd.Decoder):
|
||||
self.di = self.baud_rate.get(int(pps1 & 0x0F), 1)
|
||||
self.clock_skip = max(int(self.fi // self.di), 1)
|
||||
self.log("PPS accepted: FI", self.fi, "DI", self.di, "clock_skip", self.clock_skip)
|
||||
# PPS changed the bit rate. How we follow it depends on the reader:
|
||||
#
|
||||
# - Native CLK-sync reader (_use_clk_sync): clock_skip is the ONLY
|
||||
# timing input -- the CLK period is unchanged by PPS, so the new
|
||||
# ETU is exactly clock_skip * samples_per_clock. No DATA
|
||||
# re-measurement is needed (and none should happen: consuming a
|
||||
# burst here would silently drop the first post-PPS command).
|
||||
# Just refresh bit_samples for wait_data_falling / edge fallback.
|
||||
#
|
||||
# - Edge-list reader (non-native modes): there is no usable CLK
|
||||
# reference, so re-measure the ETU from the live DATA line on
|
||||
# the next decode cycle; that burst is emitted flagged, not
|
||||
# silently consumed.
|
||||
# PPS changed the bit rate. How we follow it depends on the mode:
|
||||
#
|
||||
# - Native CLK mode (real CLK channel): after an ACCEPTED PPS the
|
||||
# card and terminal switch to FI/DI CLK cycles per bit, and the
|
||||
# CLK period itself is unchanged. So clock_skip = FI/DI is
|
||||
# exact -- no DATA re-measurement is needed (and none should
|
||||
# happen: consuming a burst here would silently drop the first
|
||||
# post-PPS command). Just refresh bit_samples for
|
||||
# wait_data_falling / the edge-list fallback.
|
||||
# - Native CLK mode (real CLK channel): clock_skip = FI/DI is
|
||||
# protocol-defined, so the new ETU is clock_skip *
|
||||
# samples_per_clock -- no DATA re-measurement is needed (and
|
||||
# none should happen here: consuming a burst at this point
|
||||
# would silently drop the first post-PPS command). But some
|
||||
# readers also change the CLK FREQUENCY at the speed switch
|
||||
# (Alcor reader: 4.0 -> 4.8 MHz with FI/DI 512/32), which
|
||||
# invalidates the samples-per-clock measured at the ATR; the
|
||||
# first post-PPS character re-measures it (see _read_byte_clk).
|
||||
#
|
||||
# - sample_as_clock / detect modes: there is no usable CLK
|
||||
# reference, so re-measure the ETU from the live DATA line on
|
||||
@@ -2073,6 +2088,10 @@ class Decoder(srd.Decoder):
|
||||
self._pps_speed_changed = False
|
||||
# Re-hunt for the next start bit after the PPS response.
|
||||
self._start_fall = None
|
||||
# Some readers also change the CLK frequency at the switch
|
||||
# (Alcor 4.0 -> 4.8 MHz with FI/DI 512/32); re-measure it in
|
||||
# the first post-PPS character, where CLK is running.
|
||||
self._clk_remeasure_pending = True
|
||||
else:
|
||||
self._pps_speed_changed = True
|
||||
self.bit_samples = None
|
||||
|
||||
+49
-59
@@ -1,7 +1,7 @@
|
||||
=== Baseline captured 2026-09-19T00:47:38+03:00 ===
|
||||
=== Baseline captured 2026-09-27T23:45:58+03:00 ===
|
||||
|
||||
=== test_8 ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('etu recovered (samples):', 1237)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -36,7 +36,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== test_7 ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('etu recovered (samples):', 1236)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -77,27 +77,11 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== xiaomi_phone_sample ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('etu recovered (samples):', 53)
|
||||
(11673977, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 1, 0, 0, 1, 1, 1, 1])
|
||||
(11674007, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 1, 0, 0, 1, 1, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 1, 0, 0, 1, 1, 1, 1]
|
||||
(11695589, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
(11697509, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 1, 1]
|
||||
(11698149, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||
(11698789, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 0, 0, 0, 0, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||
(11699429, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 1, 1]
|
||||
(11701349, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
(11701989, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 1, 0, 0, 1, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
|
||||
(11709030, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||
CHKSUM ERROR count: 7698
|
||||
CHKSUM ERROR count: 2
|
||||
CONCAT APDU count: 0
|
||||
reader exchanges : 0
|
||||
capture ATRs : 0
|
||||
@@ -118,7 +102,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== xiaomi_mi_a1_coldboot ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -145,27 +129,13 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== 4gmodem_coldboot ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
('RST deasserted (hunt/ATR already in progress)',)
|
||||
('ENDATR', b'3b9f96801fc78031e073fe2117574a3382033339001e')
|
||||
(10083201, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
(10084089, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||
(32207790, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 0, 0, 1, 1, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 0, 0, 1, 1, 0, 0]
|
||||
(32214835, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 1, 0, 1, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 1, 0, 1, 0, 0, 0]
|
||||
(32216580, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 0, 0, 0, 0, 1, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
|
||||
(32219038, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 1, 1]
|
||||
(32219857, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||
CHKSUM ERROR count: 7218
|
||||
CHKSUM ERROR count: 0
|
||||
CONCAT APDU count: 0
|
||||
reader exchanges : 0
|
||||
capture ATRs : 1
|
||||
@@ -185,8 +155,41 @@ Degradation metrics:
|
||||
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== alcor_reader_sample ===
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
('RST deasserted (hunt/ATR already in progress)',)
|
||||
('ENDATR', b'3b9f96801fc78031e073fe2117574a3382033339001e')
|
||||
(26904874, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
('RST deasserted (hunt/ATR already in progress)',)
|
||||
('ENDATR', b'3b9f96801fc78031e073fe2117574a3382033339001e')
|
||||
CHKSUM ERROR count: 2
|
||||
CONCAT APDU count: 0
|
||||
reader exchanges : 0
|
||||
capture ATRs : 2
|
||||
capture APDUs : 48
|
||||
byte-exact matches (LCS) : 0
|
||||
reader exchanges not in capture (pre-capture / dropped): 0
|
||||
capture APDUs not in reader log:
|
||||
- proactive/CAT (unlogged by reader SDK): 36
|
||||
- valid exchanges (reader retries/dupes): 12
|
||||
- GARBAGE (mis-framed/desync): 0
|
||||
|
||||
Degradation metrics:
|
||||
- BAD_FCS (desynced by decoder): 0
|
||||
- CONCAT (multi-exchange swallowed in one packet): 0
|
||||
- payload mismatches (LCS-matched, bytes differ): 0
|
||||
- suspicious CLA: 0
|
||||
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_A55_coldboot_new_cable ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -213,7 +216,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_A55_coldboot_new_cable_sim2 ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -240,7 +243,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_S21p_coldboot_new_cable ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -267,27 +270,13 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_S21p_coldboot_new_cable_sim2 ===
|
||||
('=== iso7816 decoder v1.9.1 ===',)
|
||||
('=== iso7816 decoder v1.9.2 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
('RST deasserted (hunt/ATR already in progress)',)
|
||||
('ENDATR', b'3b9f96801fc78031e073fe2117574a3382033339001e')
|
||||
(72333388, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 1, 0, 1, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 1, 0, 1, 0, 0, 0]
|
||||
(72394723, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
(72424969, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||
(72429512, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 1, 1, 1, 1, 0, 1, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 1, 1, 1, 1, 0, 1, 0, 0, 0]
|
||||
(72487445, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
(72631364, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 0, 1, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
|
||||
(98955516, 'CHKSUM ERROR: ', 0, 'bits: ', [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
|
||||
iso7816-1: CHKSUM ERROR bits=[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
CHKSUM ERROR count: 394
|
||||
CHKSUM ERROR count: 0
|
||||
CONCAT APDU count: 0
|
||||
reader exchanges : 0
|
||||
capture ATRs : 1
|
||||
@@ -381,6 +370,7 @@ test_clean_atr_fragment_select (__main__.TestEmbeddedExchanges.test_clean_atr_fr
|
||||
test_clean_channel1_select_after_manage_channel (__main__.TestEmbeddedExchanges.test_clean_channel1_select_after_manage_channel) ... ok
|
||||
test_clean_get_response_fcp (__main__.TestEmbeddedExchanges.test_clean_get_response_fcp) ... ok
|
||||
test_clean_read_record_ff_run (__main__.TestEmbeddedExchanges.test_clean_read_record_ff_run) ... ok
|
||||
test_clean_secured_packet_payload_alcor (__main__.TestEmbeddedExchanges.test_clean_secured_packet_payload_alcor) ... ok
|
||||
test_clean_select_611e (__main__.TestEmbeddedExchanges.test_clean_select_611e) ... ok
|
||||
test_clean_select_6a82 (__main__.TestEmbeddedExchanges.test_clean_select_6a82) ... ok
|
||||
test_concat_packets_are_not_is_desynced (__main__.TestEmbeddedExchanges.test_concat_packets_are_not_is_desynced) ... ok
|
||||
@@ -425,6 +415,6 @@ test_valid_cla_interindustry_classes (__main__.TestValidateT0Apdu.test_valid_cla
|
||||
test_valid_direct_sw_with_p3_nonzero (__main__.TestValidateT0Apdu.test_valid_direct_sw_with_p3_nonzero) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 74 tests in 0.027s
|
||||
Ran 75 tests in 0.024s
|
||||
|
||||
OK
|
||||
@@ -402,6 +402,17 @@ class TestEmbeddedExchanges(unittest.TestCase):
|
||||
# ATR-recovery fragment (header + 3F 00 + 6125) must not hit.
|
||||
self.assertEqual(self._hits('00a40004023f006125'), [])
|
||||
|
||||
def test_clean_secured_packet_payload_alcor(self):
|
||||
# alcor_reader[46] (n=87): GET RESPONSE whose 80-byte payload holds a
|
||||
# secured-packet run "40 70 70 61 15" 21 bytes before an SW-like byte;
|
||||
# the reader follows with 00 c0 00 00 19 -- a clean single exchange.
|
||||
# The structural 0x4x CLA must not turn payload bytes into a hit.
|
||||
h = ('80c2000050d14e82028381060291978b4440048111227ff64070706115'
|
||||
'3500350270000030151601252500000018ed15e8476126aa9cacbbe8f4'
|
||||
'735f962016e3c6e30a26af20c02bad2a34bdf49f152e263bc7390a6119')
|
||||
self.assertTrue(self.pd.validate_t0_apdu(bytes.fromhex(h))[0])
|
||||
self.assertEqual(self._hits(h), [])
|
||||
|
||||
def test_a55_u1_concat(self):
|
||||
# A55[296] (n=70): phantom-00 (buffer {01}) CLA + slip prefix.
|
||||
h = ('000402a43f00612501c0000025c062238202782183023f00a5098001'
|
||||
|
||||
@@ -79,6 +79,13 @@ run_test "xiaomi_mi_a1_coldboot" timeout 300 sigrok-cli -i examples/xiaomi_mi_a1
|
||||
# 4gmodem_coldboot_sample — same pre-power-up capture shape, modem SIM.
|
||||
run_test "4gmodem_coldboot" timeout 300 sigrok-cli -i examples/4gmodem_coldboot_sample.sr -P iso7816:clk=CLK:data=DATA:rst=RST:vcc=VCC:clock_option=native:protocol=T=0:rst_detect=true:vcc_detect=true:gsmtap_enable=false:pcap_file=/tmp/out.pcap -A iso7816
|
||||
|
||||
# alcor_reader_sample — PC/SC reader (Alcor). Regression fixture for the PPS
|
||||
# clock upshift: CLK is 4.0 MHz at the ATR but 4.8 MHz after PPS (FI/DI
|
||||
# 512/32 -> 16 CLK/bit = 3.333 samples/CLK at 16 MHz), so the samples-per-clock
|
||||
# measured at the ATR goes stale; the first post-PPS character must re-measure
|
||||
# it or no post-PPS APDU frames.
|
||||
run_test "alcor_reader_sample" timeout 300 sigrok-cli -i examples/alcor_reader_sample.sr -P iso7816:clk=CLK:data=DATA:rst=RST:vcc=VCC:clock_option=native:rst_detect=true:gsmtap_enable=false:pcap_file=/tmp/out.pcap -A iso7816
|
||||
|
||||
# samsung_A55_coldboot_new_cable — Samsung A55 with the new short cable
|
||||
# (30 mm wires, external pull resistors GND->VCC and VCC->DATA), named
|
||||
# channels DATA/VCC/RST/CLK on bits 0/2/4/5.
|
||||
|
||||
+4
-1
@@ -65,7 +65,10 @@ MAX_TPDU_LEN = 271
|
||||
# data region, followed by its P3 data bytes and an interior status word.
|
||||
# These constants bound detector precision (empirically tuned on all 10
|
||||
# example traces: 0 hits on clean traces, every Samsung/xiaomi family hit).
|
||||
EMBEDDED_CLA = VALID_CLA
|
||||
# Mirror of pd.py: real command classes only -- the structural 0x4x/0x5x
|
||||
# forms that _plausible_cla() tolerates only add payload false hits.
|
||||
EMBEDDED_CLA = frozenset(
|
||||
cla for cla in VALID_CLA if (cla & 0xF0) not in (0x40, 0x50))
|
||||
# Commands that actually occur on these cards' bus (T=0 + STK/CAT).
|
||||
EMBEDDED_INS = frozenset((
|
||||
0x12, 0x14, 0x20, 0x44, 0x70, 0x88,
|
||||
|
||||
Reference in new issue
Block a user