v3.25.0 review: the DF-tree ARR resolution walk-up

The live card references 6F06 from MF-tree files (MF/7F20/6F07 ->
8B 03 6F06 14), but the resolver compared only the path's first
segment: the reference fell back to the ADF.USIM copy instead of the
file's own DF copy (ISO 7816-4 5.4.3.3 / TS 102 221 9.2.7 walk-up) -
masked on this card only because all four copies are byte-identical.

- The server reads the DF.TELECOM and DF.GSM copies too (MF/7F10/6F06,
  MF/7F20/6F06 - TS 51.011), best effort.
- fcpArrRules resolves by the file's DF chain (own DF first, then
  walking up to MF); a reference with no copy in the chain resolves
  only when a single candidate exists, otherwise it stays silent.
- The implicit form prefers the file's DF copy (6F06), walking up to
  the MF copy (2F06).

Tests: the DF copy wins over the ADF copy; two candidates outside the
chain stay unresolved; a single one resolves; the implicit walk-up;
the server reads the DF-tree paths.  879 frontend + 858 Python;
sw cache simple-v424.
This commit is contained in:
2026-10-10 23:26:16 +03:00
parent 3bf005619e
commit a37266f865
7 changed files with 82 additions and 17 deletions
+29 -9
View File
@@ -20545,33 +20545,53 @@ function fcpWithoutArrRef(hex) {
// Access rules of the record(s) an FCP '8B' reference points at, resolved
// against a set of EF.ARR copies ({fid, path, records[]} - the server's
// /api/arr cache or a snapshot's own EF.ARR files). `path` is the tree path
// of the file whose FCI is shown: it picks the ARR copy of that DF (the
// ISO 7816-4 5.4.3.3 lookup order - ADF files use the ADF's 6F06 copy).
// of the file whose FCI is shown: the ARR copy is looked up per the ISO
// 7816-4 5.4.3.3 / TS 102 221 9.2.7 order - the file's own DF first, then
// walking up to the ADF/MF (a DF/ADF level holds 6F06, MF holds 2F06).
// Returns {found, rules}: found = at least one referenced record exists;
// rules = the decoded rule lines (empty when nothing resolves - the caller
// stays silent, v3.24.0).
// stays silent, v3.24.0; v3.25.0 walk-up fix).
function fcpArrRules(ref, arrs, path) {
if (!ref || !arrs || !arrs.length) return { found: false, rules: [] };
const df = (((path || '').split('/')[0]) || 'MF').toUpperCase();
// the file's DF chain, own DF first: MF/7F20/6F07 -> ['MF/7F20', 'MF'];
// ADF.USIM/6F07 -> ['ADF.USIM', 'MF']
const segs = ((path || '').toUpperCase().split('/')).filter(s => s);
const fileDf = segs.slice(0, -1);
const chain = [];
for (let i = fileDf.length; i > 0; i--) chain.push(fileDf.slice(0, i).join('/'));
if (!chain.includes('MF')) chain.push('MF');
const dfOf = a => String(a.path || '').toUpperCase().split('/').slice(0, -1).join('/');
const pickAt = (df, fid) => arrs.find(a =>
String(a.fid || '').toUpperCase() === fid && dfOf(a) === df) || null;
const pick = fid => {
const hits = arrs.filter(a => String(a.fid || '').toUpperCase() === fid);
if (!hits.length) return null;
return hits.find(a => String(a.path || '').split('/')[0].toUpperCase() === df) || hits[0];
for (const df of chain) {
const hit = hits.find(a => dfOf(a) === df);
if (hit) return hit;
}
// no copy in the file's DF chain: only a single candidate is safe
return hits.length === 1 ? hits[0] : null;
};
const out = [];
let found = false;
const add = (label, fid, record) => {
const arr = pick(fid);
const add = (label, fid, record, arrOverride) => {
const arr = arrOverride !== undefined ? arrOverride : pick(fid);
const recs = (arr && arr.records) || [];
if (!(record >= 1) || record > recs.length) return;
if (!(record >= 1) || record > recs.length) return false;
found = true;
const dec = efDecArr(efBytes(recs[record - 1] || ''));
for (const rule of ((dec && dec.rules) || [])) {
out.push((label ? label + ': ' : '') + rule);
}
return true;
};
if (ref.implicit) {
add('', df.startsWith('ADF.') ? '6F06' : '2F06', ref.record);
// the implicitly known EF.ARR of the file's DF, walking up
for (const df of chain) {
const fid = df === 'MF' ? '2F06' : '6F06';
if (add('', fid, ref.record, pickAt(df, fid))) break;
}
} else if (ref.pairs) {
for (const p of ref.pairs) add(fcpSeidName(p.seid), ref.fid, p.record);
} else {