cards: read EF.ICCID at equip and auto-select the matching preset (v2.2.16)
- Server: _decode_iccid (nibble-swapped E.118 digits, trailing-F pad) and _read_iccid (best-effort MF/2FE2 read through the parent-scoped select helper; the previous selection is restored, the read never raises). The equip path records the digit string before the TERMINAL PROFILE, i.e. before any CAT session is active; a startup with a card does the same. The value is cleared on card removal and exposed as /api/status 'iccid' (only while connected). - PWA: when a connected status update reports a *new* ICCID, the matching card preset is selected in both SCP80 views - Secured Packet (sp-card-sel + form fill) and RAM (ram-card-sel + _ramCardIdx). Matching normalizes digits and accepts the raw EF hex form, leading zeros ignored; a manual choice for the same card is kept until the next equip, and a card removal re-arms the auto-selection. The status line shows the ICCID. - Tests: tests/test_iccid.py (decode variants, model + probe read, equip recording, disconnect clearing) and frontend/tests/cards_iccid.test.js (normalize, find, select, no-override, card swap); the card_state test harness stubs the new hook and covers the guard reset. - Docs: api.md /api/status fields, help EN+RU (SCP80 intro + Cards tab), READMEs, AGENTS. SW cache otaman-v183.
This commit is contained in:
@@ -12,7 +12,7 @@ from pySim.cards import UiccCardBase
|
||||
|
||||
from .shell import load_pysim_app
|
||||
from . import fastinit
|
||||
from .server import PysimHandler, StderrApduTracer, _LoggingApduTracer, VERSION, _send_terminal_profile, _DefaultProactiveHandler, _handle_proactive_chain, _send_status, _init_proactive_session, _timing_on, _tlog, _set_menu_timeout, start_card_monitor, set_auto_equip
|
||||
from .server import PysimHandler, StderrApduTracer, _LoggingApduTracer, VERSION, _send_terminal_profile, _DefaultProactiveHandler, _handle_proactive_chain, _send_status, _init_proactive_session, _timing_on, _tlog, _set_menu_timeout, start_card_monitor, set_auto_equip, _read_iccid
|
||||
|
||||
|
||||
_server_start = 0
|
||||
@@ -128,10 +128,16 @@ def main():
|
||||
_tlog('pysim_app: %.0fms' % ((time.time() - t_phase) * 1000))
|
||||
if app is not None and opts.fast_init:
|
||||
fastinit.install(app)
|
||||
iccid = None
|
||||
if scc and card is not None and hasattr(scc, '_tp'):
|
||||
scc._tp.apdu_tracer = _LoggingApduTracer()
|
||||
try:
|
||||
_init_proactive_session()
|
||||
# Read EF.ICCID before the TERMINAL PROFILE starts the CAT session
|
||||
# (the PWA auto-selects the matching card preset from it).
|
||||
iccid = _read_iccid(app)
|
||||
if iccid:
|
||||
sys.stderr.write('INIT: ICCID %s\n' % iccid)
|
||||
t_phase = time.time()
|
||||
sys.stderr.write('INIT: sending TERMINAL PROFILE %s (CLA=%s)\n' % (opts.terminal_profile, scc.cat_cla))
|
||||
sm, el = _send_terminal_profile(scc, opts.terminal_profile)
|
||||
@@ -187,6 +193,7 @@ def main():
|
||||
server.stk_pending = None
|
||||
server.card_present = card is not None
|
||||
server.card_session = 1 if card is not None else 0
|
||||
server.iccid = iccid
|
||||
server.equipping = False
|
||||
# Set server reference for polling timer and mark the card session state
|
||||
import pysim_otaman_server.server
|
||||
|
||||
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
||||
from osmocom.tlv import BER_TLV_IE
|
||||
|
||||
|
||||
VERSION = '2.2.15'
|
||||
VERSION = '2.2.16'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -355,6 +355,41 @@ def _select_path(lchan, path, app):
|
||||
return _select_with_parent(lchan, parts[-1], None, app, parent_path=parts[:-1], allow_probe=True)
|
||||
|
||||
|
||||
def _decode_iccid(data_hex):
|
||||
"""Decode EF.ICCID content: nibble-swapped E.118 digits with an optional
|
||||
trailing 'F' pad (TS 102 221 13.2 / TS 151 011 10.2). Returns the digit
|
||||
string, or None when the bytes are not a plausible ICCID."""
|
||||
h = re.sub(r'[^0-9a-fA-F]', '', data_hex or '').upper()
|
||||
if len(h) < 2 or len(h) % 2:
|
||||
return None
|
||||
digits = ''.join(h[i + 1] + h[i] for i in range(0, len(h), 2))
|
||||
digits = re.sub(r'F+$', '', digits)
|
||||
if not digits or not digits.isdigit():
|
||||
return None
|
||||
return digits
|
||||
|
||||
|
||||
def _read_iccid(app):
|
||||
"""Best-effort EF.ICCID (MF/2FE2) read: the E.118 digit string or None.
|
||||
|
||||
EF.ICCID is a mandatory transparent EF, but a card may protect it or the
|
||||
generic profile may lack it, so the read is optional and never raises.
|
||||
The previous selection is restored by the _select_path cleanup."""
|
||||
if not app or not getattr(app, 'rs', None):
|
||||
return None
|
||||
lchan = app.rs.lchan[0]
|
||||
cleanup = None
|
||||
try:
|
||||
_, cleanup = _select_path(lchan, 'MF/2FE2', app)
|
||||
data, _sw = lchan.read_binary()
|
||||
return _decode_iccid(data)
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if cleanup:
|
||||
cleanup()
|
||||
|
||||
|
||||
def _parse_tree_output(output):
|
||||
lines = (output or '').split('\n')
|
||||
children = []
|
||||
@@ -2213,6 +2248,7 @@ def _handle_card_disconnect():
|
||||
_server_ref.menu_active = False
|
||||
_server_ref.event_list = None
|
||||
_server_ref.sim_menu = None
|
||||
_server_ref.iccid = None
|
||||
_server_ref.equipping = False
|
||||
_server_ref.card_session = getattr(_server_ref, 'card_session', 0) + 1
|
||||
_reset_proactive_log()
|
||||
@@ -2234,6 +2270,14 @@ def _apply_equipped_card(server):
|
||||
_CARD_CONNECTED = True
|
||||
server.card_present = True
|
||||
server.card_session = getattr(server, 'card_session', 0) + 1
|
||||
server.iccid = None
|
||||
# Read the ICCID before the TERMINAL PROFILE starts a CAT session: the
|
||||
# PWA auto-selects the matching card preset (SCP80 views) from it.
|
||||
server.iccid = _read_iccid(server.app)
|
||||
if server.iccid:
|
||||
_tlog('equip: ICCID %s' % server.iccid)
|
||||
else:
|
||||
_tlog('equip: ICCID not readable')
|
||||
_poll_enable()
|
||||
sm, el = _send_terminal_profile(server.scc, server.terminal_profile)
|
||||
server.sim_menu = sm
|
||||
@@ -3073,6 +3117,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
'auto_equip': bool(_AUTO_EQUIP),
|
||||
'card': card.name if card else None,
|
||||
'profile': str(rs.profile) if rs and rs.profile else None,
|
||||
'iccid': getattr(self.server, 'iccid', None) if connected else None,
|
||||
'app_ready': app is not None,
|
||||
'adm_verified': rs.adm_verified if rs else False,
|
||||
'atr': rs.identity.get('ATR') if rs and rs.identity else None,
|
||||
|
||||
Reference in New Issue
Block a user