From e6ff3a2e143140618b3864a59091ba265795d011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 11 Sep 2026 01:21:02 +0300 Subject: [PATCH] Fix pcap timestamp wrap, ATR-hunt EOF crash, start.bat protocol; tighten launchers + vs_reader - pd.py ts_from_samplenum: drop c_uint32 truncation + dead sampleOverflowCount (pcap timestamps wrapped backwards past ~4.5 min at 16 MHz). - pd.py handle_atr: guard None reads at EOF (was TypeError (None & 0x80) on a capture truncated mid-ATR-hunt). - start.bat: add --protocol (+ :protocol=T=0 default) to match start.sh's mid-session decode; correct the 'flag-for-flag' header comment. - start.sh: reject --rst/--vcc assigned to the same pin (was unchecked). - setup.bat: set DEVICE_MSG on blocked/skipped probe so the trailing 'unknown' message is suppressed. - vs_reader.py: RESULT now fails on CONCAT / payload mismatch / suspicious CLA, not just GARBAGE (OK text unchanged; clean traces stay identical). --- pd.py | 17 ++++++++++++----- setup.bat | 2 ++ start.bat | 12 +++++++++++- start.sh | 1 + tools/vs_reader.py | 26 ++++++++++++++++++++------ 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/pd.py b/pd.py index 4ebe691..d069444 100644 --- a/pd.py +++ b/pd.py @@ -20,7 +20,6 @@ import struct import sys import time import codecs -import ctypes import traceback from collections import deque @@ -450,7 +449,6 @@ class Decoder(srd.Decoder): # card was already in reset" indistinguishable from a live session. self._primed = False self.lastSamplePositive = True - self.sampleOverflowCount = 0 # Rolling parity-error monitor: warns when the wire degrades # (e.g. GSM TX bursts coupling into the probe wiring). self.parity_hist = deque(maxlen=64) @@ -755,9 +753,12 @@ class Decoder(srd.Decoder): self.wrote_pcap_header = True def ts_from_samplenum(self, sample): - x = ctypes.c_uint32(sample).value; - ovrflow = ctypes.c_uint32(int(self.sampleOverflowCount * 0xFFFFFFFF * self.secs_per_sample)).value - ts = float(x) * self.secs_per_sample + ovrflow + self._pcap_base_time + # Use the full sample number. The previous c_uint32 truncation (plus + # an overflow counter that was never incremented) made pcap timestamps + # wrap and go backwards once a capture passed 2^32 samples (~4.5 min + # at 16 MHz). float64 holds sample numbers well past 2^53, so the + # whole sample domain is representable. + ts = float(sample) * self.secs_per_sample + self._pcap_base_time return (int(ts), int((ts % 1.0) * 1e6)) def signal_quality(self, err): @@ -1556,6 +1557,10 @@ class Decoder(srd.Decoder): self.put(atr_start, self.samplenum, self.out_ann, [Ann.ANN_WARN, ["Invalid TS byte 0x{byte:02x} - skipped".format(byte=byte)]]) byte = self.read_byte() + if byte is None: + # End of capture mid-hunt: stop cleanly instead of raising + # TypeError (None & 0x80) in the next loop iteration. + return atr_start = self.ss ts_attempts += 1 if (byte not in (0x3b, 0x3f)): @@ -1599,6 +1604,8 @@ class Decoder(srd.Decoder): ts=byte, conv="inverse" if self._inverse_convention else "direct")) t0 = self.read_byte() + if t0 is None: + return self.ATR.append(t0) # Lock ETU from the first two ATR bytes (TS + T0). The ATR is sent # at the default ETU, so its edge spacings are the authoritative diff --git a/setup.bat b/setup.bat index c42a0a0..75743cc 100644 --- a/setup.bat +++ b/setup.bat @@ -106,10 +106,12 @@ for %%r in (1M 2M 4M 8M 12M 16M 20M 24M) do ( -P "iso7816:clk=D0:data=D1" >"%TEMP%\iso7816-probe.txt" 2>&1 type "%TEMP%\iso7816-probe.txt" | findstr /c:"Unable to claim" >nul && ( echo blocked - another program holds the device + set "DEVICE_MSG=1" goto :probedone ) type "%TEMP%\iso7816-probe.txt" | findstr /c:"Failed to open" /c:"No devices found" >nul && ( echo skipped - no FX2 device connected + set "DEVICE_MSG=1" goto :probedone ) type "%TEMP%\iso7816-probe.txt" | findstr /r /c:"invalid argument" /c:"Unable to sample" /c:"Could not start" >nul || set MAX_RATE=%%r diff --git a/start.bat b/start.bat index d065249..3f2df69 100644 --- a/start.bat +++ b/start.bat @@ -1,6 +1,7 @@ @echo off rem sigrok_iso7816_stream launcher (Windows): capture ISO 7816 and stream -rem GSMTAP to :4729. Flag-for-flag equivalent of start.sh. +rem GSMTAP to :4729. Mirrors the core start.sh flags (channel/samplerate/ +rem clock/gsmtap/pcap/protocol/debug); start.sh has the full flag set. setlocal EnableExtensions EnableDelayedExpansion @@ -15,6 +16,7 @@ set "PORT=4729" set "PCAP=" set "DEBUG=" set "ANNOT=iso7816=apdus,iso7816=warnings" +set "PROTOCOL=T=0" :argloop if "%~1"=="" goto :argsdone @@ -28,6 +30,7 @@ if /i "%~1"=="--host" ( set "HOST=%~2" & shift & shift & goto :arg if /i "%~1"=="--port" ( set "PORT=%~2" & shift & shift & goto :argloop ) if /i "%~1"=="--pcap" ( set "PCAP=%~2" & shift & shift & goto :argloop ) if /i "%~1"=="--debug" ( set "DEBUG=1" & set "ANNOT=iso7816" & shift & goto :argloop ) +if /i "%~1"=="--protocol" ( set "PROTOCOL=%~2" & shift & shift & goto :argloop ) if /i "%~1"=="--no-rst" ( set "RST=" & shift & goto :argloop ) if /i "%~1"=="--no-vcc" ( set "VCC=" & shift & goto :argloop ) if /i "%~1"=="-h" ( goto :usage ) @@ -49,6 +52,7 @@ echo --pcap=FILE Also write decoded events to FILE (no spaces in path) echo --debug Full decoder + sigrok output (all annotation rows + -l 4). echo echo By default only decoded APDUs and decoder warnings/errors are shown. +echo --protocol=MODE T=0 (default), T=1, or auto (mid-session decode) echo --no-rst Disable RST tracking echo --no-vcc Disable VCC tracking echo. @@ -82,11 +86,17 @@ exit /b 1 :found +rem Normalize --protocol to the decoder's accepted form (T=0 / T=1 / auto). +if /i "%PROTOCOL%"=="T0" set "PROTOCOL=T=0" +if /i "%PROTOCOL%"=="T1" set "PROTOCOL=T=1" +if /i "%PROTOCOL%"=="AUTO" set "PROTOCOL=auto" + set "OPTS=clk=%CLK%:data=%DATA%:clock_option=%CLOCK%" set "OPTS=%OPTS%:gsmtap_host=%HOST%:gsmtap_port=%PORT%" if defined RST set "OPTS=%OPTS%:rst=%RST%:rst_detect=true" if defined VCC set "OPTS=%OPTS%:vcc=%VCC%:vcc_detect=true" if defined PCAP set "OPTS=%OPTS%:pcap_file=%PCAP%" +set "OPTS=%OPTS%:protocol=%PROTOCOL%" set "CMDLINE=sigrok-cli -d fx2lafw --config samplerate=%SAMPLERATE% --continuous -C D0,D1,D2,D3,D4,D5,D6,D7" if defined DEBUG set "CMDLINE=%CMDLINE% -l 4" diff --git a/start.sh b/start.sh index 12872dc..64daf9a 100755 --- a/start.sh +++ b/start.sh @@ -189,6 +189,7 @@ check_dup clk "${CLK}" rst "${RST}" check_dup clk "${CLK}" vcc "${VCC}" check_dup data "${DATA}" rst "${RST}" check_dup data "${DATA}" vcc "${VCC}" +check_dup rst "${RST}" vcc "${VCC}" echo "Channels: clk=${CLK} data=${DATA}${RST:+ rst=${RST}}${VCC:+ vcc=${VCC}}" diff --git a/tools/vs_reader.py b/tools/vs_reader.py index ccbdcfe..d40644d 100644 --- a/tools/vs_reader.py +++ b/tools/vs_reader.py @@ -380,12 +380,26 @@ def main(): print('\nWARNING: capture contains 0 APDUs but reader log has %d exchange(s).' % len(reader)) - ok = (len(pcap_garbage) == 0 and not no_apdu_failure) - print('\nRESULT: %s' % ('OK - capture fully explained (reader + CAT + valid retries)' - if ok else - 'UNCLEAN - %d mis-framed capture APDU(s)%s' - % (len(pcap_garbage), - ', 0 capture APDUs' if no_apdu_failure else ''))) + ok = (len(pcap_garbage) == 0 + and len(concat_indices) == 0 + and len(payload_mismatches) == 0 + and len(suspicious_cla) == 0 + and not no_apdu_failure) + if ok: + print('\nRESULT: OK - capture fully explained (reader + CAT + valid retries)') + else: + problems = [] + if pcap_garbage: + problems.append('%d garbage APDU(s)' % len(pcap_garbage)) + if concat_indices: + problems.append('%d CONCAT packet(s)' % len(concat_indices)) + if payload_mismatches: + problems.append('%d payload mismatch(es)' % len(payload_mismatches)) + if suspicious_cla: + problems.append('%d suspicious CLA' % len(suspicious_cla)) + if no_apdu_failure: + problems.append('0 capture APDUs') + print('\nRESULT: UNCLEAN - %s' % ', '.join(problems)) sys.exit(0 if ok else 1)