v3.28.4: the Custom-mode FCI editor + the shared ADM pattern
The profile rule editor rendered the same "FCI hex (raw SELECT response)" field for Exact and Custom, although the custom check never reads `fciHex` (it compares the data objects against the card's live FCI) - so the field looked like an expectation it was not. - The raw-FCI field and its decoded preview are exact-mode-only now; the Custom mode shows just its data-object editor. - Switching a rule to Custom seeds the data objects from its captured FCI when the list is empty (the same all-'value' seeding a captured profile gets); the "load from the captured FCI" link refills an emptied list, and a rule without a captured FCI still starts empty for manual rows. From the v3.28.3 review: the ADM's byte-pair rule now lives in one place, presets.ADM_PATTERN, used by /api/verify-adm and the outfile import - two literals could drift and an imported ADM could silently become unverifiable. The RU help's ADM wording is "4-16 hex-цифр (чётное число)". Docs: help EN/RU, the repo AGENTS. Tests: the mode switch (seed / keep / no FCI / switch-away), the field-gating source pin, the pattern's shape and the both-users pin. 917 frontend + 927 Python; sw cache simple-v442.
This commit is contained in:
@@ -369,7 +369,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-sm mb-3">Столбец <strong>SCP81</strong> показывает, задана ли в пресете рабочая пара PSK: <strong>✓</strong> (зелёный) — идентификатор и ключ заданы; <strong>⚠</strong> (жёлтый) — задано только одно из двух, и слушатель такой пресет игнорирует; <strong>—</strong> — PSK нет. Идентификатор и ключ PSK задаются вместе. Кнопка <strong>Добавить пресет</strong> открывает редактор с новым пресетом; <strong>Редактировать</strong> загружает пресет строки в редактор, поэтому поля можно менять без повторного ввода карты; <strong>Сохранить</strong> сохраняет и возвращает к списку, <strong>Отмена</strong> отменяет правки; <strong>Удалить</strong> удаляет строку (только в списке). Обмен пресетами: <strong>Экспорт в файл</strong> для выгрузки и <strong>Импорт из файла</strong> для загрузки. Выбранный пресет автоматически заполняет форму Secured Packet и устанавливает TAR для операции (RAM/GP → ISD TAR, SIM RFM → UICC RFM TAR, USIM RFM → ADF RFM TAR); TAR, введённый в форму Secured Packet вручную, сохраняется при отправке - счётчик, ключи и MSL этого TAR по-прежнему берутся из пресета; SPI1 берётся из MSL, а меньшее значение даёт предупреждение (сервер тоже возвращает его как <code class="font-mono text-sm">warning</code>); изменения сразу передаются работающему слушателю SCP81.</p>
|
||||
<p class="text-sm mb-3"><strong>Импорт outfile</strong> загружает сразу много карт из <em>outfile</em> поставщика карт — текстового дампа инструмента персонализации: шапка с баннером и параметрами, затем разделённая пробелами <strong>строка колонок</strong> (среди токенов есть <code class="font-mono text-sm">ICCID</code>; первый токен может нести префикс вида <code class="font-mono text-sm">var_out:</code>) и по одной строке на карту. Всё до строки колонок и все неиспользуемые колонки игнорируются. Разбираются <code class="font-mono text-sm">ICCID</code> (цифры, цифры с добивочным <code class="font-mono text-sm">F</code> до чётного числа нибблов, цифры с разделителями или сырой hex EF — всё приводится к одной форме), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (hex-значение ключа, 4–16 hex-цифр парами байт — например <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 байт; необязательная колонка <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> задаёт её идентификатор, по умолчанию берётся ICCID карты) и нумерованные пары ключей <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — по одному keyset на пару, до 15. <strong>Байтов KIc/KID в файле нет</strong>: номер keyset — это номер колонки (<code class="font-mono text-sm">KIC2</code> → keyset 2), а ниббл алгоритма определяется размером ключа — 8 байт DES, 24 байта 3DES3, а для <strong>16 байт</strong> — выбором в предпросмотре: <strong>3DES2</strong> (по умолчанию) или <strong>AES</strong>; любой другой размер пропускает этот keyset с пометкой. TAR также не берутся из файла: предпросмотр показывает таблицу TAR, которую получат импортируемые карты (по умолчанию три ролевые записи с TAR по спецификации и MSL 16; их можно править, кнопка <strong>+ TAR</strong> добавляет записи), и один <strong>keyset по умолчанию</strong>, применяемый ко всем записям TAR — он обязателен для карты с несколькими keysets (ненулевой MSL должен называть keyset). <strong>Общее имя</strong> применяется ко всем импортируемым картам (пустое — берётся ICCID); <strong>режим импорта</strong> выбирает <strong>Дополнить</strong> (по умолчанию — существующие пресеты сохраняются, дубли ICCID пропускаются и перечисляются) или <strong>Заменить</strong> (хранилище сначала очищается). Предпросмотр перечисляет разобранные карты (ICCID, keysets, найденные коды) и каждую пропущенную строку/ключ с причиной — до того, как что-либо сохранено.</p>
|
||||
<p class="text-sm mb-3"><strong>Импорт outfile</strong> загружает сразу много карт из <em>outfile</em> поставщика карт — текстового дампа инструмента персонализации: шапка с баннером и параметрами, затем разделённая пробелами <strong>строка колонок</strong> (среди токенов есть <code class="font-mono text-sm">ICCID</code>; первый токен может нести префикс вида <code class="font-mono text-sm">var_out:</code>) и по одной строке на карту. Всё до строки колонок и все неиспользуемые колонки игнорируются. Разбираются <code class="font-mono text-sm">ICCID</code> (цифры, цифры с добивочным <code class="font-mono text-sm">F</code> до чётного числа нибблов, цифры с разделителями или сырой hex EF — всё приводится к одной форме), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (hex-значение ключа, 4–16 hex-цифр (чётное число) — например <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 байт; необязательная колонка <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> задаёт её идентификатор, по умолчанию берётся ICCID карты) и нумерованные пары ключей <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — по одному keyset на пару, до 15. <strong>Байтов KIc/KID в файле нет</strong>: номер keyset — это номер колонки (<code class="font-mono text-sm">KIC2</code> → keyset 2), а ниббл алгоритма определяется размером ключа — 8 байт DES, 24 байта 3DES3, а для <strong>16 байт</strong> — выбором в предпросмотре: <strong>3DES2</strong> (по умолчанию) или <strong>AES</strong>; любой другой размер пропускает этот keyset с пометкой. TAR также не берутся из файла: предпросмотр показывает таблицу TAR, которую получат импортируемые карты (по умолчанию три ролевые записи с TAR по спецификации и MSL 16; их можно править, кнопка <strong>+ TAR</strong> добавляет записи), и один <strong>keyset по умолчанию</strong>, применяемый ко всем записям TAR — он обязателен для карты с несколькими keysets (ненулевой MSL должен называть keyset). <strong>Общее имя</strong> применяется ко всем импортируемым картам (пустое — берётся ICCID); <strong>режим импорта</strong> выбирает <strong>Дополнить</strong> (по умолчанию — существующие пресеты сохраняются, дубли ICCID пропускаются и перечисляются) или <strong>Заменить</strong> (хранилище сначала очищается). Предпросмотр перечисляет разобранные карты (ICCID, keysets, найденные коды) и каждую пропущенную строку/ключ с причиной — до того, как что-либо сохранено.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
@@ -470,7 +470,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 (поле сырого FCI принадлежит режиму <strong>Exact FCI</strong> — этот режим никогда не сравнивает сырые байты); правило с записанным FCI и пустым списком можно заполнить заново (<em>заполнить из записанного FCI</em>), а правило без FCI начинается с пустого списка для ручных строк.</li>
|
||||
<li><strong>Правила доступа</strong> — проверяет условия доступа файла как <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 по умолчанию пропускается — значимая проверка это правила); в редакторе захваченные правила показываются структурированными строками — операция в виде флажков битов (или командной формы 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
@@ -469,7 +469,7 @@
|
||||
<p class="text-sm mb-2">Rules run sequentially. The editor shows the symbolic pySim name (when known) next to each rule’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); 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’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’s objects, and switching a rule to this mode seeds them from its captured FCI as well (the raw-FCI field belongs to the <strong>Exact FCI</strong> mode — this mode never compares the raw bytes); a rule with a captured FCI and an empty list can be refilled from it (<em>load from the captured FCI</em>), and a rule without one starts empty for manual rows.</li>
|
||||
<li><strong>Access rules</strong> — verifies a file’s access conditions as <em>decoded rules</em>, independent of which <code class="font-mono text-sm">EF.ARR</code> record the FCI’s security-attribute reference (<code class="font-mono text-sm">'8B'</code>) points at: the referenced record’s rules are resolved from the card’s (or the snapshot’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’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; <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’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’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>
|
||||
|
||||
+16
-3
@@ -1910,7 +1910,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.28.3';
|
||||
const SIMPLE_VERSION = '3.28.4';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== view history (the browser Back/Forward buttons) =====
|
||||
@@ -20336,7 +20336,7 @@ function profilerRenderRule(i, r) {
|
||||
'<input type="number" value="' + (r.numRecords === null ? '' : r.numRecords) + '" oninput="profilerUpdateRule(' + i + ',\'numRecords\',this.value===\'\'?null:parseInt(this.value))" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="—"></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
if (fciMode === 'exact' || fciMode === 'custom') {
|
||||
if (fciMode === 'exact') {
|
||||
html += '<div class="mb-2 flex gap-3 items-start">';
|
||||
html += '<div class="flex-1 min-w-0"><label class="block text-xs text-gray-600 dark:text-slate-400 mb-0.5">' + esc(t('FCI hex (raw SELECT response)')) + '</label>' +
|
||||
'<textarea oninput="profilerFciInput(' + i + ',this.value)" rows="4" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="62...">' + escHtml(r.fciHex || '') + '</textarea></div>';
|
||||
@@ -20344,6 +20344,10 @@ function profilerRenderRule(i, r) {
|
||||
'<div id="profiler-fci-preview-' + i + '" class="text-xs font-mono leading-relaxed text-gray-600 dark:text-slate-400 break-all">' + profilerFciPreviewItems(r.fciHex || '') + '</div></div>';
|
||||
html += '</div>';
|
||||
}
|
||||
// Custom mode (v3.28.4): the raw FCI is only the *source* for the data
|
||||
// objects (the check compares the objects, never the raw bytes), so its
|
||||
// field is exact-mode-only and the object list is seeded automatically when
|
||||
// the rule is switched to Custom.
|
||||
if (fciMode === 'custom') {
|
||||
html += profilerRenderFciDosEditor(i, r);
|
||||
}
|
||||
@@ -20756,7 +20760,16 @@ function profilerUpdateRuleFileType(i, value) {
|
||||
|
||||
function profilerUpdateRuleFciMode(i, value) {
|
||||
if (!profilerDraft) return;
|
||||
profilerDraft.rules[i].fciMode = value;
|
||||
const r = profilerDraft.rules[i];
|
||||
r.fciMode = value;
|
||||
// Custom mode has no raw-FCI field: seed the data objects from the rule's
|
||||
// captured FCI when the list is still empty (the same all-'value' seeding
|
||||
// a profile created from a card or a snapshot gets, v3.25.8). A rule
|
||||
// without a captured FCI starts empty - rows are added by hand.
|
||||
if (value === 'custom' && !(r.fciDos || []).length && r.fciHex) {
|
||||
const dos = fcpDosFromFci(r.fciHex);
|
||||
if (dos.length) r.fciDos = dos;
|
||||
}
|
||||
profilerRenderEditor();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v441';
|
||||
const CACHE = 'simple-v442';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -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', 'fcpArrRuleOps', 'fcpArrRuleOpCovered', 'fcpArrRuleSatisfied', 'fcpArrRuleToggleBit', 'fcpArrRuleSetKind', 'fcpArrRuleSetSc', 'fcpArrRuleSetUq', 'fcpArrRuleAddSc', 'fcpArrRuleRemoveSc', 'profilerRenderAccessRulesEditor', 'profilerArrRuleHtml', 'profilerArrScSelect', 'profilerArrUqSelect', 'profilerArrToggleOpen', 'profilerArrShiftOpen', 'profilerAccessRulesDiffHtml', 'fcpDosFromFci', 'fcpDoName', 'profilerRenderFciDosEditor', 'profilerFciDoRowHtml', 'profilerFciDosDiffHtml', 'profilerSnapshotArr', 'efBytes', 'efHex', 'efTlv', 'efDecArr', 'efArrAmLabel', 'efArrSc', 'efArrAmDo', 'efFidFromPath',
|
||||
'fcpArrRef', 'fcpSeidName', 'fcpArrRules', 'fcpArrRefFromFci', 'fcpWithoutArrRef', 'fcpArrSplitTop', 'fcpArrKeyRefFromName', 'fcpArrKeyRefOptions', 'fcpArrScParse', 'fcpArrScEncode', 'fcpArrRuleParse', 'fcpArrRuleEncode', 'fcpArrRuleOps', 'fcpArrRuleOpCovered', 'fcpArrRuleSatisfied', 'fcpArrRuleToggleBit', 'fcpArrRuleSetKind', 'fcpArrRuleSetSc', 'fcpArrRuleSetUq', 'fcpArrRuleAddSc', 'fcpArrRuleRemoveSc', 'profilerRenderAccessRulesEditor', 'profilerArrRuleHtml', 'profilerArrScSelect', 'profilerArrUqSelect', 'profilerArrToggleOpen', 'profilerArrShiftOpen', 'profilerAccessRulesDiffHtml', 'fcpDosFromFci', 'fcpDoName', 'profilerRenderFciDosEditor', 'profilerFciDoRowHtml', 'profilerFciDosDiffHtml', 'profilerUpdateRuleFciMode', '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';
|
||||
@@ -1437,6 +1437,45 @@ test('the custom-FCI editor renders the data object rows', () => {
|
||||
delete global.t;
|
||||
});
|
||||
|
||||
test('switching a rule to Custom seeds the data objects from its FCI (v3.28.4)', () => {
|
||||
const FCI = '621A82054221000F0583026F4F8A01058B032F06028802004B8801B0';
|
||||
global.profilerRenderEditor = () => {};
|
||||
// a captured FCI with an empty list: the objects are seeded
|
||||
global.profilerDraft = { rules: [{ fciMode: 'exact', fciHex: FCI, fciDos: undefined }] };
|
||||
profilerUpdateRuleFciMode(0, 'custom');
|
||||
assert.strictEqual(global.profilerDraft.rules[0].fciMode, 'custom');
|
||||
const seeded = global.profilerDraft.rules[0].fciDos;
|
||||
assert.ok(Array.isArray(seeded) && seeded.length >= 5, JSON.stringify(seeded));
|
||||
assert.deepStrictEqual(seeded[0], { key: '82', mode: 'value', value: '4221000F05' });
|
||||
// an existing list is kept as it is
|
||||
const existing = [{ key: '82', mode: 'present' }];
|
||||
global.profilerDraft = { rules: [{ fciMode: 'exact', fciHex: FCI, fciDos: existing }] };
|
||||
profilerUpdateRuleFciMode(0, 'custom');
|
||||
assert.strictEqual(global.profilerDraft.rules[0].fciDos, existing);
|
||||
// no captured FCI: the list stays empty (rows are added by hand)
|
||||
global.profilerDraft = { rules: [{ fciMode: 'exact', fciHex: null, fciDos: undefined }] };
|
||||
profilerUpdateRuleFciMode(0, 'custom');
|
||||
assert.ok(!(global.profilerDraft.rules[0].fciDos || []).length);
|
||||
// switching away does not touch the list
|
||||
global.profilerDraft = { rules: [{ fciMode: 'custom', fciHex: FCI, fciDos: undefined }] };
|
||||
profilerUpdateRuleFciMode(0, 'exact');
|
||||
assert.ok(!(global.profilerDraft.rules[0].fciDos || []).length);
|
||||
delete global.profilerRenderEditor;
|
||||
delete global.profilerDraft;
|
||||
});
|
||||
|
||||
test('the raw-FCI field is exact-mode-only (v3.28.4)', () => {
|
||||
// the custom mode shows its own editor: the raw FCI is only the source for
|
||||
// the data objects, never the expectation
|
||||
assert.ok(/if \(fciMode === 'exact'\) \{\n\t\thtml \+= '<div class="mb-2 flex gap-3 items-start">'/.test(html),
|
||||
'the FCI hex field must be gated to the exact mode');
|
||||
assert.ok(!/fciMode === 'exact' \|\| fciMode === 'custom'/.test(html),
|
||||
'the custom mode must not render the exact-mode FCI field');
|
||||
// the auto-seed lives in the mode updater
|
||||
assert.ok(/if \(value === 'custom' && !\(r\.fciDos \|\| \[\]\)\.length && r\.fciHex\)/.test(html),
|
||||
'the Custom switch must seed the data objects');
|
||||
});
|
||||
|
||||
test('the custom FCI report block marks the expectations', () => {
|
||||
global.t = s => s;
|
||||
const out = profilerFciDosDiffHtml({ label: 'fci', mode: 'custom', ok: false, parseError: '',
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.28.3"
|
||||
version = "3.28.4"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -272,7 +272,7 @@ def parse_outfile(text, name='', algorithm16='3des2', tars=None, tar_keyset=None
|
||||
# Byte pairs, exactly like /api/verify-adm: an odd length is a half
|
||||
# byte and the verifier would always refuse the stored value
|
||||
# (v3.28.3).
|
||||
if re.fullmatch(r'(?:[0-9A-F]{2}){2,8}', adm):
|
||||
if re.fullmatch(presets.ADM_PATTERN, adm):
|
||||
entry['adm'] = adm
|
||||
else:
|
||||
skip('line %d: ADM is not 4-16 hex digits in byte pairs - dropped'
|
||||
|
||||
@@ -103,6 +103,10 @@ TAR_ROLE_DEFAULTS = {
|
||||
COUNTER_BITS = 40 # the SCP80 counter is 5 bytes (TS 31.115)
|
||||
COUNTER_WIDTH = 10 # ... shown as 10 hex digits
|
||||
KVN_MAX = 0x0F # keyset number '00' is reserved: no key set, no counter
|
||||
# The ADM's shape: 4-16 hex digits in byte pairs (the verifier pads it with 'f'
|
||||
# to the 8 CHV bytes, so an odd length or a longer value can never verify).
|
||||
# Shared by /api/verify-adm and the outfile import so the two cannot drift.
|
||||
ADM_PATTERN = r'(?:[0-9A-F]{2}){2,8}'
|
||||
|
||||
|
||||
def default_path():
|
||||
|
||||
@@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.28.3'
|
||||
VERSION = '3.28.4'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -9088,7 +9088,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
body = self._read_body()
|
||||
self._log_req(_redact_psk_fields(body))
|
||||
adm = re.sub(r'\s', '', str(body.get('adm') or '')).upper()
|
||||
if not re.fullmatch(r'(?:[0-9A-F]{2}){2,8}', adm):
|
||||
if not re.fullmatch(presets.ADM_PATTERN, adm):
|
||||
resp = {'ok': False, 'error': 'adm must be 4-16 hex digits'}
|
||||
self._send_json(resp, 400)
|
||||
self._log_resp(resp)
|
||||
|
||||
@@ -9,6 +9,7 @@ without silently destroying it.
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
@@ -610,5 +611,24 @@ class StoreInfoTests(unittest.TestCase):
|
||||
self.assertEqual(presets.default_path().parent.name, '.pysim-simple-server')
|
||||
|
||||
|
||||
class AdmPatternTests(unittest.TestCase):
|
||||
"""The ADM's shared shape (v3.28.3 review): one constant, used by both the
|
||||
outfile import and /api/verify-adm - two literals would drift and an
|
||||
imported ADM could silently become unverifiable."""
|
||||
|
||||
def test_the_pattern_is_byte_pairs_of_4_to_16_digits(self):
|
||||
for ok in ('1234', '3632333239463337', 'CFA3DD9C6B7F0F8C', '1a2b'):
|
||||
self.assertIsNotNone(re.fullmatch(presets.ADM_PATTERN, ok.upper()), ok)
|
||||
for bad in ('123', '12345', 'AB' * 9, 'ZZZZ', ''):
|
||||
self.assertIsNone(re.fullmatch(presets.ADM_PATTERN, bad), bad)
|
||||
|
||||
def test_both_users_reference_the_constant(self):
|
||||
root = pathlib.Path(__file__).resolve().parents[1]
|
||||
for rel in ('pysim_simple_server/outfile.py', 'pysim_simple_server/server.py'):
|
||||
src = (root / rel).read_text(encoding='utf-8')
|
||||
self.assertIn('presets.ADM_PATTERN', src)
|
||||
self.assertNotIn(r"'(?:[0-9A-F]{2}){2,8}'", src)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user