v3.20.0: the server-owned SCP80 counter (an untouched field = the preset's next value)
The counter an operation consumed was re-entered by the PWA on every send (the form field was filled from the preset and posted back), so a stale tab could send a value the card rejects (cntr_low), and typing in the field silently wrote the preset. Now the store is the single source of truth and the form field is a per-send override (Option B). - _preset_counter_seed: an explicit `cntr` in the request wins (a hand send, the counter probe or a pre-built packet may deliberately pick one); otherwise the preset store's value for the packet's keyset. Wired into /api/send-ota (the APDU path; the pre-built `sp` path keeps the caller's bookkeeping value - the packet carries its own counter), /api/ram-install, /api/ram-install-app, /api/cap-compat, /api/tar-probe and /api/sp-verify (the reference must use the counter the JS packet was built with). - the send-ota response echoes the effective `cntr` (a caller that omitted one learns what was used) and the OTA SEND log line prints it. - persistence is unchanged and monotonic (presets.set_counter refuses backwards writes), pinned by tests: an explicit lower counter is used for the packet but never lowers the store, a higher accepted one is saved. - PWA: the Secured packet counter field starts empty with the preset's next value as its placeholder (auto: <cntr>); typing affects that send only and is never written to the preset (spCntrSyncPreset is gone), spParams / getRamSpParams omit an empty counter, the RAM/send flows sync only the local preset copy (forward-only) instead of filling the field, the +/- buttons start from the effective value, and the preview/verify packet is built from the effective counter (genSp). Placeholder translated EN/RU. - the runner and /api/counter-probe keep their existing seeding (the probe already starts at the failed value and walks above it). - docs: api.md's optional-`cntr` contract + seeding notes on every endpoint, help EN/RU 3.1. Tests: 822 frontend + 734 Python; sw cache simple-v393.
This commit is contained in:
@@ -31,6 +31,8 @@ let code = '';
|
||||
for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
|
||||
'cardsTarEntry', 'cardsTarMsl', 'cardsApplyFields', 'cardsApply',
|
||||
'spRefreshFromPreset', 'spPorAccepted', 'spNextCntr', 'spPresetIdx',
|
||||
'spPresetCounterValue', 'spCntrPlaceholder', 'spCntrEffective', 'spCntrAhead',
|
||||
'spCntrAdjust',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
|
||||
@@ -51,6 +53,7 @@ eval(code);
|
||||
globalThis._genSpBuild = () => {};
|
||||
globalThis.spShowSizeInfo = () => {};
|
||||
globalThis.spKeysetSync = globalThis.spKeysetSync || (() => {});
|
||||
globalThis.t = s => s;
|
||||
|
||||
// The SP form is a working copy: the preset is the source of truth, and
|
||||
// every SCP80/RAM operation re-reads it before starting (v3.6.3). The plain
|
||||
@@ -81,16 +84,23 @@ function fakeEnv(selValue, selId, presetCntr) {
|
||||
return { els, gen: () => gen };
|
||||
}
|
||||
|
||||
test('spRefreshFromPreset re-reads the edited preset before an operation', () => {
|
||||
test('spRefreshFromPreset keeps an explicit counter override, empty = auto', () => {
|
||||
const env = fakeEnv('0', 'sp-card-sel', '00000000AA');
|
||||
env.els['sp-cntr'].value = '0000000001'; // stale form copy
|
||||
env.els['sp-cntr'].value = '0000000001'; // explicit override
|
||||
assert.strictEqual(spRefreshFromPreset('sp-card-sel'), '00000000AA');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '00000000AA');
|
||||
assert.ok(env.gen() > 0, 'the packet must be regenerated from the new counter');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000001',
|
||||
'a typed counter survives the pre-send refresh');
|
||||
assert.ok(env.gen() > 0, 'the packet must be regenerated from the preset');
|
||||
// an empty field means "auto": the server seeds the counter, and the
|
||||
// preset's next value is shown as the placeholder (v3.20.0)
|
||||
env.els['sp-cntr'].value = '';
|
||||
spRefreshFromPreset('sp-card-sel');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '');
|
||||
assert.match(env.els['sp-cntr'].placeholder, /00000000AA/);
|
||||
// no preset selected: the manual form is left alone
|
||||
globalThis.cards = [];
|
||||
assert.strictEqual(spRefreshFromPreset('sp-card-sel'), '');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '00000000AA');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '');
|
||||
});
|
||||
|
||||
test('spRefreshFromPreset for the RAM selector targets the ISD TAR', () => {
|
||||
@@ -105,23 +115,34 @@ test('the pre-send refresh keeps a hand-edited packet TAR (v3.6.21)', () => {
|
||||
env.els['sp-tar'].value = 'AF4D01'; // e.g. a push/link trigger target
|
||||
assert.strictEqual(spRefreshFromPreset('sp-card-sel', true), '0000000020');
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'AF4D01', 'the typed TAR must survive the send');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000020', 'the counter still refreshes');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '', 'the counter field stays an override');
|
||||
assert.match(env.els['sp-cntr'].placeholder, /0000000020/);
|
||||
// an explicit preset apply (or a pack) still sets the TAR
|
||||
cardsApplyFields(0);
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'B00000');
|
||||
});
|
||||
|
||||
test('cardsApplyFields fills the form without generating a packet', () => {
|
||||
test('cardsApplyFields leaves the counter to the server (auto) and shows it as a placeholder', () => {
|
||||
const env = fakeEnv('0', 'sp-card-sel', '0000000007');
|
||||
env.els['sp-cntr'].value = 'nonsense';
|
||||
env.els['sp-cntr'].value = 'nonsense'; // an old override
|
||||
assert.strictEqual(cardsApplyFields(0), true);
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000007');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '',
|
||||
'applying a preset clears the override (empty = the server seeds it)');
|
||||
assert.match(env.els['sp-cntr'].placeholder, /0000000007/);
|
||||
assert.strictEqual(env.gen(), 0, 'a plain field refresh must not rebuild the packet');
|
||||
cardsApply(0);
|
||||
assert.strictEqual(env.gen(), 1);
|
||||
assert.strictEqual(cardsApplyFields(3), false);
|
||||
});
|
||||
|
||||
test('the counter +/- buttons start from the preset value and create an override', () => {
|
||||
const env = fakeEnv('0', 'sp-card-sel', '0000000020');
|
||||
spCntrAdjust(1);
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000021');
|
||||
spCntrAdjust(-1);
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000020');
|
||||
});
|
||||
|
||||
test('spPorAccepted treats a missing PoR and por_ok as accepted', () => {
|
||||
assert.strictEqual(spPorAccepted(undefined), true);
|
||||
assert.strictEqual(spPorAccepted({ response_status: 'por_ok' }), true);
|
||||
|
||||
Reference in New Issue
Block a user