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,7 +31,8 @@ let code = '';
|
||||
const ASYNC_FNS = ['cardsAdd', 'cardsImport', 'cardsRemove'];
|
||||
for (const fn of ['cardsFormValues', 'cardsClearForm', 'cardsEdit',
|
||||
'cardsAdd', 'cardsImport', 'cardsRemove', 'ramCardIdxAfterRemove',
|
||||
'spPresetIdx', 'spCntrSyncPreset', 'spCntrLocalSync', 'ramSaveCntr',
|
||||
'spPresetIdx', 'spPresetCounterValue', 'spCntrPlaceholder', 'spCntrEffective',
|
||||
'spCntrAhead', 'spCntrLocalSync', 'ramSaveCntr',
|
||||
'cardsApplyFields', 'cardsApply', 'ramApplyCard',
|
||||
'cardsScp80Complete', 'cardsScp81Complete', 'cardsAdmPresent',
|
||||
'spTarKeyForPack', 'spPresetTar', 'spTarListRender', 'packToSp',
|
||||
@@ -415,29 +416,25 @@ test('import posts the entries to the server store', async () => {
|
||||
assert.strictEqual(apiCalls.length, 0);
|
||||
});
|
||||
|
||||
test('the SCP80 counter edit is written to the store, a reported counter only locally', () => {
|
||||
test('the SCP80 counter field is an override - an edit is never written to the store', () => {
|
||||
const els = setup();
|
||||
globalThis.cards = [{ id: 'abc123', name: 'C', keysets: [
|
||||
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' }] }];
|
||||
els['sp-card-sel'].value = '0';
|
||||
els['sp-kic-hex'].value = '15';
|
||||
els['sp-kid-hex'].value = '15';
|
||||
els['sp-cntr'].value = '0000000009';
|
||||
assert.strictEqual(spCntrSyncPreset(), true);
|
||||
assert.strictEqual(cards[0].keysets[0].cntr, '0000000009');
|
||||
assert.strictEqual(apiCalls.length, 1);
|
||||
assert.strictEqual(apiCalls[0].path, '/api/presets/update');
|
||||
assert.strictEqual(apiCalls[0].body.id, 'abc123');
|
||||
assert.strictEqual(apiCalls[0].body.fields.keysets[0].cntr, '0000000009');
|
||||
// a counter the server already persisted: display only, no second write
|
||||
apiCalls = [];
|
||||
els['sp-cntr'].value = '0000000009'; // an explicit override
|
||||
// typing alone writes nothing (no spCntrSyncPreset anymore, v3.20.0)
|
||||
assert.strictEqual(apiCalls.length, 0);
|
||||
// a counter the server persisted: the local copy moves forward, display only
|
||||
spCntrLocalSync('000000000A');
|
||||
assert.strictEqual(cards[0].keysets[0].cntr, '000000000A');
|
||||
assert.strictEqual(apiCalls.length, 0);
|
||||
// no preset selected: nothing to write
|
||||
els['sp-card-sel'].value = '';
|
||||
assert.strictEqual(spCntrSyncPreset(), false);
|
||||
assert.strictEqual(apiCalls.length, 0);
|
||||
// and the copy never regresses (the store is monotonic)
|
||||
spCntrLocalSync('0000000005');
|
||||
assert.strictEqual(cards[0].keysets[0].cntr, '000000000A');
|
||||
// the override in the field is untouched by the copy sync
|
||||
assert.strictEqual(els['sp-cntr'].value, '0000000009');
|
||||
});
|
||||
|
||||
test('applying a preset uses the TAR of the current operation and seeds the SPI1 from its MSL', () => {
|
||||
|
||||
Reference in New Issue
Block a user