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
@@ -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