Правила выполняются последовательно. Редактор показывает символьное имя файла pySim (если известно) рядом с путём правила; Добавить правило добавляет правило, Сохранить сохраняет изменения. Правило файловой системы задаётся:
Путь — начинается с MF (например, MF/7F10/6F3A) или с AID ADF (например, A0000000871002/6F07).
-
Проверка FCP/FCI — какая часть информации об управлении файлом проверяется: Только тип файла (FCP) (существование + тип файла), Тип файла + размер (FCP) (добавляются размер файла либо длина/число записей для record-файлов) или Полный FCI (добавляется побайтовое сравнение сырого ответа SELECT — шаблона FCP '62' — выявляет изменения FID/AID, жизненного цикла, атрибутов безопасности и проприетарных параметров).
+
Проверка FCP/FCI — какая часть информации об управлении файлом проверяется: Только тип файла (FCP) (существование + тип файла), Тип файла + размер (FCP) (добавляются размер файла либо длина/число записей для record-файлов), Полный FCI (добавляется побайтовое сравнение сырого ответа SELECT — шаблона FCP '62' — выявляет изменения FID/AID, жизненного цикла, атрибутов безопасности и проприетарных параметров) или По объектам данных — список ожиданий по объектам: каждая строка задаёт объект данных (стандартные теги с именами, включая вложенные A5/xx/C6/xx, либо произвольный тег), режим — значение (без ? совпадение по префиксу, ? маскирует полубайт: 8B + 2F06 принимает любую запись EF.ARR с таким FID, 2F06?? — 3-байтовую форму), присутствует (объект обязан быть) или отсутствует (его быть не должно) — и для значений hex для сравнения. Проверяются только перечисленные объекты; Лишние объекты данных: игнорировать/не допускать дополнительно запрещает всё неперечисленное (без учёта порядка, в отличие от побайтового режима). Профили, созданные с этим режимом с карты или из снимка, заполняются объектами записанного FCI; правило с записанным FCI и пустым списком можно заполнить из него (заполнить из записанного FCI).
Правила доступа — проверяет условия доступа файла как декодированные правила, независимо от того, на какую запись EF.ARR указывает ссылка атрибута безопасности ('8B') в FCI: правила указанной записи разрешаются из EF.ARR карты (или снимка) и сравниваются как множество с захваченными строками. Захватываются автоматически при создании профиля с карты или из снимка для каждого файла, чей FCI содержит ссылку (собственное содержимое EF.ARR по умолчанию пропускается — значимая проверка это правила); в редакторе захваченные правила показываются структурированными строками — операция в виде флажков битов (или командной формы RESIZE FILE / INCREASE), а каждое условие безопасности — селектором (always, never или ссылка на ключ с её использованием), с предпросмотром канонической декодированной строки, которую сравнивает проверка; «+ правило» создаёт новое (READ always), а шаблонные формы вне этого набора остаются нередактируемыми строками с возможностью удаления. Селектор «Сопоставление» выбирает сравнение: «Точное» (по умолчанию) требует полного совпадения правил карты с перечисленными строками; «Содержит» требует, чтобы каждое перечисленное правило было разрешено картой, — сравнение по операциям (каждый бит AM / командная запись, поэтому другое объединение битов в байте AM при той же политике тоже совпадёт), а не перечисленное правило не проверяется. Переключатель «Нет/Проверять» отключает проверку, не теряя правила; блок сворачивается в заголовок со счётчиком правил, а «Нет» скрывает его полностью. Файл, чей FCI потерял ссылку, не проходит проверку; ссылка, которую не удалось разрешить (нет кэша EF.ARR, запись вне диапазона), помечается как непроверяемая. При Полном FCI необязательный флажок «Игнорировать ссылку на EF.ARR в точном FCI» сравнивает FCI без значения '8B': другой номер записи больше не приводит к несовпадению, а все остальные DO по-прежнему сравниваются побайтово (смысл ссылки проверяет эта проверка).
Атрибуты файла — тип файла, размер, длина записи и число записей из FCP-шаблона (любой можно оставить незаданным).
Проверить содержимое (опционально) — Точное (точное совпадение hex) или Маска, где ? — шаблон на один полубайт (маска без ? — совпадение префикса, например 0891 для MCC/MNC из IMSI). Для record-файлов хранится список по записям.
Проверить карту выполняет каждое правило на подключённой карте и показывает строку прогресса и отчёт прохождения. Рядом с путём файла указывается, что именно проверялось (например, тип файла и размер, содержимое или полный FCI); если часть проверок прошла, а часть нет — каждый аспект помечается (тип файла ✓, размер ✗, содержимое ✓), а расхождения расписываются ниже. Несовпавшие сырые данные (FCI, содержимое, данные записей) показываются как поля только для чтения с моноширинным шрифтом — ожидаемое над фактическим, в одной и той же колонке — для удобного сравнения; расхождения содержимого дополнительно показывают для файлов с декодером декодированное сравнение по полям (только отличающиеся поля, с заголовками колонок «ожидаемое/фактическое» над значениями; если декодированные значения совпадают, вместо таблицы выводится пометка об этом, а сторона, чья запись целиком из FF, показывает в своих ячейках Пусто (только FF)), а для расхождений FCI дополнительно показывается декодированное сравнение по параметрам (размер файла, дескриптор/структура, жизненный цикл, FID, SFI, проприетарные параметры…). Расхождения правил доступа выводят декодированные строки правил (✓ совпадает, − отсутствует, + лишнее). Декодированный просмотр FCI также отображается рядом с полем FCI hex при редактировании правила. Если данные FCI повреждены, показывается всё, что удалось декодировать до места ошибки, вместе с явным сообщением об ошибке декодирования. Для record-файлов при расхождении содержимого добавляется пометка совпадающие записи: 1-5, 7-10 со списком записей, которые совпали. В отчёте поля расхождений и колонки сравнения FCI подписаны ожидалось (имя профиля) и фактически (ICCID карты), а в заголовке отчёта выводится Результаты проверки профиля: <профиль> → <ICCID карты>. Опция «Только расхождения» в заголовке отчёта скрывает все совпавшие файлы и оставляет только несовпадения и ошибки.
Опции сканирования «Профиль с карты»
-
Диалог сканирования запрашивает имя профиля и предлагает селектор «Проверка FCP/FCI» (те же три режима, по умолчанию Тип файла + размер (FCP)), применяемый ко всем создаваемым правилам, а также список «Игнорировать содержимое файлов» (все отмечены по умолчанию, кроме EF.ARR; флажок в заголовке отмечает или снимает весь список) часто перезаписываемых файлов, содержимое которых пропускается: EF.LOCI, EF.PSLOCI, EF.EPSLOCI, EF.5GS3GPPLOCI, EF.Keys, EF.KeysPS, EF.SMS, EF.Kc, EF.KcGPRS, EF.LOCIGPRS, EF.CBMID, EF.SMSS, EF.ACC, EF.EPSNSC, EF.START-HFN, EF.ARR. Ещё две отмеченные по умолчанию опции «Сравнивать первые 4 байта для»EF.IMSI и EF.ICCID захватывают содержимое этих файлов как маску только первых 4 байт (снимите для точного сравнения). Строка прогресса показывает N / всего файлов с текущим путём файла во время сканирования; при сканировании опции скрываются, а кнопки блокируются. Правила создаются только для файлов, которые реально существуют на карте (возвращён FCP-шаблон); отсутствующие файлы пропускаются. Пользовательские файлы из подвкладки «Пользовательские файлы» включаются с той же проверкой существования. Тот же диалог и опции использует «Профиль из снимка»: вместо карты обходятся захваченные файлы выбранного снимка; для файлов, содержимое которых не было захвачено, правило создаётся без проверки содержимого (при последующей проверке профиля они помечаются как непроверяемые).
+
Диалог сканирования запрашивает имя профиля и предлагает селектор «Проверка FCP/FCI» (те же четыре режима, по умолчанию Тип файла + размер (FCP); выбор По объектам данных заполняет каждое правило объектами записанного FCI — их можно ослабить в редакторе), применяемый ко всем создаваемым правилам, а также список «Игнорировать содержимое файлов» (все отмечены по умолчанию, кроме EF.ARR; флажок в заголовке отмечает или снимает весь список) часто перезаписываемых файлов, содержимое которых пропускается: EF.LOCI, EF.PSLOCI, EF.EPSLOCI, EF.5GS3GPPLOCI, EF.Keys, EF.KeysPS, EF.SMS, EF.Kc, EF.KcGPRS, EF.LOCIGPRS, EF.CBMID, EF.SMSS, EF.ACC, EF.EPSNSC, EF.START-HFN, EF.ARR. Ещё две отмеченные по умолчанию опции «Сравнивать первые 4 байта для»EF.IMSI и EF.ICCID захватывают содержимое этих файлов как маску только первых 4 байт (снимите для точного сравнения). Строка прогресса показывает N / всего файлов с текущим путём файла во время сканирования; при сканировании опции скрываются, а кнопки блокируются. Правила создаются только для файлов, которые реально существуют на карте (возвращён FCP-шаблон); отсутствующие файлы пропускаются. Пользовательские файлы из подвкладки «Пользовательские файлы» включаются с той же проверкой существования. Тот же диалог и опции использует «Профиль из снимка»: вместо карты обходятся захваченные файлы выбранного снимка; для файлов, содержимое которых не было захвачено, правило создаётся без проверки содержимого (при последующей проверке профиля они помечаются как непроверяемые).
Снимки карт
Представление списка имеет три вкладки — «Профили», «Снимки карт» и «Пользовательские файлы» (см. ниже). Снимок карты — неизменяемая фиксация файловой системы карты: для каждого существующего файла сохраняются путь, символьное имя, тип, размер (или длина/число записей), сырой FCI из ответа SELECT и содержимое, если файл читается (без списка игнорирования и без масок). ICCID декодируется из EF.ICCID и показывается рядом с именем снимка. Захваченное содержимое показывается вместе с декодированным представлением, где есть декодер — таблица полей для transparent-файлов и однострочная сводка по каждой записи для record-файлов. При сканировании измеряется время каждой команды карты (SELECT, READ BINARY, READ RECORD) от отправки до ответа; снимок хранит min/сред/max по каждому типу команд и общее время сканирования, а в представлении эти значения показываются в сводке под заголовком, время select/read — для каждого файла и время чтения — для каждой записи. Время носит информационный характер и не используется при проверках и сравнении.
Rules run sequentially. The editor shows the symbolic pySim name (when known) next to each rule’s path; use Add rule to append one and Save to keep the changes. A filesystem rule is defined by:
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).
+
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), 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), or Custom (per data object) — a list of per-object expectations: each row names a data object (the standard tags with their names, the nested A5/xx/C6/xx children included, or a free tag), the mode — value (no ? matches as a prefix, ? masks a nibble: 8B + 2F06 accepts any EF.ARR record of that FID, 2F06?? the 3-byte form), present (the object must exist) or absent (it must not) — and, for values, the hex to compare. Only the listed objects are checked; Extra data objects: ignore/fail 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’s objects; a rule with a captured FCI and an empty list can be filled from it (load from the captured FCI).
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 Match selector picks the comparison: Exact (default) requires the card’s rules to equal the listed lines; Contains requires every listed rule to be granted by the card, matched per operation (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 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.
Check card runs every rule against the equipped card and shows a live progress line plus a pass/fail report. Each row states exactly what was verified next to the file path (e.g. filetype and size, contents or exact FCI); when some checks pass and others fail, each aspect is marked (filetype ✓, size ✗, contents ✓) with the mismatches detailed below. Mismatched raw data (FCI, contents, record data) is shown as read-only monospace fields — expected above actual, aligned in the same column — for easy comparison; content mismatches additionally show, for files with a decoder, a decoded per-field comparison (only the differing fields, with expected/actual column headers above the side-by-side values; when the decoded values match, a note says so instead of the table, and a side whose record is all-FF shows Empty (all FF) in its cells), and FCI mismatches additionally show a decoded per-parameter comparison (file size, file descriptor/structure, life cycle, FID, SFI, proprietary parameters…). Access-rule mismatches list the decoded rule lines (✓ matching, − missing, + extra). A decoded FCI preview is also shown beside the FCI hex field while editing a rule. If the FCI data is corrupt, whatever was decoded before the faulty part is shown together with an explicit decode-failure note. For record files with a contents mismatch, a matching records: 1-5, 7-10 note lists the records that did match. In the report the mismatch fields and FCI comparison columns are labelled expected (profile name) and actual (card ICCID), and the results header reads Profile verification results for: <profile> → <card ICCID>. An Only mismatches option in the results header hides all passing files and keeps only failures and errors.
“Profile from card” scan options
-
The scan dialog asks for a profile name and offers a “FCP/FCI check” selector (the same three modes above, default Filetype + size) applied to every generated rule, plus an “Ignore contents of files” checklist (all checked by default except EF.ARR; the header checkbox checks or unchecks the whole list) of frequently-overwritten files whose contents are skipped: EF.LOCI, EF.PSLOCI, EF.EPSLOCI, EF.5GS3GPPLOCI, EF.Keys, EF.KeysPS, EF.SMS, EF.Kc, EF.KcGPRS, EF.LOCIGPRS, EF.CBMID, EF.SMSS, EF.ACC, EF.EPSNSC, EF.START-HFN, EF.ARR. Two further checked-by-default options “Match first 4 bytes for”EF.IMSI and EF.ICCID capture those files’ contents as a mask of only the first 4 bytes (uncheck for exact matching). A progress line shows N / total files with the current file path while scanning; during the scan the options are hidden and the buttons are locked. Rules are created only for files that actually exist on the card (a FCP template is returned); missing files are skipped. Custom files from the Custom files sub-tab are included under the same existence check. The same dialog and options are used by Profile from snapshot, which walks the selected snapshot’s captured files instead of the card; rules for files whose contents were not captured during the scan get no content check (they are reported as unverifiable when the profile is later checked).
+
The scan dialog asks for a profile name and offers a “FCP/FCI check” selector (the same four modes above, default Filetype + size; choosing Custom (per data object) seeds every rule with the file’s captured data objects, ready to relax in the editor) applied to every generated rule, plus an “Ignore contents of files” checklist (all checked by default except EF.ARR; the header checkbox checks or unchecks the whole list) of frequently-overwritten files whose contents are skipped: EF.LOCI, EF.PSLOCI, EF.EPSLOCI, EF.5GS3GPPLOCI, EF.Keys, EF.KeysPS, EF.SMS, EF.Kc, EF.KcGPRS, EF.LOCIGPRS, EF.CBMID, EF.SMSS, EF.ACC, EF.EPSNSC, EF.START-HFN, EF.ARR. Two further checked-by-default options “Match first 4 bytes for”EF.IMSI and EF.ICCID capture those files’ contents as a mask of only the first 4 bytes (uncheck for exact matching). A progress line shows N / total files with the current file path while scanning; during the scan the options are hidden and the buttons are locked. Rules are created only for files that actually exist on the card (a FCP template is returned); missing files are skipped. Custom files from the Custom files sub-tab are included under the same existence check. The same dialog and options are used by Profile from snapshot, which walks the selected snapshot’s captured files instead of the card; rules for files whose contents were not captured during the scan get no content check (they are reported as unverifiable when the profile is later checked).
Card snapshots
The list view has three tabs — Profiles, Card snapshots and Custom files (see below). A card snapshot is an immutable capture of the card filesystem: for every existing file it stores the path, symbolic name, file type, size (or record length/count), the raw FCI from the SELECT response, and the contents whenever the file is readable (no ignore list, no masking). The ICCID is decoded from EF.ICCID and shown next to the snapshot name. Captured contents are shown with their decoded form where a decoder exists — a field table for transparent files and a one-line summary per record for record files. The scan also measures each card command (SELECT, READ BINARY, READ RECORD) from command to response; the snapshot stores min/avg/max per command type and the total scan time, and the view shows these in the summary under the title plus the select/read times per file and the read time per record. Timings are informational only and are not used by checks or comparisons.
diff --git a/frontend/index.html b/frontend/index.html
index df602c3..04f27cb 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1833,6 +1833,7 @@
+
@@ -1906,7 +1907,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.25.7';
+const SIMPLE_VERSION = '3.25.8';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== view history (the browser Back/Forward buttons) =====
@@ -18956,7 +18957,19 @@ function profilerValidateProfile(obj) {
if (!r || typeof r !== 'object') return 'Invalid rule';
if (r.type !== 'file') return 'Unsupported rule type: ' + r.type;
if (!r.path) return 'Rule missing path';
- if (r.fciMode && !['type', 'type_size', 'exact'].includes(r.fciMode)) return 'Invalid FCP/FCI mode: ' + r.fciMode;
+ if (r.fciMode && !['type', 'type_size', 'exact', 'custom'].includes(r.fciMode)) return 'Invalid FCP/FCI mode: ' + r.fciMode;
+ if (r.fciDos !== undefined && r.fciDos !== null) {
+ if (!Array.isArray(r.fciDos)) return 'Invalid FCI data objects: ' + r.path;
+ for (const d of r.fciDos) {
+ if (!d || typeof d !== 'object') return 'Invalid FCI data object: ' + r.path;
+ if (!/^[0-9A-F]{2}(?:[0-9A-F]{2})?(?:\/[0-9A-F]{2}(?:[0-9A-F]{2})?)?$/.test(String(d.key || '').toUpperCase()))
+ return 'Invalid FCI data object tag: ' + r.path;
+ if (!['value', 'present', 'absent'].includes(d.mode)) return 'Invalid FCI data object mode: ' + r.path;
+ if (d.mode === 'value' && !/^[0-9A-F?]{2,}$/.test(String(d.value || '').toUpperCase()))
+ return 'Invalid FCI data object value: ' + r.path;
+ }
+ }
+ if (r.fciExtra !== undefined && !['ignore', 'fail'].includes(r.fciExtra)) return 'Invalid FCI extra data object policy: ' + r.path;
if (r.accessRules !== undefined && r.accessRules !== null) {
const ar = r.accessRules;
if (typeof ar !== 'object' || !Array.isArray(ar.expected) ||
@@ -19325,6 +19338,12 @@ function profilerBuildFileRuleFromSnapshot(f, ignoreFids, ignoreNames, fciMode,
fciHex: f.fciHex || null,
content: null,
};
+ // Custom FCI (v3.25.8): seed the per-data-object expectations from the
+ // captured FCI - value-exact at first, relaxed in the editor.
+ if (rule.fciMode === 'custom') {
+ const dos = fcpDosFromFci(rule.fciHex);
+ if (dos.length) rule.fciDos = dos;
+ }
// Access rules from the snapshot's own EF.ARR copies (v3.25.0)
const ref = fcpArrRefFromFci(rule.fciHex);
if (ref) {
@@ -19385,6 +19404,12 @@ async function profilerBuildFileRule(path, c, ignoreFids, ignoreNames, fciMode,
fciHex: sel.fci_hex || null,
content: null,
};
+ // Custom FCI (v3.25.8): seed the per-data-object expectations from the
+ // captured FCI - value-exact at first, relaxed in the editor.
+ if (rule.fciMode === 'custom') {
+ const dos = fcpDosFromFci(rule.fciHex);
+ if (dos.length) rule.fciDos = dos;
+ }
// Access rules (v3.25.0): resolve the FCP's '8B' reference against the
// card's cached EF.ARR copies. Captured before the ignore list - the
// rules are the meaningful per-file check (contents may be ignored).
@@ -19799,6 +19824,7 @@ function profilerRenderRule(i, r) {
'' +
'' +
'' +
+ '' +
'';
html += '
' +
'
';
@@ -19813,7 +19839,7 @@ function profilerRenderRule(i, r) {
'';
}
html += '';
- if (fciMode === 'exact') {
+ if (fciMode === 'exact' || fciMode === 'custom') {
html += '
';
html += '
' +
'
';
@@ -19821,6 +19847,9 @@ function profilerRenderRule(i, r) {
'
' + profilerFciPreviewItems(r.fciHex || '') + '
';
html += '';
}
+ if (fciMode === 'custom') {
+ html += profilerRenderFciDosEditor(i, r);
+ }
if (r.fileType !== 'df') {
html += profilerRenderContentEditor(i, r);
}
@@ -19829,6 +19858,114 @@ function profilerRenderRule(i, r) {
return html;
}
+// Custom-FCI editor (v3.25.8): a per-data-object expectation list. Profiles
+// created from a card or a snapshot with fciMode 'custom' are seeded with the
+// captured FCI's objects (all 'value', the captured bytes); a rule with a
+// captured FCI but an empty list can be filled from it here. Only the listed
+// objects are checked; `present`/`absent` demand or forbid one, and a value
+// without '?' matches as a prefix ('?' masks a nibble).
+function profilerRenderFciDosEditor(i, r) {
+ const dos = r.fciDos || [];
+ let html = '
';
+ html += '
';
+ html += '' + esc(t('Data objects (FCI)')) + (dos.length ? ' (' + dos.length + ')' : '') + '';
+ if (!dos.length && r.fciHex) {
+ html += '';
+ }
+ html += '';
+ html += '
';
+ html += '';
+ for (let di = 0; di < dos.length; di++) html += profilerFciDoRowHtml(i, di, dos[di]);
+ if (!dos.length) {
+ html += '
' + esc(t('No data objects listed - the FCI is not checked.')) + '
';
+ }
+ html += '';
+ html += '
';
+ return html;
+}
+
+// One expectation row: the tag (with the known keys suggested), the mode, and
+// the value for the 'value' mode.
+function profilerFciDoRowHtml(i, di, d) {
+ const mode = (d.mode === 'present' || d.mode === 'absent') ? d.mode : 'value';
+ let html = '
';
+ html += '';
+ html += '';
+ if (mode === 'value') {
+ html += '';
+ }
+ html += '' + esc(fcpDoName(d.key)) + '';
+ html += '';
+ html += '
';
+ return html;
+}
+
+function profilerFciDosAdd(i) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (!r.fciDos) r.fciDos = [];
+ r.fciDos.push({ key: '82', mode: 'value', value: '' });
+ profilerRenderEditor();
+}
+
+function profilerFciDoRemove(i, di) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (!r.fciDos) return;
+ r.fciDos.splice(di, 1);
+ if (!r.fciDos.length) delete r.fciDos;
+ profilerRenderEditor();
+}
+
+function profilerFciDoSetKey(i, di, v) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (!r.fciDos || !r.fciDos[di]) return;
+ r.fciDos[di].key = String(v || '').trim().toUpperCase();
+ const el = document.getElementById('profiler-fci-do-name-' + i + '-' + di);
+ if (el) el.textContent = fcpDoName(r.fciDos[di].key);
+}
+
+function profilerFciDoSetMode(i, di, v) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (!r.fciDos || !r.fciDos[di]) return;
+ r.fciDos[di].mode = (v === 'present' || v === 'absent') ? v : 'value';
+ profilerRenderEditor();
+}
+
+function profilerFciDoSetValue(i, di, v) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (!r.fciDos || !r.fciDos[di]) return;
+ r.fciDos[di].value = String(v || '').trim().toUpperCase();
+}
+
+function profilerSetFciExtra(i, v) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ if (v === 'fail') r.fciExtra = 'fail';
+ else delete r.fciExtra;
+}
+
+// Fill an empty list from the rule's own captured FCI (never the card - the
+// rows come from what the profile already stores; v3.25.8).
+function profilerFciDosLoad(i) {
+ if (!profilerDraft) return;
+ const r = profilerDraft.rules[i];
+ const dos = fcpDosFromFci(r.fciHex);
+ if (!dos.length) return;
+ r.fciDos = dos;
+ profilerRenderEditor();
+}
+
// The expanded access-rule blocks of the profile editor (transient UI state,
// kept across the editor re-renders; None hides the block entirely; a fresh
// draft resets it and a deleted rule shifts the keys).
@@ -20457,6 +20594,32 @@ async function profilerRunRule(r, source) {
if (!ok) res.status = 'fail';
res.checks.push({ label: 'fci', expected: r.fciHex, actual: sel.fci_hex || '', ok: ok, ignoredRef: ignoredRef });
}
+ // Custom FCI (v3.25.8): a per-data-object expectation list - only the
+ // listed objects are checked (not listed = not checked); `value` reuses
+ // the contents vocabulary (no '?' = prefix, '?' = nibble mask), `present`
+ // demands the object, `absent` forbids it, and `fciExtra: 'fail'` also
+ // fails on any object the card carries although it is not listed
+ // (order-insensitive strictness).
+ if (fciMode === 'custom' && (r.fciDos || []).length) {
+ const dec = fcpDecode(sel.fci_hex || '');
+ const dos = [];
+ for (const d of r.fciDos) {
+ const hits = dec.items.filter(it => it.key === d.key);
+ let ok;
+ if (d.mode === 'absent') ok = hits.length === 0;
+ else if (d.mode === 'present') ok = hits.length > 0;
+ else ok = hits.some(it => profilerMatch('mask', d.value || '', it.value));
+ dos.push({ key: d.key, name: fcpDoName(d.key), mode: d.mode, value: d.value || '',
+ ok: ok, actual: hits.map(it => it.value) });
+ }
+ const listed = {};
+ r.fciDos.forEach(d => { listed[d.key] = true; });
+ const extra = r.fciExtra === 'fail' ? dec.items.filter(it => !listed[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,
+ parseError: dec.error || '', expected: r.fciDos, actual: dec.items, ok: ok });
+ }
// Access rules (v3.25.0): the decoded conditions of the EF.ARR record the
// FCI's '8B' reference points at - record-number independent, so cards
// with a different EF.ARR layout still verify.
@@ -20556,13 +20719,13 @@ function profilerCustomNameForPath(path) {
// that actually ran. 'Exact FCI' subsumes filetype/size/records; 'exists' is
// implicit and omitted.
function profilerResultAspects(res) {
- const groups = { filetype: [], size: [], records: [], 'Exact FCI': [], contents: [], accessRules: [] };
+ const groups = { filetype: [], size: [], records: [], 'Exact FCI': [], 'FCI (custom)': [], contents: [], accessRules: [] };
for (const c of (res.checks || [])) {
const l = c.label;
if (l === 'fileType') groups.filetype.push(c);
else if (l === 'fileSize') groups.size.push(c);
else if (l === 'recordLen' || l === 'numRecords') groups.records.push(c);
- else if (l === 'fci') groups['Exact FCI'].push(c);
+ else if (l === 'fci') groups[c.mode === 'custom' ? 'FCI (custom)' : 'Exact FCI'].push(c);
else if (l === 'content' || l.startsWith('content.')) groups.contents.push(c);
else if (l === 'accessRules') groups.accessRules.push(c);
}
@@ -20574,6 +20737,7 @@ function profilerResultAspects(res) {
if (groups.size.length) out.push({ key: 'size', ok: groups.size.every(c => c.ok) });
if (groups.records.length) out.push({ key: 'records', ok: groups.records.every(c => c.ok) });
}
+ if (groups['FCI (custom)'].length) out.push({ key: 'FCI (custom)', ok: groups['FCI (custom)'].every(c => c.ok) });
if (groups.contents.length) out.push({ key: 'contents', ok: groups.contents.every(c => c.ok) });
if (groups.accessRules.length) out.push({ key: 'Access rules', ok: groups.accessRules.every(c => c.ok) });
return out;
@@ -21178,6 +21342,32 @@ function fcpDecode(hex) {
return { ok: !error && items.length > 0, template, items, error };
}
+// The data object keys the FCI decoder names (TS 102 221 11.1.1.4 + the
+// A5/C6 children) - the custom-FCI editor's tag suggestions (v3.25.8).
+const FCP_DO_KEYS = [
+ '80', '81', '82', '83', '84', '85', '86', '87', '88', '8A', '8B', '8C',
+ '8D', '8E', 'A0', 'A5', 'AB', 'C6',
+ 'A5/80', 'A5/81', 'A5/82', 'A5/83', 'A5/84', 'A5/85', 'A5/86', 'A5/87',
+ 'A5/88', 'A5/89', 'A5/8A', 'A5/8B', 'A5/C0', 'A5/C1', 'A5/C2',
+ 'C6/90', 'C6/95', 'C6/83',
+];
+
+// A data object's display name ('' for an empty or unknown key).
+function fcpDoName(key) {
+ const k = String(key || '').toUpperCase();
+ if (!k) return '';
+ const n = fcpDo(k, '').name || '';
+ return /^Unknown TLV/.test(n) ? '' : n;
+}
+
+// The decoded data objects of an FCI as custom-check rows: one 'value'
+// expectation per object, the captured bytes - profiles created from a card
+// or a snapshot with fciMode 'custom' are seeded with these (v3.25.8).
+function fcpDosFromFci(hex) {
+ const dec = fcpDecode(hex || '');
+ return dec.items.map(it => ({ key: it.key, mode: 'value', value: it.value }));
+}
+
function profilerLabelText(labels, which) {
if (!labels || !labels[which]) return t(which);
return labels.prefix ? t(which) + ' (' + labels[which] + ')' : labels[which];
@@ -21320,6 +21510,38 @@ function profilerAccessRulesDiffHtml(c) {
return html + '';
}
+// The custom FCI's expectation rows in the report (v3.25.8): the same marks
+// as the access-rules diff - '−' a failed expectation, '+' an object the card
+// carries although fciExtra forbids it.
+function profilerFciDosDiffHtml(c) {
+ let html = '
';
+ html += '
' + esc(t('FCI (custom)')) + '
';
+ if (c.parseError) {
+ html += '
' +
+ esc(t('the FCI could not be parsed') + ': ' + c.parseError) + '