Путь — начинается с MF (например, MF/7F10/6F3A) или с AID ADF (например, A0000000871002/6F07).
Проверка FCP/FCI — какая часть информации об управлении файлом проверяется: Только тип файла (FCP) (существование + тип файла), Тип файла + размер (FCP) (добавляются размер файла либо длина/число записей для record-файлов) или Полный FCI (добавляется побайтовое сравнение сырого ответа SELECT — шаблона FCP '62' — выявляет изменения FID/AID, жизненного цикла, атрибутов безопасности и проприетарных параметров).
-
Правила доступа — проверяет условия доступа файла как декодированные правила, независимо от того, на какую запись EF.ARR указывает ссылка атрибута безопасности ('8B') в FCI: правила указанной записи разрешаются из EF.ARR карты (или снимка) и сравниваются как множество с захваченными строками. Захватываются автоматически при создании профиля с карты или из снимка для каждого файла, чей FCI содержит ссылку (собственное содержимое EF.ARR по умолчанию пропускается — значимая проверка это правила); в редакторе захваченные правила показываются структурированными строками — операция в виде флажков битов (или командной формы RESIZE FILE / INCREASE), а каждое условие безопасности — селектором (always, never или ссылка на ключ с её использованием), с предпросмотром канонической декодированной строки, которую сравнивает проверка; «+ правило» создаёт новое (READ always), а шаблонные формы вне этого набора остаются нередактируемыми строками с возможностью удаления. Переключатель «Нет/Проверять» отключает проверку, не теряя правила. Файл, чей FCI потерял ссылку, не проходит проверку; ссылка, которую не удалось разрешить (нет кэша EF.ARR, запись вне диапазона), помечается как непроверяемая. При Полном FCI необязательный флажок «Игнорировать ссылку на EF.ARR в точном FCI» сравнивает FCI без значения '8B': другой номер записи больше не приводит к несовпадению, а все остальные DO по-прежнему сравниваются побайтово (смысл ссылки проверяет эта проверка).
+
Правила доступа — проверяет условия доступа файла как декодированные правила, независимо от того, на какую запись EF.ARR указывает ссылка атрибута безопасности ('8B') в FCI: правила указанной записи разрешаются из EF.ARR карты (или снимка) и сравниваются как множество с захваченными строками. Захватываются автоматически при создании профиля с карты или из снимка для каждого файла, чей FCI содержит ссылку (собственное содержимое EF.ARR по умолчанию пропускается — значимая проверка это правила); в редакторе захваченные правила показываются структурированными строками — операция в виде флажков битов (или командной формы RESIZE FILE / INCREASE), а каждое условие безопасности — селектором (always, never или ссылка на ключ с её использованием), с предпросмотром канонической декодированной строки, которую сравнивает проверка; «+ правило» создаёт новое (READ always), а шаблонные формы вне этого набора остаются нередактируемыми строками с возможностью удаления. Переключатель «Нет/Проверять» отключает проверку, не теряя правила; блок сворачивается в заголовок со счётчиком правил, а «Нет» скрывает его полностью. Файл, чей FCI потерял ссылку, не проходит проверку; ссылка, которую не удалось разрешить (нет кэша EF.ARR, запись вне диапазона), помечается как непроверяемая. При Полном FCI необязательный флажок «Игнорировать ссылку на EF.ARR в точном FCI» сравнивает FCI без значения '8B': другой номер записи больше не приводит к несовпадению, а все остальные DO по-прежнему сравниваются побайтово (смысл ссылки проверяет эта проверка).
Атрибуты файла — тип файла, размер, длина записи и число записей из FCP-шаблона (любой можно оставить незаданным).
Проверить содержимое (опционально) — Точное (точное совпадение hex) или Маска, где ? — шаблон на один полубайт (маска без ? — совпадение префикса, например 0891 для MCC/MNC из IMSI). Для record-файлов хранится список по записям.
Path — starts with MF (e.g. MF/7F10/6F3A) or an ADF AID (e.g. A0000000871002/6F07).
FCP/FCI check — how much of the file control information to verify: Filetype only (FCP) (existence + file type), Filetype + size (FCP) (adds file size, or record length/count for record files), or Exact FCI (adds a byte-for-byte comparison of the raw SELECT response — the FCP template '62' — catching FID/AID, life-cycle status, security-attribute and proprietary-parameter changes).
-
Access rules — verifies a file’s access conditions as decoded rules, independent of which EF.ARR record the FCI’s security-attribute reference ('8B') points at: the referenced record’s rules are resolved from the card’s (or the snapshot’s) EF.ARR and compared as a set with the captured lines. Captured automatically when a profile is created from a card or a snapshot for every file whose FCI carries a reference (EF.ARR’s own contents are skipped by default — the rules are the meaningful check); the editor shows the captured rules as structured rows — the operation as bit checkboxes (or the RESIZE FILE / INCREASE command form) and each security condition as a select (always, never, or a key reference with its usage), with a preview line of the canonical decoded string the check compares; + rule builds a new one (READ always), and the template forms outside this subset stay raw and removable. The None/Check toggle disables the check without losing the rules. A file whose FCI lost the reference fails; a reference that cannot be resolved (no cached EF.ARR, record out of range) is reported as unverifiable. With Exact FCI, the optional Ignore the EF.ARR reference in the exact FCI checkbox compares the FCI without the '8B' value: a different record number no longer fails while every other DO is still compared byte-for-byte (the reference’s meaning is verified by this check).
+
Access rules — verifies a file’s access conditions as decoded rules, independent of which EF.ARR record the FCI’s security-attribute reference ('8B') points at: the referenced record’s rules are resolved from the card’s (or the snapshot’s) EF.ARR and compared as a set with the captured lines. Captured automatically when a profile is created from a card or a snapshot for every file whose FCI carries a reference (EF.ARR’s own contents are skipped by default — the rules are the meaningful check); the editor shows the captured rules as structured rows — the operation as bit checkboxes (or the RESIZE FILE / INCREASE command form) and each security condition as a select (always, never, or a key reference with its usage), with a preview line of the canonical decoded string the check compares; + rule builds a new one (READ always), and the template forms outside this subset stay raw and removable. The None/Check toggle disables the check without losing the rules; the block collapses to a header with the rule count and None hides it entirely. A file whose FCI lost the reference fails; a reference that cannot be resolved (no cached EF.ARR, record out of range) is reported as unverifiable. With Exact FCI, the optional Ignore the EF.ARR reference in the exact FCI checkbox compares the FCI without the '8B' value: a different record number no longer fails while every other DO is still compared byte-for-byte (the reference’s meaning is verified by this check).
File attributes — file type, size, record length and record count, taken from the FCP template (any may be left unset).
Check contents (optional) — Exact hex equality, or Mask where ? is a per-nibble wildcard (a mask with no ? is a prefix match, e.g. 0891 for the IMSI MCC/MNC). Record files store a per-record list.
diff --git a/frontend/index.html b/frontend/index.html
index 4d2326c..9d712dc 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -19812,19 +19812,27 @@ function profilerRenderRule(i, r) {
// built the same way; every change re-encodes the canonical decoded line the
// check compares. Forms outside the supported subset stay raw (read-only,
// removable). Capture fills the lines when a profile is created from a card
-// or a snapshot; the radio toggles the check.
+// or a snapshot; the radio toggles the check, None hides the whole block and
+// the block collapses to its header (the rules are kept either way).
function profilerRenderAccessRulesEditor(i, r) {
const ar = r.accessRules || null;
const lines = (ar && ar.expected) || [];
const on = !!(ar && ar.enabled !== false);
+ const open = on && !!_profilerArrOpen[i];
let html = '
';
- html += '' + esc(t('Check access rules')) + '';
+ if (on) {
+ html += '';
+ } else {
+ html += '' + esc(t('Check access rules')) + '';
+ }
html += '
';
html += '';
html += '';
html += '
';
+ if (!open) return html;
if (r.fciMode === 'exact') {
- html += '';
+ html += '';
}
for (let ri = 0; ri < lines.length; ri++) {
html += profilerArrRuleHtml(i, ri, lines[ri]);
@@ -19836,6 +19844,15 @@ function profilerRenderAccessRulesEditor(i, r) {
return html;
}
+// The expanded access-rule blocks of the profile editor (transient UI state,
+// kept across the editor re-renders; None hides the block entirely).
+let _profilerArrOpen = {};
+
+function profilerArrToggleOpen(i) {
+ _profilerArrOpen[i] = !_profilerArrOpen[i];
+ profilerRenderEditor();
+}
+
// One expected rule row: the operation form + bit checkboxes (or the RESIZE /
// INCREASE command), the condition rows and the canonical preview line.
function profilerArrRuleHtml(i, ri, line) {
@@ -19972,6 +19989,8 @@ function profilerSetAccessRules(i, on) {
const r = profilerDraft.rules[i];
if (!r.accessRules) r.accessRules = { expected: [] };
r.accessRules.enabled = on;
+ // Check opens the block, None hides it (the rules are kept either way)
+ _profilerArrOpen[i] = on;
profilerRenderEditor();
}
diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js
index 02999bd..a450cdf 100644
--- a/frontend/tests/profiler.test.js
+++ b/frontend/tests/profiler.test.js
@@ -22,7 +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', 'fcpArrRefFromFci', 'fcpWithoutArrRef', 'fcpArrSplitTop', 'fcpArrKeyRefFromName', 'fcpArrKeyRefOptions', 'fcpArrScParse', 'fcpArrScEncode', 'fcpArrRuleParse', 'fcpArrRuleEncode', 'fcpArrRuleToggleBit', 'fcpArrRuleSetKind', 'fcpArrRuleSetSc', 'fcpArrRuleSetUq', 'fcpArrRuleAddSc', 'fcpArrRuleRemoveSc', 'profilerRenderAccessRulesEditor', 'profilerArrRuleHtml', 'profilerArrScSelect', 'profilerArrUqSelect', 'profilerAccessRulesDiffHtml', 'profilerSnapshotArr', 'efBytes', 'efHex', 'efTlv', 'efDecArr', 'efArrAmLabel', 'efArrSc', 'efArrAmDo', 'efFidFromPath',
+ 'fcpArrRef', 'fcpSeidName', 'fcpArrRules', 'fcpArrRefFromFci', 'fcpWithoutArrRef', 'fcpArrSplitTop', 'fcpArrKeyRefFromName', 'fcpArrKeyRefOptions', 'fcpArrScParse', 'fcpArrScEncode', 'fcpArrRuleParse', 'fcpArrRuleEncode', 'fcpArrRuleToggleBit', 'fcpArrRuleSetKind', 'fcpArrRuleSetSc', 'fcpArrRuleSetUq', 'fcpArrRuleAddSc', 'fcpArrRuleRemoveSc', 'profilerRenderAccessRulesEditor', 'profilerArrRuleHtml', 'profilerArrScSelect', 'profilerArrUqSelect', 'profilerArrToggleOpen', 'profilerAccessRulesDiffHtml', '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';
@@ -37,6 +37,7 @@ code += extractFunc(html, 'profilerScanSnapshot', true) + '\n';
code += extractFunc(html, 'pysimArrEnsure', true) + '\n';
code += "var _scanTarget = 'profile';\nvar profilerResults = null;\nvar profilerResultsHeader = null;\nvar profilerMismatchOnly = false;\n";
code += "var _pysimArrCache = null;\nvar _pysimCardSession = null;\n";
+code += "var _profilerArrOpen = {};\n";
// the view-history hooks (the router lives in the page; this pure test stubs it)
code += 'globalThis.navRecord = function() {};\nglobalThis.navReplace = function() {};\n';
code += html.match(/const PROFILER_MASK_PREFIX4_FIDS = \{[\s\S]*?\n\};/)[0] + '\n';
@@ -1114,23 +1115,45 @@ test('the access-rules editor renders structured rows', () => {
'RESIZE FILE (AM_DO 0x84, INS 0xD4): never',
'AM 0x02 (UPDATE/ERASE (EF) / CREATE FILE EF (DF)): (PIN app1 (verify) OR PIN app2 (verify))',
] } };
- const html = profilerRenderAccessRulesEditor(3, r);
+ // collapsed by default: the header + toggle carry the rule count, no rows
+ _profilerArrOpen = {};
+ const closed = profilerRenderAccessRulesEditor(3, r);
+ assert.ok(closed.includes('profilerArrToggleOpen(3)'), closed);
+ assert.ok(closed.includes('Check access rules (3)'), closed);
+ assert.ok(!closed.includes('profilerArrToggleBit(3,0,1'), closed);
+ assert.ok(!closed.includes('profilerArrAddRule(3)'), closed);
+ // expanded: structured rows
+ _profilerArrOpen = { 3: true };
+ const out = profilerRenderAccessRulesEditor(3, r);
// the READ bit is checked; the condition select shows always
- assert.ok(html.includes('type="checkbox" checked onchange="profilerArrToggleBit(3,0,1,this.checked)"'), html);
- assert.ok(html.includes(''), html);
- assert.ok(html.includes(''), html);
+ assert.ok(out.includes('type="checkbox" checked onchange="profilerArrToggleBit(3,0,1,this.checked)"'), out);
+ assert.ok(out.includes(''), out);
+ assert.ok(out.includes(''), out);
// the RESIZE row uses the command form, not bits
- assert.ok(html.includes(''), html);
+ assert.ok(out.includes(''), out);
// the template rule is raw and not editable
- assert.ok(html.includes('raw: AM 0x02'), html);
- assert.ok(!html.includes('profilerArrToggleBit(3,2'), html);
+ assert.ok(out.includes('raw: AM 0x02'), out);
+ assert.ok(!out.includes('profilerArrToggleBit(3,2'), out);
// the preview lines and the + rule button are present
- assert.ok(html.includes('RESIZE FILE (AM_DO 0x84, INS 0xD4): never'), html);
- assert.ok(html.includes('profilerArrAddRule(3)'), html);
- // an empty rule shows the capture hint with no disabled controls
- const empty = profilerRenderAccessRulesEditor(4, { fciMode: 'type_size', accessRules: null });
+ assert.ok(out.includes('RESIZE FILE (AM_DO 0x84, INS 0xD4): never'), out);
+ assert.ok(out.includes('profilerArrAddRule(3)'), out);
+ // None hides the whole block (no toggle, no body)
+ const none = profilerRenderAccessRulesEditor(3, { ...r, accessRules: { ...r.accessRules, enabled: false } });
+ assert.ok(none.includes('Check access rules'), none);
+ assert.ok(!none.includes('profilerArrToggleOpen'), none);
+ assert.ok(!none.includes('profilerArrAddRule'), none);
+ // a rule without accessRules starts hidden too (Check enables it)
+ const absent = profilerRenderAccessRulesEditor(4, { fciMode: 'type_size', accessRules: null });
+ assert.ok(!absent.includes('profilerArrToggleOpen'), absent);
+ assert.ok(!absent.includes('profilerArrAddRule'), absent);
+ // an enabled but empty block shows the capture hint and + rule
+ _profilerArrOpen = { 4: true };
+ const empty = profilerRenderAccessRulesEditor(4, { fciMode: 'type_size', accessRules: { expected: [] } });
assert.ok(empty.includes('profilerArrAddRule(4)'), empty);
assert.ok(!empty.includes(' disabled'), empty);
+ _profilerArrOpen = {};
+ // Check opens the block, None hides it
+ assert.ok(html.includes('_profilerArrOpen[i] = on;'), 'the radio drives the visibility');
delete global.t;
});