v3.29.1: the per-row keysets in the outfile preview
The import applied one global default keyset to every TAR entry of every imported card. The preview's TAR table (TAR + MSL, the three role rows + "+ TAR") now has a keyset select per row, mirroring the preset editor: an empty "— none —" plus the keysets found in the file with their KIc/KID, the first preselected, and the editor's amber warning when a non-zero MSL has no keyset. Each TAR's packets then use exactly the chosen keyset. - PWA: the global "Default keyset for the TARs" select (and the request's tar_keyset) is gone; cardsOutfileTarsFromDom/FromForm carry each row's keyset; cardsOutfileKeysets() became cardsOutfileKeysetOptions() (the union with the KIc/KID bytes for the labels). - Server: a `tars` entry's own `keyset` (1-15; an unusable value is a 400) wins, then the optional global `tar_keyset` (the API shorthand), then the card's first keyset - parse_outfile no longer clobbers per-entry keysets with the card-level fallback. presets.normalise_keyset_ref() is module-level (one rule for the store and the parser). - Docs: help EN/RU, api.md (the tars entry keyset + the global fallback), the repo AGENTS. Tests: the per-entry/global/card fallbacks, the bad-keyset 400, the options helper, the row select and round-trip, the import body. 925 frontend + 944 Python; sw cache simple-v448.
This commit is contained in:
@@ -371,7 +371,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> — список из найденных в файле keysets (первый выбран заранее; при ненулевом MSL keyset обязателен и помечается жёлтым), поэтому пакеты к каждому TAR используют именно выбранный keyset. <strong>Общее имя</strong> применяется ко всем импортируемым картам (пустое — берётся ICCID); <strong>режим импорта</strong> выбирает <strong>Дополнить</strong> (по умолчанию — существующие пресеты сохраняются, дубли ICCID пропускаются и перечисляются) или <strong>Заменить</strong> (хранилище сначала очищается). Предпросмотр перечисляет разобранные карты (ICCID, keysets, найденные коды) и каждую пропущенную строку/ключ с причиной — до того, как что-либо сохранено.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+1
-1
@@ -370,7 +370,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-sm mb-3">The <strong>SCP81</strong> column shows whether the preset supplies a usable PSK pair: <strong>✓</strong> (green) — identity and key set; <strong>⚠</strong> (amber) — only one of the two, so the listener ignores the preset; <strong>—</strong> — no PSK. PSK identity and key must be set together. <strong>Add preset</strong> opens the editor with a fresh preset; <strong>Edit</strong> loads the row's preset into it so fields can be changed without re-entering the card; <strong>Save</strong> stores it and returns to the list, <strong>Cancel</strong> discards the edits; <strong>Remove</strong> deletes the row (list only). Presets can be shared with <strong>Export to file</strong> and restored with <strong>Import from file</strong>. The selected card preset auto-fills the Secured Packet form and sets the TAR for the operation (RAM/GP → ISD TAR, SIM RFM → UICC RFM TAR, USIM RFM → ADF RFM TAR); a TAR typed into the Secured Packet form by hand is kept when sending - the counter, keys and the TAR's MSL are still re-read from the preset; the SPI1 is seeded from the TAR's MSL and a lower value warns (the server returns it as a <code class="font-mono text-sm">warning</code> too); edits are pushed into a running SCP81 listener automatically.</p>
|
||||
<p class="text-sm mb-3"><strong>Import outfile</strong> imports many cards at once from a card vendor's <em>outfile</em> — the personalization tool's plain-text dump: a banner and a configuration header, then a whitespace-separated <strong>column line</strong> (the tokens include <code class="font-mono text-sm">ICCID</code>; the first token may carry a <code class="font-mono text-sm">var_out:</code>-style prefix) and one row per card. Everything before the column line and every unused column is ignored. The parser reads <code class="font-mono text-sm">ICCID</code> (digits, the digits with an odd-nibble <code class="font-mono text-sm">F</code> pad, spaced digits or the raw EF hex — all normalised to one form), <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> (the key's hex value, 4–16 hex digits in byte pairs — e.g. <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 bytes; an optional <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> names its identity, defaulting to the card's ICCID) and the numbered key pairs <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — one keyset per pair, up to 15. The <strong>KIc/KID bytes are never in the file</strong>: the keyset number is the column number (<code class="font-mono text-sm">KIC2</code> → keyset 2) and the algorithm nibble follows the key size — 8 bytes DES, 24 bytes 3DES3, and for <strong>16 bytes</strong> the preview's choice, <strong>3DES2</strong> (default) or <strong>AES</strong>; any other size skips that keyset with a note. The TARs never come from the file either: the preview shows the TAR table the imported cards get (the three role entries with their spec TARs and MSL 16 by default, editable, with <strong>+ TAR</strong> for extra entries) and one <strong>default keyset</strong> applied to every TAR entry — required for a card with several keysets (a non-zero MSL must name a keyset). A <strong>common name</strong> is applied to every imported card (empty falls back to the ICCID); <strong>Import mode</strong> chooses <strong>Merge</strong> (default — existing presets are kept, duplicate ICCIDs are skipped and reported) or <strong>Replace</strong> (the store is wiped first). The preview lists the parsed cards (ICCID, keysets, the codes found) and every skipped row/key with its reason before anything is stored.</p>
|
||||
<p class="text-sm mb-3"><strong>Import outfile</strong> imports many cards at once from a card vendor's <em>outfile</em> — the personalization tool's plain-text dump: a banner and a configuration header, then a whitespace-separated <strong>column line</strong> (the tokens include <code class="font-mono text-sm">ICCID</code>; the first token may carry a <code class="font-mono text-sm">var_out:</code>-style prefix) and one row per card. Everything before the column line and every unused column is ignored. The parser reads <code class="font-mono text-sm">ICCID</code> (digits, the digits with an odd-nibble <code class="font-mono text-sm">F</code> pad, spaced digits or the raw EF hex — all normalised to one form), <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> (the key's hex value, 4–16 hex digits in byte pairs — e.g. <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 bytes; an optional <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> names its identity, defaulting to the card's ICCID) and the numbered key pairs <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — one keyset per pair, up to 15. The <strong>KIc/KID bytes are never in the file</strong>: the keyset number is the column number (<code class="font-mono text-sm">KIC2</code> → keyset 2) and the algorithm nibble follows the key size — 8 bytes DES, 24 bytes 3DES3, and for <strong>16 bytes</strong> the preview's choice, <strong>3DES2</strong> (default) or <strong>AES</strong>; any other size skips that keyset with a note. The TARs never come from the file either: the preview shows the TAR table the imported cards get (the three role entries with their spec TARs and MSL 16 by default, editable, with <strong>+ TAR</strong> for extra entries) and <strong>every row carries its own keyset</strong> — a select over the keysets found in the file (the first one is preselected; a non-zero MSL needs one, marked amber), so each TAR's packets use exactly the keyset you choose. A <strong>common name</strong> is applied to every imported card (empty falls back to the ICCID); <strong>Import mode</strong> chooses <strong>Merge</strong> (default — existing presets are kept, duplicate ICCIDs are skipped and reported) or <strong>Replace</strong> (the store is wiped first). The preview lists the parsed cards (ICCID, keysets, the codes found) and every skipped row/key with its reason before anything is stored.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+34
-29
@@ -1928,7 +1928,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.29.0';
|
||||
const SIMPLE_VERSION = '3.29.1';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== view history (the browser Back/Forward buttons) =====
|
||||
@@ -14856,7 +14856,7 @@ function cardsImportFile(input) {
|
||||
// shows the preview plus the controls: the common name, the 16-byte key
|
||||
// algorithm, the TAR table and its one global default keyset - the final call
|
||||
// imports with a mode.
|
||||
let _cardsOutfile = null; // {text, file, name, algorithm16, tarKeyset, mode, preview}
|
||||
let _cardsOutfile = null; // {text, file, name, algorithm16, mode, preview}
|
||||
let _cardsOutfileTars = 0; // the free TAR rows of the panel
|
||||
|
||||
function cardsOutfilePick() {
|
||||
@@ -14879,7 +14879,7 @@ async function cardsOutfileFile(input) {
|
||||
}
|
||||
_cardsOutfile = { text: text, file: String(file.name || ''),
|
||||
name: String(file.name || '').replace(/\.[^.]+$/, ''),
|
||||
algorithm16: '3des2', tarKeyset: null, mode: 'merge', preview: null };
|
||||
algorithm16: '3des2', mode: 'merge', preview: null };
|
||||
_cardsOutfileTars = 0;
|
||||
cardsOutfileRender();
|
||||
await cardsOutfilePreview();
|
||||
@@ -14905,8 +14905,6 @@ function cardsOutfileControls() {
|
||||
if (document.getElementById('cards-of-mode')) {
|
||||
o.mode = val('cards-of-mode') === 'replace' ? 'replace' : 'merge';
|
||||
}
|
||||
const ks = parseInt(val('cards-of-keyset'), 10);
|
||||
if (Number.isInteger(ks) && ks > 0) o.tarKeyset = ks;
|
||||
}
|
||||
|
||||
// The TAR table as the panel shows it: the three role rows (their defaults
|
||||
@@ -14917,17 +14915,21 @@ function cardsOutfileTarsFromDom() {
|
||||
const el = document.getElementById(id);
|
||||
return el ? String(el.value).trim() : '';
|
||||
};
|
||||
const ks = i => {
|
||||
const v = parseInt(get('cards-of-tar-' + i + '-keyset'), 10);
|
||||
return Number.isInteger(v) && v > 0 ? v : null;
|
||||
};
|
||||
const out = CARDS_TAR_ROLES.map((role, i) => ({
|
||||
role: role,
|
||||
tar: get('cards-of-tar-' + i + '-tar').replace(/\s/g, '').toUpperCase(),
|
||||
msl: get('cards-of-tar-' + i + '-msl').replace(/\s/g, '').toUpperCase(),
|
||||
desc: '',
|
||||
desc: '', keyset: ks(i),
|
||||
}));
|
||||
for (let i = 0; i < _cardsOutfileTars; i++) {
|
||||
const idx = CARDS_TAR_ROLES.length + i;
|
||||
out.push({ tar: get('cards-of-tar-' + idx + '-tar').replace(/\s/g, '').toUpperCase(),
|
||||
msl: get('cards-of-tar-' + idx + '-msl').replace(/\s/g, '').toUpperCase(),
|
||||
desc: get('cards-of-tar-' + idx + '-desc') });
|
||||
desc: get('cards-of-tar-' + idx + '-desc'), keyset: ks(idx) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -14942,7 +14944,7 @@ function cardsOutfileTarsFromForm() {
|
||||
const e = rows[i];
|
||||
if (e.role) {
|
||||
out.push({ role: e.role, tar: e.tar || cardsRoleDefault(e.role),
|
||||
msl: e.msl || CARDS_MSL_DEFAULT, desc: '' });
|
||||
msl: e.msl || CARDS_MSL_DEFAULT, desc: '', keyset: e.keyset || null });
|
||||
} else if (e.tar || e.msl || e.desc) {
|
||||
out.push(e);
|
||||
}
|
||||
@@ -14953,9 +14955,24 @@ function cardsOutfileTarsFromForm() {
|
||||
function cardsOutfileTarRowHtml(e, i) {
|
||||
const cls = 'border border-gray-300 dark:border-slate-600 text-xs rounded px-1.5 py-0.5 dark:bg-slate-800 font-mono';
|
||||
const role = CARDS_TAR_ROLES[i] || '';
|
||||
// The keyset this TAR's packets use: one select per row, like the preset
|
||||
// editor (v3.29.1); it defaults to the first parsed keyset, and a non-zero
|
||||
// MSL without one gets the amber marker.
|
||||
const opts = cardsOutfileKeysetOptions();
|
||||
const sel = e.keyset ? Number(e.keyset) : (opts.length ? opts[0].v : null);
|
||||
const msl = String(e.msl || '').toUpperCase();
|
||||
let html = '<div class="flex flex-wrap items-center gap-1.5 mb-1">';
|
||||
html += '<input id="cards-of-tar-' + i + '-tar" class="w-24 ' + cls + '" maxlength="6" placeholder="TAR" value="' + escHtml(e.tar || '') + '">';
|
||||
html += '<input id="cards-of-tar-' + i + '-msl" class="w-14 ' + cls + '" maxlength="2" placeholder="MSL" value="' + escHtml(e.msl || '') + '">';
|
||||
html += '<select id="cards-of-tar-' + i + '-keyset" class="' + cls + '" title="' + esc(t('The keyset packets to this TAR use - a non-zero MSL requires one')) + '">' +
|
||||
'<option value=""' + (sel ? '' : ' selected') + '>' + esc(t('— none —')) + '</option>' +
|
||||
opts.map(o => '<option value="' + o.v + '"' + (o.v === sel ? ' selected' : '') + '>' +
|
||||
esc(t('Keyset') + ' ' + o.v + ' \u2014 ' + o.kic + '/' + o.kid) + '</option>').join('') +
|
||||
'</select>';
|
||||
if (!sel && msl && msl !== '00') {
|
||||
html += '<span class="text-amber-500" title="' +
|
||||
esc(t('This TAR needs a keyset (its MSL requires security)')) + '">\u26a0</span>';
|
||||
}
|
||||
if (role) {
|
||||
html += '<span class="text-xs text-gray-500 dark:text-slate-400">' + esc(CARDS_TAR_ROLE_DESC[role] || role) + '</span>';
|
||||
} else {
|
||||
@@ -15006,15 +15023,19 @@ function cardsOutfileTarRender(rows) {
|
||||
}
|
||||
|
||||
// The keyset numbers the parsed cards carry (the global default's options).
|
||||
function cardsOutfileKeysets() {
|
||||
const out = [];
|
||||
// The parsed keysets as the TAR rows' options: the union across the parsed
|
||||
// cards, with their KIc/KID bytes for the labels (v3.29.1).
|
||||
function cardsOutfileKeysetOptions() {
|
||||
const seen = {};
|
||||
(((_cardsOutfile || {}).preview || {}).presets || []).forEach(c => {
|
||||
(c.keysets || []).forEach(ks => {
|
||||
const v = spKeysetKvnOf(ks.kic);
|
||||
if (v !== null && !out.includes(v)) out.push(v);
|
||||
if (v !== null && !seen[v]) {
|
||||
seen[v] = { v: v, kic: ks.kic || '??', kid: ks.kid || '??' };
|
||||
}
|
||||
});
|
||||
});
|
||||
return out.sort((a, b) => a - b);
|
||||
return Object.keys(seen).map(k => seen[k]).sort((a, b) => a.v - b.v);
|
||||
}
|
||||
|
||||
function cardsOutfileResultHtml() {
|
||||
@@ -15059,15 +15080,6 @@ function cardsOutfileRender() {
|
||||
const panel = document.getElementById('cards-outfile-panel');
|
||||
const o = _cardsOutfile;
|
||||
if (!panel || !o) return;
|
||||
const keysets = cardsOutfileKeysets();
|
||||
// An empty or failed preview must not drop a chosen keyset: the value is
|
||||
// only re-selected against a list that has keysets, and the option list is
|
||||
// the preview's keysets plus a remembered value (so it stays visible).
|
||||
if (keysets.length && !keysets.includes(o.tarKeyset)) o.tarKeyset = keysets[0];
|
||||
const options = keysets.slice();
|
||||
if (o.tarKeyset && !options.includes(o.tarKeyset)) options.push(o.tarKeyset);
|
||||
options.sort((a, b) => a - b);
|
||||
const selected = o.tarKeyset;
|
||||
let html = '<div class="flex items-center justify-between mb-2">';
|
||||
html += '<span class="text-sm font-semibold">' + esc(t('outfile import')) +
|
||||
': <span class="font-mono text-xs">' + esc(o.file || '') + '</span></span>';
|
||||
@@ -15080,11 +15092,6 @@ function cardsOutfileRender() {
|
||||
'<select id="cards-of-algo" onchange="cardsOutfilePreview()" class="block mt-0.5 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1 dark:bg-slate-800">' +
|
||||
'<option value="3des2"' + (o.algorithm16 === 'aes' ? '' : ' selected') + '>3DES2</option>' +
|
||||
'<option value="aes"' + (o.algorithm16 === 'aes' ? ' selected' : '') + '>AES</option></select></label>';
|
||||
html += '<label class="text-xs text-gray-600 dark:text-slate-400">' + esc(t('Default keyset for the TARs')) +
|
||||
'<select id="cards-of-keyset" onchange="cardsOutfilePreview()" class="block mt-0.5 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1 dark:bg-slate-800">' +
|
||||
(options.length ? options.map(v => '<option value="' + v + '"' + (v === selected ? ' selected' : '') + '>' + esc(t('Keyset') + ' ' + v) + '</option>').join('')
|
||||
: '<option value="">\u2014</option>') +
|
||||
'</select></label>';
|
||||
html += '<label class="text-xs text-gray-600 dark:text-slate-400">' + esc(t('Import mode')) +
|
||||
'<select id="cards-of-mode" class="block mt-0.5 border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1 dark:bg-slate-800">' +
|
||||
'<option value="merge"' + (o.mode === 'replace' ? '' : ' selected') + '>' + esc(t('Merge')) + '</option>' +
|
||||
@@ -15110,7 +15117,6 @@ async function cardsOutfilePreview() {
|
||||
text: o.text, name: o.name, algorithm16: o.algorithm16,
|
||||
tars: cardsOutfileTarsFromForm(),
|
||||
};
|
||||
if (o.tarKeyset) body.tar_keyset = o.tarKeyset;
|
||||
try {
|
||||
o.preview = await pysimFetchOk('/api/presets/import-outfile', body);
|
||||
} catch (e) {
|
||||
@@ -15131,7 +15137,6 @@ async function cardsOutfileImport() {
|
||||
text: o.text, name: o.name, algorithm16: o.algorithm16,
|
||||
tars: cardsOutfileTarsFromForm(), mode: o.mode,
|
||||
};
|
||||
if (o.tarKeyset) body.tar_keyset = o.tarKeyset;
|
||||
try {
|
||||
const res = await pysimFetchOk('/api/presets/import-outfile', body);
|
||||
await cardsFetch();
|
||||
@@ -23632,7 +23637,7 @@ const LANG_RU = {
|
||||
'outfile import': 'Импорт outfile',
|
||||
'Common name': 'Общее имя',
|
||||
'16-byte keys': '16-байтные ключи',
|
||||
'Default keyset for the TARs': 'Keyset по умолчанию для TAR',
|
||||
'This TAR needs a keyset (its MSL requires security)': 'Этому TAR нужен keyset (его MSL требует защиты)',
|
||||
'Import mode': 'Режим импорта',
|
||||
'Merge': 'Дополнить',
|
||||
'Replace': 'Заменить',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v447';
|
||||
const CACHE = 'simple-v448';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -31,7 +31,7 @@ const ASYNC_FNS = ['cardsOutfileFile', 'cardsOutfilePreview', 'cardsOutfileImpor
|
||||
let code = '';
|
||||
for (const fn of ['cardsOutfileTarsFromDom', 'cardsOutfileTarsFromForm',
|
||||
'cardsOutfileTarRowHtml', 'cardsOutfileTarRowsHtml', 'cardsOutfileTarAdd',
|
||||
'cardsOutfileTarRemove', 'cardsOutfileTarRender', 'cardsOutfileKeysets',
|
||||
'cardsOutfileTarRemove', 'cardsOutfileTarRender', 'cardsOutfileKeysetOptions',
|
||||
'cardsOutfileResultHtml', 'cardsOutfileControls', 'cardsOutfileClose',
|
||||
'cardsOutfileRender', 'cardsOutfileFile', 'cardsOutfilePreview',
|
||||
'cardsOutfileImport', 'esc', 'escHtml', 'cardsRoleDefault', 'spKeysetKvnOf']) {
|
||||
@@ -71,7 +71,7 @@ const app = new Function('els', `
|
||||
sandbox.cardsOutfileTarAdd = cardsOutfileTarAdd;
|
||||
sandbox.cardsOutfileTarRemove = cardsOutfileTarRemove;
|
||||
sandbox.cardsOutfileTarRender = cardsOutfileTarRender;
|
||||
sandbox.cardsOutfileKeysets = cardsOutfileKeysets;
|
||||
sandbox.cardsOutfileKeysetOptions = cardsOutfileKeysetOptions;
|
||||
sandbox.cardsOutfileResultHtml = cardsOutfileResultHtml;
|
||||
sandbox.cardsOutfileControls = cardsOutfileControls;
|
||||
sandbox.cardsOutfileClose = cardsOutfileClose;
|
||||
@@ -107,9 +107,11 @@ test('the preview and the import both post the outfile endpoint', () => {
|
||||
assert.match(preview, /'\/api\/presets\/import-outfile'/);
|
||||
assert.match(imp, /'\/api\/presets\/import-outfile'/);
|
||||
assert.match(imp, /mode: o\.mode/);
|
||||
// the keyset is only sent when one was resolved
|
||||
assert.match(preview, /if \(o\.tarKeyset\) body\.tar_keyset = o\.tarKeyset;/);
|
||||
assert.match(imp, /if \(o\.tarKeyset\) body\.tar_keyset = o\.tarKeyset;/);
|
||||
// the keysets travel per TAR row (v3.29.1): no global tar_keyset
|
||||
assert.ok(!preview.includes('tar_keyset'), 'the preview must not send a global keyset');
|
||||
assert.ok(!imp.includes('tar_keyset'), 'the import must not send a global keyset');
|
||||
assert.match(preview, /tars: cardsOutfileTarsFromForm\(\)/);
|
||||
assert.match(imp, /tars: cardsOutfileTarsFromForm\(\), mode: o\.mode/);
|
||||
});
|
||||
|
||||
test('the 16-byte algorithm values match the server map', () => {
|
||||
@@ -159,20 +161,33 @@ test('the TAR rows render the role ids and the free rows with a remove button',
|
||||
assert.match(rows2, /id="cards-of-tar-3-tar"/);
|
||||
assert.match(rows2, /id="cards-of-tar-3-desc"/);
|
||||
assert.match(rows2, /onclick="cardsOutfileTarRemove\(3\)"/);
|
||||
// every row has a keyset select, fed by the parsed keysets (v3.29.1)
|
||||
assert.match(rows2, /id="cards-of-tar-0-keyset"/);
|
||||
assert.match(rows2, /id="cards-of-tar-3-keyset"/);
|
||||
app.state.preview = { presets: [{ keysets: [{ kic: '15', kid: '15' }, { kic: '25', kid: '25' }] }] };
|
||||
const rows3 = app.cardsOutfileTarRowsHtml();
|
||||
assert.match(rows3, /<option value="1" selected>Keyset 1 — 15\/15<\/option>/);
|
||||
assert.match(rows3, /<option value="2">Keyset 2 — 25\/25<\/option>/);
|
||||
// a non-zero MSL without a keyset gets the amber marker
|
||||
app.state.preview = { presets: [] };
|
||||
const rows4 = app.cardsOutfileTarRowsHtml();
|
||||
assert.match(rows4, /This TAR needs a keyset/);
|
||||
});
|
||||
|
||||
test('cardsOutfileKeysets collects the parsed keyset numbers', () => {
|
||||
test('cardsOutfileKeysetOptions collects the parsed keysets with their bytes', () => {
|
||||
clearEls();
|
||||
app.state = {
|
||||
text: TEXT, name: 'x', algorithm16: '3des2', tarKeyset: null, mode: 'merge',
|
||||
text: TEXT, name: 'x', algorithm16: '3des2', mode: 'merge',
|
||||
preview: { presets: [
|
||||
{ keysets: [{ kic: '25' }, { kic: '15' }] },
|
||||
{ keysets: [{ kic: '15' }, { kic: '31' }] },
|
||||
{ keysets: [{ kic: '25', kid: '25' }, { kic: '15', kid: '15' }] },
|
||||
{ keysets: [{ kic: '15', kid: '15' }, { kic: '31', kid: '31' }] },
|
||||
] },
|
||||
};
|
||||
assert.deepStrictEqual(app.cardsOutfileKeysets(), [1, 2, 3]);
|
||||
assert.deepStrictEqual(app.cardsOutfileKeysetOptions(),
|
||||
[{ v: 1, kic: '15', kid: '15' }, { v: 2, kic: '25', kid: '25' },
|
||||
{ v: 3, kic: '31', kid: '31' }]);
|
||||
app.state.preview = { presets: [] };
|
||||
assert.deepStrictEqual(app.cardsOutfileKeysets(), []);
|
||||
assert.deepStrictEqual(app.cardsOutfileKeysetOptions(), []);
|
||||
});
|
||||
|
||||
test('cardsOutfileResultHtml renders the report and the parsed cards', () => {
|
||||
@@ -203,16 +218,14 @@ test('cardsOutfileResultHtml renders the report and the parsed cards', () => {
|
||||
|
||||
test('cardsOutfileControls reads the panel controls back into the state', () => {
|
||||
clearEls();
|
||||
app.state = { text: TEXT, name: 'old', algorithm16: '3des2', tarKeyset: null, mode: 'merge', preview: null };
|
||||
app.state = { text: TEXT, name: 'old', algorithm16: '3des2', mode: 'merge', preview: null };
|
||||
setEl('cards-of-name', ' Batch 7 ');
|
||||
setEl('cards-of-algo', 'aes');
|
||||
setEl('cards-of-mode', 'replace');
|
||||
setEl('cards-of-keyset', '2');
|
||||
app.cardsOutfileControls();
|
||||
assert.strictEqual(app.state.name, 'Batch 7');
|
||||
assert.strictEqual(app.state.algorithm16, 'aes');
|
||||
assert.strictEqual(app.state.mode, 'replace');
|
||||
assert.strictEqual(app.state.tarKeyset, 2);
|
||||
});
|
||||
|
||||
test('picking a file previews it with the file name and the TAR defaults', async () => {
|
||||
@@ -231,9 +244,12 @@ test('picking a file previews it with the file name and the TAR defaults', async
|
||||
assert.strictEqual(body.name, 'batch'); // the file base name
|
||||
assert.strictEqual(body.algorithm16, '3des2');
|
||||
assert.strictEqual(body.mode, undefined); // the preview is a dry run
|
||||
assert.strictEqual(body.tar_keyset, undefined); // none resolved yet
|
||||
assert.strictEqual(body.tar_keyset, undefined); // the keysets are per row now
|
||||
assert.strictEqual(body.tars.length, 3);
|
||||
assert.strictEqual(app.state.tarKeyset, 1); // the first parsed keyset
|
||||
// the rows' keyset selects are filled from the parsed keysets (the first one
|
||||
// is the default)
|
||||
assert.match(els['cards-outfile-panel'].innerHTML, /id="cards-of-tar-0-keyset"/);
|
||||
assert.match(els['cards-outfile-panel'].innerHTML, /<option value="1" selected>Keyset 1 — 15\/15<\/option>/);
|
||||
assert.match(els['cards-outfile-panel'].innerHTML, /8970119000004600098/);
|
||||
});
|
||||
|
||||
@@ -259,11 +275,13 @@ test('the import posts the chosen mode and refreshes the list', async () => {
|
||||
setEl('cards-of-mode', 'replace');
|
||||
setEl('cards-of-name', 'Batch 7');
|
||||
app.state = { text: TEXT, file: 'batch.out', name: 'Batch 7', algorithm16: '3des2',
|
||||
tarKeyset: 2, mode: 'merge', preview: { presets: [], report: {} } };
|
||||
mode: 'merge', preview: { presets: [], report: {} } };
|
||||
setEl('cards-of-tar-0-keyset', '2'); // the ISD row's chosen keyset
|
||||
await app.cardsOutfileImport();
|
||||
assert.strictEqual(app.calls.length, 1);
|
||||
assert.strictEqual(app.calls[0].body.mode, 'replace');
|
||||
assert.strictEqual(app.calls[0].body.tar_keyset, 2);
|
||||
assert.strictEqual(app.calls[0].body.tar_keyset, undefined); // per row, not global
|
||||
assert.strictEqual(app.calls[0].body.tars[0].keyset, 2);
|
||||
assert.strictEqual(app.calls[0].body.name, 'Batch 7');
|
||||
assert.strictEqual(app.fetched, 1);
|
||||
assert.deepStrictEqual(app.io, [['cards-io', 'Imported 2 cards (1 skipped).']]);
|
||||
@@ -292,22 +310,22 @@ test('a declined replace confirmation aborts the import', async () => {
|
||||
assert.strictEqual(app.calls.length, 1);
|
||||
});
|
||||
|
||||
test('a chosen keyset survives an empty (failed) preview', () => {
|
||||
test('a row keeps its chosen keyset through the DOM round-trip', () => {
|
||||
clearEls();
|
||||
setEl('cards-outfile-panel', '');
|
||||
app.state = { text: TEXT, file: 'a.out', name: 'x', algorithm16: '3des2',
|
||||
tarKeyset: 2, mode: 'merge',
|
||||
preview: { presets: [{ iccid: '8970119000004600098', keysets: [{ kic: '15' }, { kic: '25' }] }], report: {} } };
|
||||
app.cardsOutfileRender();
|
||||
assert.strictEqual(app.state.tarKeyset, 2);
|
||||
assert.match(els['cards-outfile-panel'].innerHTML, /<option value="2" selected>/);
|
||||
// a failed re-preview has no keysets - the choice must not be dropped
|
||||
app.state.preview = { report: { error: 'TAR must be three hex bytes: ZZ' } };
|
||||
app.cardsOutfileRender();
|
||||
assert.strictEqual(app.state.tarKeyset, 2);
|
||||
assert.match(els['cards-outfile-panel'].innerHTML, /<option value="2" selected>/);
|
||||
// a keyset the new preview does not define falls back to its first one
|
||||
app.state.preview = { presets: [{ iccid: '8970119000004600098', keysets: [{ kic: '15' }] }], report: {} };
|
||||
app.cardsOutfileRender();
|
||||
assert.strictEqual(app.state.tarKeyset, 1);
|
||||
app.state = { text: TEXT, name: 'x', algorithm16: '3des2', mode: 'merge',
|
||||
preview: { presets: [{ keysets: [{ kic: '15', kid: '15' }, { kic: '25', kid: '25' }] }] } };
|
||||
app.tars = 0;
|
||||
setEl('cards-of-tar-0-keyset', '2'); // the user picked keyset 2 for the ISD row
|
||||
setEl('cards-of-tar-1-keyset', '');
|
||||
const tars = app.cardsOutfileTarsFromForm();
|
||||
assert.strictEqual(tars[0].keyset, 2);
|
||||
assert.strictEqual(tars[1].keyset, null);
|
||||
// a free row carries its own keyset too
|
||||
app.tars = 1;
|
||||
setEl('cards-of-tar-3-tar', 'b00011');
|
||||
setEl('cards-of-tar-3-msl', '1a');
|
||||
setEl('cards-of-tar-3-keyset', '1');
|
||||
const tars2 = app.cardsOutfileTarsFromForm();
|
||||
assert.strictEqual(tars2.length, 4);
|
||||
assert.strictEqual(tars2[3].keyset, 1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user