v3.20.0 (review fixes): the PoR decode uses the effective counter; the PWA fallback matches the server's seed

Two boundary flaws found reviewing v3.20.0:

- /api/send-ota decoded the PoR with the request's `cntr`, not the counter
  the packet was actually built with.  With the new seeding an omitted
  counter is the normal case, so a warning ENVELOPE's verdict - notably the
  live card's cntr_low PoR - could fail its RC/CC check and be lost (the
  success and the failure path are both fixed).  Tests pin the decode counter
  on both paths; the failure path also echoes the effective `cntr`.
- the PWA's effective counter (preview / verify / the older-server fallback)
  fell back to the preset's first keyset and then 1, while the server's empty
  seed builds counter 0 (no preset / kvn 0 / an unknown keyset).  A manual
  form or a keyless packet then produced a spurious "Verify vs pySim"
  MISMATCH and a preview that differed from the delivered packet.
  spPresetCounterValue()/spCntrEffective() now mirror _preset_counter_seed()
  exactly: the packet's matched keyset, else 0.
- the `OTA SEND: counter not tracked` line reflects the effective counter (a
  seeded value is logged too); docs/api.md records that the seed uses the
  packet's keyset whatever SPI1.b5b4 says.

Tests: 823 frontend + 737 Python; sw cache simple-v394.
This commit is contained in:
2026-10-05 22:37:28 +03:00
parent 85cc4b9535
commit bf6730f028
6 changed files with 120 additions and 14 deletions
+20
View File
@@ -64,6 +64,10 @@ function fakeEnv(selValue, selId, presetCntr) {
'sp-kid-idx', 'sp-kid-alg', 'sp-tar', 'sp-cntr', 'sp-kic-key', 'sp-kid-key']) {
els[id] = { value: '' };
}
// the combined key bytes the server reads (updateSpKic maintains them in
// the real form): the fake preset's keyset resolves to kvn 1
els['sp-kic-hex'] = { value: '15' };
els['sp-kid-hex'] = { value: '15' };
els[selId || 'sp-card-sel'] = { value: selValue };
globalThis.document = { getElementById: id => els[id] || null };
globalThis.cards = [{ name: 'C', cntr: presetCntr,
@@ -143,6 +147,22 @@ test('the counter +/- buttons start from the preset value and create an override
assert.strictEqual(env.els['sp-cntr'].value, '0000000020');
});
test('spCntrEffective mirrors the server seed: matched keyset, else 0', () => {
const env = fakeEnv('0', 'sp-card-sel', '0000000020');
assert.strictEqual(spCntrEffective(), '0000000020');
env.els['sp-cntr'].value = '0000000042'; // a typed override wins
assert.strictEqual(spCntrEffective(), '0000000042');
env.els['sp-cntr'].value = '';
env.els['sp-kic-hex'].value = '00'; // keyless: no keyset
env.els['sp-kid-hex'].value = '00';
assert.strictEqual(spCntrEffective(), '0000000000',
'the server seeds nothing for kvn 0 - the packet carries 0');
env.els['sp-kic-hex'].value = '15';
env.els['sp-kid-hex'].value = '15';
globalThis.cards = []; // no preset: no seed
assert.strictEqual(spCntrEffective(), '0000000000');
});
test('spPorAccepted treats a missing PoR and por_ok as accepted', () => {
assert.strictEqual(spPorAccepted(undefined), true);
assert.strictEqual(spPorAccepted({ response_status: 'por_ok' }), true);