v1.7.0: spurious end-of-decode hardening + session-resilient start.sh
The decoder could end decode() early on an idle/busy live line; the frontend symptom was a flood of "sr_session_stop: session was NULL" after the session stopped. Harden the EOF detection and stop the capture from dropping. pd.py: - _at_eof(): a step that consumes only queued bytes (_replay / peeked_byte, e.g. the invalid-INS re-frame) no longer counts as end-of-capture; bounded to 64 consecutive no-advance steps. - _stall_check(track=False): the idle-LEVEL wait in wait_data_falling returns immediately at the same sample on an idle line -- not EOF. Only "pins is None" ends the wait there; sample-stall detection is kept for the edge wait. - resync_idle(): guard wait()==None (was an unguarded pins[...] TypeError at stream end). Effect: samsung_phone_sample.sr had halted at ~21.9M/320M samples with 1 APDU; it now decodes through ~293M to a true end (939 APDUs, CHKSUM 176). The 6 CONCAT packets are real mis-framed STK bursts now reachable (known Samsung de-concatenation family). All other traces unchanged. start.sh: - Auto-restart is now the default (--no-loop disables) so an intermittent FX2 USB stop reconnects instead of ending the session. - Per-session pcap suffix (FILE.N.ext) so a restart never overwrites the previous capture. - Filter the repetitive "sr_session_stop: session was NULL" teardown line from stderr (real decoder errors still pass). Validation: test_8 56/0/OK, test_7 65/0/OK, xiaomi_phone 640, xiaomi_cold 473, 4gmodem 327, A55 407/395, S21p 399/375, target S21p 369; 0 CONCAT; 65/65 unit tests (10 new). VERSION -> 1.7.0.
This commit is contained in:
1 parent
bca1b85b94
commit
690cc132ca
5 files changed
+205
-23
No files matched your search
@@ -6,6 +6,7 @@ the real pd.py / gsmtap_stream.py sources under the `iso7816` package
|
||||
name (mirroring how libsigrokdecode loads a decoder directory).
|
||||
"""
|
||||
|
||||
import collections
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
@@ -447,5 +448,87 @@ class TestEmbeddedExchanges(unittest.TestCase):
|
||||
self.assertFalse(self.pd.is_desynced(bytes.fromhex(a55u1)))
|
||||
|
||||
|
||||
class TestStallCheck(unittest.TestCase):
|
||||
"""_stall_check distinguishes true EOF (None pins) from a live line that
|
||||
is merely idle (a level wait returns immediately at the same sample)."""
|
||||
|
||||
def setUp(self):
|
||||
_, pd = _load_iso7816()
|
||||
self.inst = object.__new__(pd.Decoder)
|
||||
self.inst.samplenum = 100
|
||||
self.inst._prev_wait_sn = None
|
||||
self.inst._stall = 0
|
||||
|
||||
def test_none_pins_is_eof(self):
|
||||
self.assertTrue(self.inst._stall_check(None))
|
||||
|
||||
def test_advancing_sample_is_not_stall(self):
|
||||
for sn in range(100, 110):
|
||||
self.inst.samplenum = sn
|
||||
self.assertFalse(self.inst._stall_check([1]))
|
||||
|
||||
def test_repeated_same_sample_is_bounded_stall(self):
|
||||
results = []
|
||||
for _ in range(10):
|
||||
self.inst.samplenum = 100
|
||||
results.append(self.inst._stall_check([1]))
|
||||
self.assertFalse(results[0])
|
||||
self.assertIn(True, results)
|
||||
|
||||
def test_untracked_level_wait_never_stalls(self):
|
||||
# An idle-level wait repeats at the same sample on a live idle line;
|
||||
# with track=False it must not be flagged as EOF.
|
||||
for _ in range(10):
|
||||
self.inst.samplenum = 100
|
||||
self.assertFalse(self.inst._stall_check([1], track=False))
|
||||
self.assertEqual(self.inst._stall, 0)
|
||||
self.assertIsNone(self.inst._prev_wait_sn)
|
||||
|
||||
def test_untracked_resets_stall_window(self):
|
||||
for _ in range(6):
|
||||
self.inst.samplenum = 100
|
||||
self.inst._stall_check([1]) # would trip the tracked stall
|
||||
self.assertTrue(self.inst._stall > 4)
|
||||
self.inst._stall_check([1], track=False)
|
||||
self.assertFalse(self.inst._stall_check([1])) # window reset
|
||||
|
||||
|
||||
class TestAtEof(unittest.TestCase):
|
||||
"""decode_step must not treat a replay-only step (no sample progress) as
|
||||
end-of-capture, but must still terminate a genuinely stuck stream."""
|
||||
|
||||
def setUp(self):
|
||||
_, pd = _load_iso7816()
|
||||
self.inst = object.__new__(pd.Decoder)
|
||||
self.inst.samplenum = 500
|
||||
self.inst._last_sn = 500
|
||||
self.inst._no_advance = 0
|
||||
self.inst._eof = False
|
||||
self.inst._replay = collections.deque()
|
||||
self.inst.peeked_byte = None
|
||||
|
||||
def test_advancing_is_not_eof(self):
|
||||
self.inst.samplenum = 501
|
||||
self.assertFalse(self.inst._at_eof())
|
||||
|
||||
def test_stuck_with_nothing_queued_is_eof(self):
|
||||
self.assertTrue(self.inst._at_eof())
|
||||
|
||||
def test_pending_replay_is_not_eof(self):
|
||||
self.inst._replay.append(0x00)
|
||||
self.assertFalse(self.inst._at_eof())
|
||||
|
||||
def test_pending_replay_is_bounded(self):
|
||||
self.inst._replay.append(0x00)
|
||||
for _ in range(64):
|
||||
self.assertFalse(self.inst._at_eof())
|
||||
self.assertTrue(self.inst._at_eof())
|
||||
|
||||
def test_explicit_eof_wins(self):
|
||||
self.inst._eof = True
|
||||
self.inst.samplenum = 600
|
||||
self.assertTrue(self.inst._at_eof())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user