feat: CAP compatibility test in the popup + per-AID failure analysis (v3.6.52)

The compatibility test now runs in the same operation modal as the installs
(live progress from the server's ram_progress, Close disabled until done) and
its result gains a per-AID detail block instead of the comma list:

- the probe summary (probe: 2 imports -> 0.0 . +1 appended);
- the import list as tested (ramCompatImportRows): the CAP's imports with the
  version the card was asked for and the CAP's own version when the probe
  lowered it, appended synthetic entries and unmatched lines - each with the
  resolved library name (jcAidName), so a raw AID list becomes readable rows;
- on a rejection, the failure analysis (ramCompatFailureHtml): which CAP
  section the failing block covers (load-file byte range and component names,
  'completes Import'), what the card checks at that point (JC VM spec 4.5.2)
  or that the import list already passed, the remote status word with its
  name, and what the probe state implies (every import at 0.0 -> a missing
  package or another load-file check is the cause; otherwise lower the
  versions and bisect).

ramCompatVerdict names the remote SW; the inline result keeps the verdict +
short probe note and the modal carries the escaped step lines + the detail
HTML (ramOpModalFinish gained an asHtml mode); the server reports
probe_unmatched as [{'aid', 'version'}] (_probe_unmatched), documented in
docs/api.md.  The same failure block for the real install is the next step.

sw cache simple-v325; tests: 550 Python / 703 Node.
This commit is contained in:
2026-09-30 09:22:22 +03:00
parent 599d84fd66
commit ee747003f2
10 changed files with 434 additions and 42 deletions
+20 -6
View File
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
VERSION = '3.6.51'
VERSION = '3.6.52'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -895,6 +895,23 @@ def _cap_import_aids(load_file_hex):
return []
def _probe_unmatched(probe, known):
"""Import-probe override AIDs that matched no Import entry: the CAP does
not import them, so the card is never asked about them. Returns
[{'aid': hex, 'version': 'x.y'}] (the version the probe requested), sorted
by AID; the 'all' override is skipped."""
out = []
for key, version in (probe or {}).items():
k = str(key).replace(' ', '')
if k.lower() == 'all':
continue
if k.upper() in known:
continue
out.append({'aid': k.upper(), 'version': str(version)})
out.sort(key=lambda e: e['aid'])
return out
def _cap_compat_blocks(load_file_hex, block_size):
"""(boundary, total) LOAD block numbers for the compatibility test.
@@ -6990,13 +7007,10 @@ class PysimHandler(BaseHTTPRequestHandler):
known = original_aids | {
str(a.get('aid', '')).replace(' ', '').upper()
for a in additions}
probe_unmatched = sorted(
k for k in probe
if str(k).lower() != 'all'
and str(k).replace(' ', '').upper() not in known)
probe_unmatched = _probe_unmatched(probe, known)
sys.stderr.write('CAP-COMPAT: import probe applied: %s%s\n' % (
json.dumps(probe_applied),
(' (unmatched: %s)' % ', '.join(probe_unmatched))
(' (unmatched: %s)' % ', '.join(e['aid'] for e in probe_unmatched))
if probe_unmatched else ''))
block_size_req = body.get('load_block_size')