v3.10.0: preset TAR table with per-TAR MSL; derive the SPI1 from the TAR

Presets drop the card-wide SPI1/SPI2 and the fixed tar/uiccTar/usimTar
fields: a preset now carries `tars` - the three mandatory role entries
(isd/uiccRfm/usimRfm, spec defaults pre-settled) plus free TARs with an
optional description, each with its MSL (Minimum SPI1, TS 102 226
8.2.1.3.2.4).  v3.9.1 presets convert on load/import (the spi1 seeds all
three MSLs); duplicate TARs and empty TAR/MSL values are refused; the
store schema version is 3.

Operations derive a packet's SPI1 from its TAR's MSL (_spi1_for_tar): the
RAM ops, the test runner (per-step overrides still win), the counter probe
and each TAR-probe row; the plain send keeps an explicit SPI1 with a
`warning` when it is below the MSL, and a TAR outside the preset without an
explicit SPI1 is refused with a hint.  The TAR probe resolves per checked
TAR before sending and reports the effective spi1 per row.  The secured
packet form's SPI2 default is 00 (the RAM/probe operations pin their own).

PWA: the Cards form edits the TAR table (role rows with MSL inputs plus
Add/Remove free rows with a description), the list shows a TARs (MSL)
column, the SCP80 TAR field suggests the preset's entries (datalist) and a
preset apply seeds the SPI1 from the operation TAR's MSL with an amber
warning below it; the preset-wide SPI1/SPI2 fields and the header marker's
SPI requirement are gone (completeness = a keyset + the three role entries
with their MSLs).  Help EN/RU, docs/api.md and AGENTS updated; sw cache
simple-v358.

Live: TAR probe 000000 at its MSL 16 (registered, por_ok, counter
advanced); a send without spi1 derived 16 (por_ok, counter advanced); a
send with spi1 0A returned the warning and the card refused it (6200, no
counter consumed); an unknown TAR without spi1 was refused with the hint.
This commit is contained in:
2026-10-02 08:28:46 +03:00
parent f059d05040
commit 6360dc5bae
19 changed files with 1187 additions and 269 deletions
+19 -6
View File
@@ -5,6 +5,12 @@ const path = require('node:path');
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
function extractConst(src, name) {
const m = new RegExp('const\\s+' + name + '\\s*=\\s*([^;]+);').exec(src);
if (!m) throw new Error('const ' + name + ' not found');
return 'var ' + name + ' = ' + m[1] + ';\n';
}
function extractFunc(src, name) {
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
const m = re.exec(src);
@@ -22,18 +28,23 @@ function extractFunc(src, name) {
}
let code = '';
for (const fn of ['cardsTarValue', 'cardsApplyFields', 'cardsApply',
for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar', 'cardsRoleMsl',
'cardsTarEntry', 'cardsTarMsl', 'cardsApplyFields', 'cardsApply',
'spRefreshFromPreset', 'spPorAccepted', 'spNextCntr', 'spPresetIdx',
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply',
'spKeysetChanged', 'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard',
'spTarListRender',
'ramKeysetGuard',
]) {
code += extractFunc(html, fn) + '\n';
}
code = extractConst(html, 'CARDS_TAR_DEFAULTS') + extractConst(html, 'CARDS_TAR_ROLES')
+ extractConst(html, 'CARDS_MSL_DEFAULT') + code;
code = 'var _cardsKeysetCount = 0;\nglobalThis.spPresetWarningRender = function() {};\n'
+ 'globalThis.spMslWarningRender = function() {};\n'
+ 'globalThis.pysimApplyAvailability = function() {};\n' + code;
code = 'var _genSpBuildStub = function() {};\n' + code;
eval(code);
@@ -52,10 +63,12 @@ function fakeEnv(selValue, selId, presetCntr) {
}
els[selId || 'sp-card-sel'] = { value: selValue };
globalThis.document = { getElementById: id => els[id] || null };
globalThis.cards = [{ name: 'C', cntr: presetCntr, spi1: '16', spi2: '01',
kic: '15', kid: '15', tar: '000000', uiccTar: 'B00000',
kicKey: 'AA', kidKey: 'BB' }];
globalThis._spTarKey = 'uiccTar';
globalThis.cards = [{ name: 'C', cntr: presetCntr,
kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB',
tars: [{ role: 'isd', tar: '000000', msl: '16' },
{ role: 'uiccRfm', tar: 'B00000', msl: '16' },
{ role: 'usimRfm', tar: 'B00001', msl: '16' }] }];
globalThis._spTarKey = 'uiccRfm';
globalThis.updateSpKic = () => {};
globalThis.updateSpKid = () => {};
globalThis.spInvalidate = () => {};
@@ -83,7 +96,7 @@ test('spRefreshFromPreset re-reads the edited preset before an operation', () =>
test('spRefreshFromPreset for the RAM selector targets the ISD TAR', () => {
const env = fakeEnv('0', 'ram-card-sel', '0000000010');
assert.strictEqual(spRefreshFromPreset('ram-card-sel'), '0000000010');
assert.strictEqual(_spTarKey, 'tar');
assert.strictEqual(_spTarKey, 'isd');
assert.strictEqual(env.els['sp-tar'].value, '000000');
});