v3.24.0: the EF.ARR access rules in the FCP view
The file manager's decoded FCI shows the '8B' security attribute reference (EF_ARR 2F06, record 23). The referenced record's access rules are now rendered as indented muted lines under that row, decoded with the existing EF.ARR record decoder (AM_DO/SC_DO conditions). The server reads the card's ARR copies (MF/2F06 and ADF.USIM/6F06, best effort) once per card session in _apply_equipped_card and keeps them as server.arr; GET /api/arr serves the cache without the card lock, a disconnect clears it, and a write to an ARR copy through /api/write refreshes it. The PWA fetches it lazily per card session (pysimArrEnsure, keyed by card_session) and resolves the reference with the selected file's DF context: an ADF.USIM file uses the ADF's 6F06 copy, the implicit reference form too (ISO 7816-4 5.4.3.3 lookup order). Unresolvable references stay silent. The '8B' decode now covers all three ISO 7816-4 Table 25 forms: implicit ARR byte / FID + record / FID + (SEID, ARR) pairs - the pairs form was mis-decoded as a record before. A snapshot resolves the same references offline from its own captured EF.ARR copy. Tests: the three forms, the resolution (DF preference, out-of-range silence), the snapshot path, the server read/cache/route/write-refresh. 868 frontend + 856 Python; sw cache simple-v423.
This commit is contained in:
@@ -443,7 +443,7 @@ test('file manager shows FCI info and keeps the selection in state, not the DOM'
|
||||
const content = html.indexOf('id="pysim-fs-content"');
|
||||
assert.ok(detail !== -1 && info > detail && info < content, 'pysim-fs-info must sit above the content');
|
||||
assert.ok(html.includes('function pysimFsInfoHtml'));
|
||||
assert.ok(html.includes('pysimFsInfoHtml(sel, name)'));
|
||||
assert.ok(html.includes('pysimFsInfoHtml(sel, name, { arr: arrs, path: pysimFsNodePath(node) })'));
|
||||
assert.ok(!html.includes('pysim-fs-filename'));
|
||||
assert.ok(html.includes('let pysimFsSelected = null;'));
|
||||
assert.ok(html.includes('pysimFsSelected = name;'));
|
||||
|
||||
@@ -22,6 +22,7 @@ function extractFunc(src, name, asyncFn) {
|
||||
}
|
||||
|
||||
const FNS = ['profilerNormHex', 'profilerNormHexStrict', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile', 'profilerCustomNameForPath', 'pysimCustomNormPath', 'profilerUpdateRulePath', 'profilerResultAspects', 'profilerAspectSummary', 'profilerNumRanges', 'profilerMatchedRecordsText', 'profilerCloneName', 'profilerClone', 'profilerNewId', 'esc', 'escHtml', 'profilerRawDataCheck', 'profilerRenderReport', 'parseBerLen', 'parseTlvList', 'fcpInt', 'fcpParseTlvs', 'fcpFileDescriptor', 'fcpLifeCycle', 'fcpSfi', 'fcpDo', 'fcpDecode',
|
||||
'fcpArrRef', 'fcpSeidName', 'fcpArrRules', 'profilerSnapshotArr', 'efBytes', 'efHex', 'efTlv', 'efDecArr', 'efArrAmLabel', 'efArrSc', 'efArrAmDo', 'efFidFromPath',
|
||||
'pinKeyRefName', 'fcpPsTemplate', 'fcpDiffHtml', 'profilerFciPreviewItems', 'profilerUpdateFciPreview', 'profilerUpdateRule', 'profilerFciInput', 'profilerScanToggleAll', 'profilerScanIgnoreAllState', 'swapNibbles', 'decIccid', 'profilerSnapshotIccid', 'profilerValidateSnapshot', 'profilerListSwitch', 'profilerScanRefreshOptions', 'profilerLiveSource', 'profilerSnapshotSource', 'profilerVisibleResults', 'profilerRulesFromSnapshot', 'profilerExtraFileResults', 'profilerScanNameKeydown', 'profilerTimingStats', 'profilerTimingAccumulator', 'profilerFormatMs', 'profilerRenderSnapshotSummary', 'profilerSnapshotCountLabel', 'pysimFsInfoHtml', 'profilerLabelText', 'profilerResultsHeaderText', 'profilerRenderResultsView', 'profilerBuildFileRuleFromSnapshot', 'profilerSnapshotPickListHtml', 'profilerScanSetTarget'];
|
||||
let code = '';
|
||||
for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
@@ -813,6 +814,94 @@ test('pysimFsInfoHtml keeps the FCI File size when the header has none', () => {
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('fcpArrRef parses the three 8B referencing forms', () => {
|
||||
assert.deepStrictEqual(fcpArrRef('17'), { implicit: true, record: 23 });
|
||||
assert.deepStrictEqual(fcpArrRef('2F0617'), { fid: '2F06', record: 23 });
|
||||
assert.deepStrictEqual(fcpArrRef('2f0600170101'), {
|
||||
fid: '2F06', pairs: [{ seid: 0, record: 23 }, { seid: 1, record: 1 }] });
|
||||
assert.strictEqual(fcpArrRef('2F060'), null); // odd length
|
||||
assert.strictEqual(fcpArrRef('ZZZZZZ'), null); // not hex
|
||||
});
|
||||
|
||||
test('fcpDo decodes every 8B referencing form', () => {
|
||||
global.t = s => s;
|
||||
assert.strictEqual(fcpDo('8B', '2F0617').decoded, 'EF_ARR 2F06, record 23');
|
||||
assert.strictEqual(fcpDo('8B', '17').decoded, 'record 23 (implicit EF_ARR)');
|
||||
assert.strictEqual(fcpDo('8B', '2F0600170101').decoded,
|
||||
'EF_ARR 2F06, SE00 \u2192 record 23, SE01 \u2192 record 1');
|
||||
assert.strictEqual(fcpDo('8B', '2F06').decoded, '2F06');
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('the FCI view shows the referenced EF.ARR rules when the ARR is known', () => {
|
||||
global.t = s => s;
|
||||
// the stk_params fixture FCI with 8B 03 2F06 02 (record 2)
|
||||
const hex = '621A82054221000F0583026F4F8A01058B032F06028802004B8801B0';
|
||||
const arrs = [{ fid: '2F06', path: 'MF/2F06', record_len: 6, num_records: 2,
|
||||
records: ['FFFFFFFFFFFFFFFFFFFFFFFF', '800101A40683010A950108'] }];
|
||||
const out = profilerFciPreviewItems(hex, { arr: arrs, path: 'MF/7F20/6F07' });
|
||||
assert.ok(out.includes('EF_ARR 2F06, record 2'), out);
|
||||
assert.ok(out.includes('AM 0x01 (READ/SEARCH (EF) / DELETE FILE child (DF)): ADM1 (verify)'), out);
|
||||
assert.ok(out.includes('\u21b3'), out);
|
||||
// without ARR data the row is unchanged
|
||||
const plain = profilerFciPreviewItems(hex);
|
||||
assert.ok(plain.includes('EF_ARR 2F06, record 2'), plain);
|
||||
assert.ok(!plain.includes('ADM1'), plain);
|
||||
// an out-of-range record stays silent
|
||||
const hex9 = '621A82054221000F0583026F4F8A01058B032F06098802004B8801B0';
|
||||
const oor = profilerFciPreviewItems(hex9, { arr: arrs, path: 'MF/2F06' });
|
||||
assert.ok(oor.includes('EF_ARR 2F06, record 9'), oor);
|
||||
assert.ok(!oor.includes('ADM1'), oor);
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('the implicit 8B form uses the ARR of the file\'s DF', () => {
|
||||
global.t = s => s;
|
||||
// 8B 01 02: implicit EF_ARR, record 2
|
||||
const hex = '621882054221000F0583026F4F8A01058B01028002004B8801B0';
|
||||
const arrs = [
|
||||
{ fid: '2F06', path: 'MF/2F06', records: ['FFFFFFFF', '800101A40683010A950108'] },
|
||||
{ fid: '6F06', path: 'ADF.USIM/6F06', records: ['FFFFFFFF', '8001019000'] },
|
||||
];
|
||||
// an ADF.USIM file resolves against the ADF's 6F06 copy
|
||||
const adf = profilerFciPreviewItems(hex, { arr: arrs, path: 'ADF.USIM/6F07' });
|
||||
assert.ok(adf.includes('record 2 (implicit EF_ARR)'), adf);
|
||||
assert.ok(adf.includes('AM 0x01 (READ/SEARCH (EF) / DELETE FILE child (DF)): always'), adf);
|
||||
assert.ok(!adf.includes('ADM1'), adf);
|
||||
// an MF-tree file resolves against MF/2F06
|
||||
const mf = profilerFciPreviewItems(hex, { arr: arrs, path: 'MF/7F20/6F07' });
|
||||
assert.ok(mf.includes('AM 0x01 (READ/SEARCH (EF) / DELETE FILE child (DF)): ADM1 (verify)'), mf);
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('the snapshot FCI view resolves references from the snapshot ARR', () => {
|
||||
global.t = s => s;
|
||||
const snap = { files: [
|
||||
{ path: 'MF/2F06', name: 'EF.ARR', fileType: 'linear_fixed', recordLen: 6, numRecords: 2,
|
||||
content: { kind: 'record', records: [{ num: 1, data: 'FFFFFFFFFFFFFFFFFFFFFFFF' },
|
||||
{ num: 2, data: '800101A40683010A950108' }] } },
|
||||
{ path: 'MF/7F20/6F07', name: 'EF.IMSI',
|
||||
fciHex: '621A82054221000F0583026F4F8A01058B032F06028802004B8801B0' },
|
||||
]};
|
||||
const arrs = profilerSnapshotArr(snap);
|
||||
assert.deepStrictEqual(arrs.map(a => a.fid), ['2F06']);
|
||||
const out = profilerFciPreviewItems(snap.files[1].fciHex, { arr: arrs, path: snap.files[1].path });
|
||||
assert.ok(out.includes('AM 0x01 (READ/SEARCH (EF) / DELETE FILE child (DF)): ADM1 (verify)'), out);
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('pysimFsInfoHtml shows the referenced ARR rules when given the cache', () => {
|
||||
global.t = s => s;
|
||||
const hex = '621A82054221000F0583026F4F8A01058B032F06028802004B8801B0';
|
||||
const arrs = [{ fid: '2F06', path: 'MF/2F06', records: ['', '800101a40683010a950108'] }];
|
||||
const out = pysimFsInfoHtml({ fid: '6f4f', file_type: 'linear_fixed', file_size: 75,
|
||||
record_len: 15, num_of_rec: 5, fci_hex: hex }, 'EF.SAMPLE',
|
||||
{ arr: arrs, path: 'MF/7F20/6F4F' });
|
||||
assert.ok(out.includes('EF_ARR 2F06, record 2'), out);
|
||||
assert.ok(out.includes('ADM1 (verify)'), out);
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('pysimFsInfoHtml embeds the symbolic name in the FCI border', () => {
|
||||
global.t = s => s;
|
||||
const hex = '6212800200098202412183026F078A0105880110';
|
||||
|
||||
Reference in New Issue
Block a user