v1.9.5: T=0 framing — immediate SW, short responses, Le=256
- Decode the first post-header byte as SW1 before the ACK/data decision: a case-2 rejection (6A82/6CXX) carries no ACK and used to be read as command data, silently consuming the exchange and the next one. - Short responses (Lr < Le): after the P3-byte read, split the buffer at the first plausible SW1 followed by a plausible CLA, replay the tail and flag BAD_FCS; previously the SW scan discarded the real SW, merged the following exchanges and lost the exchange at EOF. - P3=0 is Le=256 for an outgoing command: keep the response data (read until SW) instead of discarding it, including data bytes equal to INS. - ~INS ACK: one data byte per ACK via the new helpers; case-4 inline data is appended (permissive; ISO 7816-3 §12.2 maps case 4 to GET RESPONSE). - tests: four TestDecodeStep vectors (immediate SW, short read + split + replay, Le=256 data incl. an INS-valued byte); 99/99. - AGENTS v1.9.5 + T=0 section, README/README_RUS bullets; baseline regenerated (xiaomi_mi_a1_coldboot 473 -> 475 APDUs, all else identical); compare_baseline: IDENTICAL.
This commit is contained in:
1 parent
883033e712
commit
b574f900a1
6 files changed
+294
-98
No files matched your search
+18
-14
@@ -1,7 +1,7 @@
|
||||
=== Baseline captured 2026-10-18T02:13:20+03:00 ===
|
||||
=== Baseline captured 2026-10-20T16:56:58+03:00 ===
|
||||
|
||||
=== test_8 ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('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.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('etu recovered (samples):', 1236)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -77,7 +77,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== xiaomi_phone_sample ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('etu recovered (samples):', 53)
|
||||
(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]
|
||||
@@ -102,7 +102,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== xiaomi_mi_a1_coldboot ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -112,12 +112,12 @@ CHKSUM ERROR count: 0
|
||||
CONCAT APDU count: 0
|
||||
reader exchanges : 0
|
||||
capture ATRs : 1
|
||||
capture APDUs : 473
|
||||
capture APDUs : 475
|
||||
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): 10
|
||||
- valid exchanges (reader retries/dupes): 463
|
||||
- valid exchanges (reader retries/dupes): 465
|
||||
- GARBAGE (mis-framed/desync): 0
|
||||
|
||||
Degradation metrics:
|
||||
@@ -129,7 +129,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== 4gmodem_coldboot ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -156,7 +156,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== alcor_reader_sample ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -189,7 +189,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_A55_coldboot_new_cable ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -216,7 +216,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_A55_coldboot_new_cable_sim2 ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -243,7 +243,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_S21p_coldboot_new_cable ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -270,7 +270,7 @@ Degradation metrics:
|
||||
RESULT: OK - capture fully explained (reader + CAT + valid retries)
|
||||
|
||||
=== samsung_S21p_coldboot_new_cable_sim2 ===
|
||||
('=== iso7816 decoder v1.9.4 ===',)
|
||||
('=== iso7816 decoder v1.9.5 ===',)
|
||||
('RST low at start: expecting cold-boot ATR',)
|
||||
iso7816-1: RST deasserted
|
||||
('RST deasserted',)
|
||||
@@ -359,7 +359,11 @@ test_bit_dense_valid_cla_is_not_idle_noise (__main__.TestDecodeStep.test_bit_den
|
||||
test_ex_class_exchange_frames (__main__.TestDecodeStep.test_ex_class_exchange_frames) ... ok
|
||||
test_extended_channel_exchange_frames (__main__.TestDecodeStep.test_extended_channel_exchange_frames) ... ok
|
||||
test_idle_noise_still_discarded (__main__.TestDecodeStep.test_idle_noise_still_discarded) ... ok
|
||||
test_immediate_sw_rejection_frames (__main__.TestDecodeStep.test_immediate_sw_rejection_frames) ... ok
|
||||
test_invalid_ins_re_frame_recovers_command (__main__.TestDecodeStep.test_invalid_ins_re_frame_recovers_command) ... ok
|
||||
test_le_256_response_data_is_kept (__main__.TestDecodeStep.test_le_256_response_data_is_kept) ... ok
|
||||
test_short_response_is_split_and_flagged (__main__.TestDecodeStep.test_short_response_is_split_and_flagged) ... ok
|
||||
test_short_response_replays_following_exchange (__main__.TestDecodeStep.test_short_response_replays_following_exchange) ... ok
|
||||
test_text_byte_boundary_is_discarded_not_framed (__main__.TestDecodeStep.test_text_byte_boundary_is_discarded_not_framed) ... ok
|
||||
test_glitch_low_pulse_removed (__main__.TestDeglitchEdges.test_glitch_low_pulse_removed) ... ok
|
||||
test_no_edges (__main__.TestDeglitchEdges.test_no_edges) ... ok
|
||||
@@ -435,6 +439,6 @@ test_valid_ins_odd_variants (__main__.TestValidateT0Apdu.test_valid_ins_odd_vari
|
||||
test_vs_reader_mirror (__main__.TestValidateT0Apdu.test_vs_reader_mirror) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 95 tests in 0.028s
|
||||
Ran 99 tests in 0.032s
|
||||
|
||||
OK
|
||||
@@ -880,6 +880,52 @@ class TestDecodeStep(unittest.TestCase):
|
||||
self.assertEqual(inst.discarded[0][0], 0xfe)
|
||||
self.assertIn('idle-line noise', inst.discarded[0][1])
|
||||
|
||||
def test_immediate_sw_rejection_frames(self):
|
||||
# A command rejected with SW1 SW2 instead of an ACK (ISO 7816-3
|
||||
# §10.3.3) must not be read as command data; the exchange and the
|
||||
# following one must frame separately.
|
||||
inst = self._make(bytes.fromhex('00b00000106a82' +
|
||||
'00a40004009000'))
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted,
|
||||
[(bytes.fromhex('00b00000106a82'), 0)])
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted[1],
|
||||
(bytes.fromhex('00a40004009000'), 0))
|
||||
|
||||
def test_short_response_is_split_and_flagged(self):
|
||||
# Lr < Le (6282 end-of-file): the SW inside the P3-byte read ends
|
||||
# the response, and the packet is flagged BAD_FCS (it used to be a
|
||||
# silent total loss).
|
||||
inst = self._make(bytes.fromhex('00b0000010b012349000'))
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted,
|
||||
[(bytes.fromhex('00b000001012349000'),
|
||||
self.gs.GSMTAP_FLAG_BAD_FCS)])
|
||||
|
||||
def test_short_response_replays_following_exchange(self):
|
||||
# A short response followed by another command: the split must
|
||||
# replay the tail so the next exchange is framed intact.
|
||||
inst = self._make(bytes.fromhex('00b0000010b012349000' +
|
||||
'00a40004009000'))
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted[0],
|
||||
(bytes.fromhex('00b000001012349000'),
|
||||
self.gs.GSMTAP_FLAG_BAD_FCS))
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted[1],
|
||||
(bytes.fromhex('00a40004009000'), 0))
|
||||
|
||||
def test_le_256_response_data_is_kept(self):
|
||||
# P3=0 in an outgoing command is Le=256 (ISO 7816-3 §10.3.2): the
|
||||
# data after the ACK must be appended, not discarded — including a
|
||||
# data byte that happens to equal INS.
|
||||
payload = 'a5' * 8 + 'b0' + 'a5' * 7
|
||||
inst = self._make(bytes.fromhex('00b0000000b0' + payload + '9000'))
|
||||
self.assertTrue(inst.decode_step())
|
||||
self.assertEqual(inst.emitted,
|
||||
[(bytes.fromhex('00b0000000' + payload + '9000'), 0)])
|
||||
|
||||
|
||||
class TestAtrHuntDetect(unittest.TestCase):
|
||||
"""The ATR-hunt command-detect handoff in handle_atr(), driven by a
|
||||
|
||||
Reference in new issue
Block a user