v3.25.8 review: the FCI extras coverage for templates

The custom-FCI check's `fciExtra: 'fail'` coverage counted only the
exact keys, so a listed template (A5) did not accept its decoded
children (A5/80) and a listed child did not accept its parent - both
were reported as unexpected.  An object is now covered by its own key,
by a listed parent or by a listed child (an unlisted sibling still
fails); the seeded flow lists every object and was unaffected.

Tests: the coverage cases (a listed A5 accepts A5/80 + A5/81, a listed
A5/80 accepts A5 but not the A5/81 sibling) - the fixture's A5 length
corrected in the process.  Help EN/RU + both AGENTS.  893 frontend +
880 Python; sw cache simple-v433.
This commit is contained in:
2026-10-14 00:21:45 +03:00
parent a50ab27a19
commit 6e4494195c
5 changed files with 20 additions and 4 deletions
+1 -1
View File
@@ -468,7 +468,7 @@
<p class="text-sm mb-2">Правила выполняются последовательно. Редактор показывает символьное имя файла pySim (если известно) рядом с путём правила; <strong>Добавить правило</strong> добавляет правило, <strong>Сохранить</strong> сохраняет изменения. Правило файловой системы задаётся:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Путь</strong> — начинается с <code class="font-mono text-sm">MF</code> (например, <code class="font-mono text-sm">MF/7F10/6F3A</code>) или с AID ADF (например, <code class="font-mono text-sm">A0000000871002/6F07</code>).</li>
<li><strong>Проверка FCP/FCI</strong> — какая часть информации об управлении файлом проверяется: <strong>Только тип файла (FCP)</strong> (существование + тип файла), <strong>Тип файла + размер (FCP)</strong> (добавляются размер файла либо длина/число записей для record-файлов), <strong>Полный FCI</strong> (добавляется побайтовое сравнение сырого ответа SELECT — шаблона FCP <code class="font-mono text-sm">'62'</code> — выявляет изменения FID/AID, жизненного цикла, атрибутов безопасности и проприетарных параметров) или <strong>По объектам данных</strong> — список ожиданий по объектам: каждая строка задаёт объект данных (стандартные теги с именами, включая вложенные <code class="font-mono text-sm">A5/xx</code>/<code class="font-mono text-sm">C6/xx</code>, либо произвольный тег), режим — <em>значение</em> (без <code class="font-mono text-sm">?</code> совпадение по префиксу, <code class="font-mono text-sm">?</code> маскирует полубайт: <code class="font-mono text-sm">8B</code> + <code class="font-mono text-sm">2F06</code> принимает любую запись <code class="font-mono text-sm">EF.ARR</code> с таким FID, <code class="font-mono text-sm">2F06??</code> — 3-байтовую форму), <em>присутствует</em> (объект обязан быть) или <em>отсутствует</em> (его быть не должно) — и для значений hex для сравнения. Проверяются только перечисленные объекты; <strong>Лишние объекты данных: игнорировать/не допускать</strong> дополнительно запрещает всё неперечисленное (без учёта порядка, в отличие от побайтового режима). Профили, созданные с этим режимом с карты или из снимка, заполняются объектами записанного FCI; правило с записанным FCI и пустым списком можно заполнить из него (<em>заполнить из записанного FCI</em>).</li>
<li><strong>Проверка FCP/FCI</strong> — какая часть информации об управлении файлом проверяется: <strong>Только тип файла (FCP)</strong> (существование + тип файла), <strong>Тип файла + размер (FCP)</strong> (добавляются размер файла либо длина/число записей для record-файлов), <strong>Полный FCI</strong> (добавляется побайтовое сравнение сырого ответа SELECT — шаблона FCP <code class="font-mono text-sm">'62'</code> — выявляет изменения FID/AID, жизненного цикла, атрибутов безопасности и проприетарных параметров) или <strong>По объектам данных</strong> — список ожиданий по объектам: каждая строка задаёт объект данных (стандартные теги с именами, включая вложенные <code class="font-mono text-sm">A5/xx</code>/<code class="font-mono text-sm">C6/xx</code>, либо произвольный тег), режим — <em>значение</em> (без <code class="font-mono text-sm">?</code> совпадение по префиксу, <code class="font-mono text-sm">?</code> маскирует полубайт: <code class="font-mono text-sm">8B</code> + <code class="font-mono text-sm">2F06</code> принимает любую запись <code class="font-mono text-sm">EF.ARR</code> с таким FID, <code class="font-mono text-sm">2F06??</code> — 3-байтовую форму), <em>присутствует</em> (объект обязан быть) или <em>отсутствует</em> (его быть не должно) — и для значений hex для сравнения. Проверяются только перечисленные объекты; <strong>Лишние объекты данных: игнорировать/не допускать</strong> дополнительно запрещает всё неперечисленное (без учёта порядка, в отличие от побайтового режима); указание шаблона принимает его дочерние объекты, а указание дочернего объекта — родителя, неперечисленный же соседний объект по-прежнему не проходит. Профили, созданные с этим режимом с карты или из снимка, заполняются объектами записанного FCI; правило с записанным FCI и пустым списком можно заполнить из него (<em>заполнить из записанного FCI</em>).</li>
<li><strong>Правила доступа</strong> &mdash; проверяет условия доступа файла как <em>декодированные правила</em>, независимо от того, на какую запись <code class="font-mono text-sm">EF.ARR</code> указывает ссылка атрибута безопасности (<code class="font-mono text-sm">'8B'</code>) в FCI: правила указанной записи разрешаются из <code class="font-mono text-sm">EF.ARR</code> карты (или снимка) и сравниваются как множество с захваченными строками. Захватываются автоматически при создании профиля с карты или из снимка для каждого файла, чей FCI содержит ссылку (собственное содержимое EF.ARR по умолчанию пропускается &mdash; значимая проверка это правила); в редакторе захваченные правила показываются структурированными строками &mdash; операция в виде флажков битов (или командной формы RESIZE FILE / INCREASE), а каждое условие безопасности — селектором (always, never или ссылка на ключ с её использованием), с предпросмотром канонической декодированной строки, которую сравнивает проверка; «+ правило» создаёт новое (READ always), а шаблонные формы вне этого набора остаются нередактируемыми строками с возможностью удаления. Селектор <strong>«Сопоставление»</strong> выбирает сравнение: <strong>«Точное»</strong> (по умолчанию) требует полного совпадения правил карты с перечисленными строками; <strong>«Содержит»</strong> требует, чтобы каждое перечисленное правило было разрешено картой, — сравнение <em>по операциям</em> (каждый бит AM / командная запись, поэтому другое объединение битов в байте AM при той же политике тоже совпадёт), а не перечисленное правило не проверяется. Переключатель «Нет/Проверять» отключает проверку, не теряя правила; блок сворачивается в заголовок со счётчиком правил, а «Нет» скрывает его полностью. Файл, чей FCI потерял ссылку, не проходит проверку; ссылка, которую не удалось разрешить (нет кэша <code class="font-mono text-sm">EF.ARR</code>, запись вне диапазона), помечается как непроверяемая. При <strong>Полном FCI</strong> необязательный флажок <em>«Игнорировать ссылку на EF.ARR в точном FCI»</em> сравнивает FCI без значения <code class="font-mono text-sm">'8B'</code>: другой номер записи больше не приводит к несовпадению, а все остальные DO по-прежнему сравниваются побайтово (смысл ссылки проверяет эта проверка).</li>
<li><strong>Атрибуты файла</strong> — тип файла, размер, длина записи и число записей из FCP-шаблона (любой можно оставить незаданным).</li>
<li><strong>Проверить содержимое</strong> (опционально) — <strong>Точное</strong> (точное совпадение hex) или <strong>Маска</strong>, где <code class="font-mono text-sm">?</code> — шаблон на один полубайт (маска без <code class="font-mono text-sm">?</code> — совпадение префикса, например <code class="font-mono text-sm">0891</code> для MCC/MNC из IMSI). Для record-файлов хранится список по записям.</li>
+1 -1
View File
@@ -467,7 +467,7 @@
<p class="text-sm mb-2">Rules run sequentially. The editor shows the symbolic pySim name (when known) next to each rule&rsquo;s path; use <strong>Add rule</strong> to append one and <strong>Save</strong> to keep the changes. A filesystem rule is defined by:</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Path</strong> — starts with <code class="font-mono text-sm">MF</code> (e.g. <code class="font-mono text-sm">MF/7F10/6F3A</code>) or an ADF AID (e.g. <code class="font-mono text-sm">A0000000871002/6F07</code>).</li>
<li><strong>FCP/FCI check</strong> — how much of the file control information to verify: <strong>Filetype only (FCP)</strong> (existence + file type), <strong>Filetype + size (FCP)</strong> (adds file size, or record length/count for record files), <strong>Exact FCI</strong> (adds a byte-for-byte comparison of the raw SELECT response — the FCP template <code class="font-mono text-sm">'62'</code> — catching FID/AID, life-cycle status, security-attribute and proprietary-parameter changes), or <strong>Custom (per data object)</strong> — a list of per-object expectations: each row names a data object (the standard tags with their names, the nested <code class="font-mono text-sm">A5/xx</code>/<code class="font-mono text-sm">C6/xx</code> children included, or a free tag), the mode — <em>value</em> (no <code class="font-mono text-sm">?</code> matches as a prefix, <code class="font-mono text-sm">?</code> masks a nibble: <code class="font-mono text-sm">8B</code> + <code class="font-mono text-sm">2F06</code> accepts any <code class="font-mono text-sm">EF.ARR</code> record of that FID, <code class="font-mono text-sm">2F06??</code> the 3-byte form), <em>present</em> (the object must exist) or <em>absent</em> (it must not) — and, for values, the hex to compare. Only the listed objects are checked; <strong>Extra data objects: ignore/fail</strong> additionally forbids anything unlisted (order-insensitive, unlike the byte-exact mode). Profiles created from a card or a snapshot with this mode are seeded with the captured FCI&rsquo;s objects; a rule with a captured FCI and an empty list can be filled from it (<em>load from the captured FCI</em>).</li>
<li><strong>FCP/FCI check</strong> — how much of the file control information to verify: <strong>Filetype only (FCP)</strong> (existence + file type), <strong>Filetype + size (FCP)</strong> (adds file size, or record length/count for record files), <strong>Exact FCI</strong> (adds a byte-for-byte comparison of the raw SELECT response — the FCP template <code class="font-mono text-sm">'62'</code> — catching FID/AID, life-cycle status, security-attribute and proprietary-parameter changes), or <strong>Custom (per data object)</strong> — a list of per-object expectations: each row names a data object (the standard tags with their names, the nested <code class="font-mono text-sm">A5/xx</code>/<code class="font-mono text-sm">C6/xx</code> children included, or a free tag), the mode — <em>value</em> (no <code class="font-mono text-sm">?</code> matches as a prefix, <code class="font-mono text-sm">?</code> masks a nibble: <code class="font-mono text-sm">8B</code> + <code class="font-mono text-sm">2F06</code> accepts any <code class="font-mono text-sm">EF.ARR</code> record of that FID, <code class="font-mono text-sm">2F06??</code> the 3-byte form), <em>present</em> (the object must exist) or <em>absent</em> (it must not) — and, for values, the hex to compare. Only the listed objects are checked; <strong>Extra data objects: ignore/fail</strong> additionally forbids anything unlisted (order-insensitive, unlike the byte-exact mode); listing a template accepts its children and listing a child accepts its parent, while an unlisted sibling still fails. Profiles created from a card or a snapshot with this mode are seeded with the captured FCI&rsquo;s objects; a rule with a captured FCI and an empty list can be filled from it (<em>load from the captured FCI</em>).</li>
<li><strong>Access rules</strong> &mdash; verifies a file&rsquo;s access conditions as <em>decoded rules</em>, independent of which <code class="font-mono text-sm">EF.ARR</code> record the FCI&rsquo;s security-attribute reference (<code class="font-mono text-sm">'8B'</code>) points at: the referenced record&rsquo;s rules are resolved from the card&rsquo;s (or the snapshot&rsquo;s) <code class="font-mono text-sm">EF.ARR</code> 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&rsquo;s own contents are skipped by default &mdash; the rules are the meaningful check); the editor shows the captured rules as structured rows &mdash; 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; <em>+ rule</em> builds a new one (READ always), and the template forms outside this subset stay raw and removable. The <strong>Match</strong> selector picks the comparison: <strong>Exact</strong> (default) requires the card&rsquo;s rules to equal the listed lines; <strong>Contains</strong> requires every listed rule to be granted by the card, matched <em>per operation</em> (each AM bit / command entry, so a different AM grouping of the same policy still matches), and a rule not listed is not checked. 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 <code class="font-mono text-sm">EF.ARR</code>, record out of range) is reported as unverifiable. With <strong>Exact FCI</strong>, the optional <em>Ignore the EF.ARR reference in the exact FCI</em> checkbox compares the FCI without the <code class="font-mono text-sm">'8B'</code> value: a different record number no longer fails while every other DO is still compared byte-for-byte (the reference&rsquo;s meaning is verified by this check).</li>
<li><strong>File attributes</strong> — file type, size, record length and record count, taken from the FCP template (any may be left unset).</li>
<li><strong>Check contents</strong> (optional) — <strong>Exact</strong> hex equality, or <strong>Mask</strong> where <code class="font-mono text-sm">?</code> is a per-nibble wildcard (a mask with no <code class="font-mono text-sm">?</code> is a prefix match, e.g. <code class="font-mono text-sm">0891</code> for the IMSI MCC/MNC). Record files store a per-record list.</li>
+7 -1
View File
@@ -20614,7 +20614,13 @@ async function profilerRunRule(r, source) {
}
const listed = {};
r.fciDos.forEach(d => { listed[d.key] = true; });
const extra = r.fciExtra === 'fail' ? dec.items.filter(it => !listed[it.key]) : [];
// An object is covered by its own key, by a listed parent (a listed
// template accepts its children) or by a listed child (the parent of
// a listed child is implied); an unlisted sibling still fails.
const covered = k => listed[k] ||
(k.indexOf('/') > 0 && listed[k.split('/')[0]]) ||
Object.keys(listed).some(l => l.startsWith(k + '/'));
const extra = r.fciExtra === 'fail' ? dec.items.filter(it => !covered(it.key)) : [];
const ok = dos.every(d => d.ok) && !extra.length && !dec.error;
if (!ok) res.status = 'fail';
res.checks.push({ label: 'fci', mode: 'custom', dos: dos, extra: extra,
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v432';
const CACHE = 'simple-v433';
const URLS = [
'index.html',
'help.html',
+10
View File
@@ -1367,6 +1367,16 @@ test('the custom FCI check matches per data object', async () => {
record_len: 6, num_of_rec: 2, fci_hex: FCI_A5, exists: true }) };
r = await profilerRunRule({ ...rule([{ key: 'A5/80', mode: 'value', value: '01' }]), fciHex: FCI_A5 }, a5src);
assert.strictEqual(r.status, 'pass', JSON.stringify(r.checks));
// fciExtra covers a listed template's children and a listed child's parent
// (an unlisted sibling still fails)
const FCI_A5T = '621682054221000F0583026F4F8A0105A506800101810102';
const a5tsrc = { ...src, select: async () => ({ name: 'EF.SAMPLE', file_type: 'linear_fixed',
record_len: 6, num_of_rec: 2, fci_hex: FCI_A5T, exists: true }) };
r = await profilerRunRule({ ...rule([{ key: 'A5', mode: 'present' }]), fciHex: FCI_A5T, fciExtra: 'fail' }, a5tsrc);
assert.strictEqual(r.status, 'fail'); // 82/83/8A are still extras
assert.deepStrictEqual(r.checks.find(c => c.label === 'fci').extra.map(it => it.key), ['82', '83', '8A']);
r = await profilerRunRule({ ...rule([{ key: 'A5/80', mode: 'present' }]), fciHex: FCI_A5T, fciExtra: 'fail' }, a5tsrc);
assert.deepStrictEqual(r.checks.find(c => c.label === 'fci').extra.map(it => it.key), ['82', '83', '8A', 'A5/81']);
// an unparseable FCI fails with the parse error
const bad = { ...src, select: async () => ({ name: 'EF.SAMPLE', file_type: 'linear_fixed',
record_len: 6, num_of_rec: 2, fci_hex: '62', exists: true }) };