fix: late-PoR handler on every path, one poll per packet, LOAD-step components (v3.6.37)
_send_envelope defined its SEND SHORT MESSAGE capture handler inside the 91xx branch, but the 'STATUS poll (PoR not captured)' path used it: a packet whose ENVELOPE answered 9000 and whose late PoR arrived on the STATUS poll raised UnboundLocalError, returned 500 and aborted the RAM operation mid-packet with the card's command left pending (live 2026-09-29, 120-byte LOAD blocks). The handler is now defined for every path, and the late-PoR poll runs only after a packet's last segment (poll_status from _send_secured_packet) - a concatenated download makes no spurious mid-packet STATUS exchanges. The PWA annotates every LOAD step with the CAP components its block covers (ramStepComponents: 'completes Import' / 'inside Method' / 'Header...Import') and ramInstallFailHint names the component the failing block completed - the live import-verification rejection lands in the block completing Import (6438/6985 at both 240 B and 64 B blocks). sw cache simple-v310; tests: 509 Python / 672 Node.
This commit is contained in:
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.6.36'
|
||||
VERSION = '3.6.37'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -870,7 +870,7 @@ def _build_sms_tpdu(chunk_hex, chunk_total=1, chunk_num=1, oa_number='12345', in
|
||||
|
||||
|
||||
def _send_envelope(tpdu_hex, scc, sm_sc='12345678912', submit_handler=None,
|
||||
handle_proactive=True):
|
||||
handle_proactive=True, poll_status=True):
|
||||
from pySim.ts_31_102 import SMSPPDownload
|
||||
from pySim.cat import DeviceIdentities, Address
|
||||
from osmocom.tlv import COMPR_TLV_IE
|
||||
@@ -884,6 +884,30 @@ def _send_envelope(tpdu_hex, scc, sm_sc='12345678912', submit_handler=None,
|
||||
def to_bytes(self, context={}):
|
||||
return self._raw
|
||||
|
||||
# Handler for a proactive chain that carries the PoR: the ENVELOPE may
|
||||
# answer 9000 and the card delivers SEND SHORT MESSAGE on a later exchange
|
||||
# (the STATUS poll below). v3.6.37: this used to be defined inside the
|
||||
# 91xx branch, so the poll path raised UnboundLocalError and the RAM
|
||||
# operation died mid-packet with the command left pending (live 500).
|
||||
def _capture_sms_tpdu(raw, cmd_num, cmd_type, dev_src, dev_dst):
|
||||
if submit_handler:
|
||||
tpdu_hex = _find_sms_tpdu(raw)
|
||||
if tpdu_hex:
|
||||
ref, total, num, payload = _parse_sms_concat(bytes.fromhex(tpdu_hex))
|
||||
if total is not None and num is not None:
|
||||
submit_handler.sms_segments.append((ref, total, num, payload.hex()))
|
||||
matching = [s for s in submit_handler.sms_segments if s[0] == ref]
|
||||
if len(matching) >= total:
|
||||
sorted_segs = sorted(matching, key=lambda s: s[2])
|
||||
assembled = b''.join(bytes.fromhex(s[3]) for s in sorted_segs)
|
||||
submit_handler.submit_ud_hex = assembled.hex()
|
||||
submit_handler.submit_tpdu_hex = submit_handler.submit_tpdu_hex or tpdu_hex
|
||||
sys.stderr.write('SMS concat: assembled %d segments (ref=%s, %d B)\n' % (
|
||||
total, ref, len(assembled)))
|
||||
else:
|
||||
submit_handler.submit_ud_hex = payload.hex()
|
||||
submit_handler.submit_tpdu_hex = tpdu_hex
|
||||
|
||||
address = Address()
|
||||
oa_raw = _encode_sms_oa(sm_sc)
|
||||
address.from_bytes(oa_raw[1:])
|
||||
@@ -897,27 +921,12 @@ def _send_envelope(tpdu_hex, scc, sm_sc='12345678912', submit_handler=None,
|
||||
get_len = int(sw[2:], 16) if len(sw) == 4 else 0x100
|
||||
data, sw = scc._tp.send_apdu('00c00000%02x' % get_len)
|
||||
elif handle_proactive and sw.startswith('91'):
|
||||
def _capture_sms_tpdu(raw, cmd_num, cmd_type, dev_src, dev_dst):
|
||||
if submit_handler:
|
||||
tpdu_hex = _find_sms_tpdu(raw)
|
||||
if tpdu_hex:
|
||||
ref, total, num, payload = _parse_sms_concat(bytes.fromhex(tpdu_hex))
|
||||
if total is not None and num is not None:
|
||||
submit_handler.sms_segments.append((ref, total, num, payload.hex()))
|
||||
matching = [s for s in submit_handler.sms_segments if s[0] == ref]
|
||||
if len(matching) >= total:
|
||||
sorted_segs = sorted(matching, key=lambda s: s[2])
|
||||
assembled = b''.join(bytes.fromhex(s[3]) for s in sorted_segs)
|
||||
submit_handler.submit_ud_hex = assembled.hex()
|
||||
submit_handler.submit_tpdu_hex = submit_handler.submit_tpdu_hex or tpdu_hex
|
||||
sys.stderr.write('SMS concat: assembled %d segments (ref=%s, %d B)\n' % (
|
||||
total, ref, len(assembled)))
|
||||
else:
|
||||
submit_handler.submit_ud_hex = payload.hex()
|
||||
submit_handler.submit_tpdu_hex = tpdu_hex
|
||||
_handle_proactive_chain(scc, sw, _capture_sms_tpdu)
|
||||
data, sw = '', '9000'
|
||||
if (handle_proactive and sw == '9000' and submit_handler
|
||||
# Late-PoR fallback: only after the last segment of a packet (a
|
||||
# concatenated download cannot have produced its PoR before the card got
|
||||
# every part, and polling mid-packet made spurious STATUS exchanges).
|
||||
if (handle_proactive and poll_status and sw == '9000' and submit_handler
|
||||
and not submit_handler.submit_tpdu_hex and not data):
|
||||
sys.stderr.write('STATUS poll (PoR not captured)\n')
|
||||
st_data, st_sw = _send_status(scc)
|
||||
@@ -1115,7 +1124,8 @@ def _send_secured_packet(scc, sp_hex, oa_number, sm_sc=None, include_cpi=True,
|
||||
i + 1, total, len(part), ' + CPI' if i == 0 and include_cpi else ''))
|
||||
data, sw = _send_envelope(tpdu, scc, sm_sc=sm_sc or '12345678912',
|
||||
submit_handler=submit_handler,
|
||||
handle_proactive=handle_proactive)
|
||||
handle_proactive=handle_proactive,
|
||||
poll_status=(i == total - 1))
|
||||
if sw != '9000' and not sw.startswith('91'):
|
||||
return {'success': False, 'sw': sw, 'bytes': len(pkt),
|
||||
'segments': total,
|
||||
|
||||
Reference in New Issue
Block a user