docs: help/README catch up with v3.8.0; keyset-number wording; RU "пресет"

Documentation:
- help (§1/§3): the SCP80 tab is switched by three pills - the tab list and the
  section intro now name the TAR probe (RU "TAR-сканер"); §3.1/§3.2 describe the
  Keyset selector next to the preset selector (and that a successful PoR
  advances the counter only when SPI1 carries a counter check); §4 describes
  the keyset editor rows, the derived number and the per-keyset counters; the
  probe section keeps its counter rule.
- README/README_RUS: the SCP80 tab says three pills and gains a "TAR probe" /
  "TAR-сканер" section (standard list, verdicts, counter persistence); the
  Cards section is rewritten for the server-side store - keysets, server-owned
  per-keyset counters with the audit trail, JSON export/import as the migration
  path, the keyset editor, the equipped-card mismatch banner - and the field
  table, "Add a card" and "Edit / remove" follow.

Terminology:
- "key version" is no longer used for SCP80 keysets in the UI, the server error
  strings and the docs: the selector is "Keyset", its options "Keyset 1 - ...",
  the messages say "keyset N ...".  The TS 102 225 A.1/A.2 quotes and the GP
  PUT KEY P1 field keep the spec's own wording.
- RU keeps the word "keyset" untranslated ('Добавить keyset', 'Номер keyset',
  "не является keyset пресета", ...) and uses "пресет" instead of
  "предустановка" throughout the dictionary, the help and README_RUS.

sw cache simple-v340.  Tests: 742 Node / 619 Python.
This commit is contained in:
2026-09-30 23:48:32 +03:00
parent 31715ba58a
commit 9f7876b5fa
13 changed files with 207 additions and 180 deletions
+3 -3
View File
@@ -613,7 +613,7 @@ class PresetStoreHttpTests(unittest.TestCase):
'sp': '00', 'spi1': '16', 'spi2': '01', 'kic': '15', 'kid': '25',
'tar': '000000', 'cntr': '0000000001'})
self.assertEqual(status, 400, resp)
self.assertIn('same key version', resp['error'])
self.assertIn('same keyset number', resp['error'])
def test_send_ota_refuses_an_undefined_key_version(self):
p = self.store.add(self._preset(iccid='8970119000004600098'))
@@ -622,7 +622,7 @@ class PresetStoreHttpTests(unittest.TestCase):
'sp': '00', 'spi1': '16', 'spi2': '01', 'kic': '35', 'kid': '35',
'tar': '000000', 'cntr': '0000000001', 'preset_id': p['id']})
self.assertEqual(status, 400, resp)
self.assertIn('key version 3 is not defined', resp['error'])
self.assertIn('keyset 3 is not defined', resp['error'])
def test_the_counter_lands_in_the_keyset_of_the_packet(self):
p = self.store.add(self._preset(keysets=[
@@ -659,7 +659,7 @@ class PresetStoreHttpTests(unittest.TestCase):
status, resp = self._post('/api/ram-install', {
'cap_hex': _mini_cap_hex(), 'kic': '35', 'kid': '35', 'preset_id': p['id']})
self.assertEqual(status, 400, resp)
self.assertIn('key version 3 is not defined', resp['error'])
self.assertIn('keyset 3 is not defined', resp['error'])
def test_a_keyless_send_leaves_every_counter_untouched(self):
# SPI1 00: the counter is "present, ignored, never updated"
+1 -1
View File
@@ -2116,7 +2116,7 @@ class NoSecurityAndCounterTrackingTests(unittest.TestCase):
server = types.SimpleNamespace(card_presets=store)
S._preset_counter_persist(server, 'pid', '0000000002', 'send-ota', None)
S._preset_counter_persist(server, 'pid', '0000000002', 'send-ota', 0)
self.assertFalse(store.set_counter.called, 'no key version -> no write')
self.assertFalse(store.set_counter.called, 'no keyset number -> no write')
S._preset_counter_persist(server, 'pid', '0000000002', 'send-ota', 2)
store.set_counter.assert_called_once_with('pid', '0000000002', 'send-ota', 2)
+4 -4
View File
@@ -3,7 +3,7 @@
The store replaces the localStorage presets; the ICCID normalisation must stay
byte-compatible with the PWA's `cardsNormIccid` (digits / spaced / raw
nibble-swapped EF hex), every keyset carries its own monotonic counter (one per
key version, TS 102 225 Annex A.1), and the file must survive a corrupt store
keyset number, TS 102 225 Annex A.1), and the file must survive a corrupt store
without silently destroying it.
"""
@@ -122,7 +122,7 @@ class StoreCrudTests(unittest.TestCase):
# TS 102 225 A.2: a mismatch is rejected by the card - refuse it here
with self.assertRaises(presets.PresetError) as ctx:
self.store.add(preset(keysets=[keyset(kic='15', kid='25')]))
self.assertIn('same key version', str(ctx.exception))
self.assertIn('same keyset number', str(ctx.exception))
def test_key_version_00_and_duplicates_are_refused(self):
with self.assertRaises(presets.PresetError) as ctx:
@@ -131,7 +131,7 @@ class StoreCrudTests(unittest.TestCase):
with self.assertRaises(presets.PresetError) as ctx:
self.store.add(preset(keysets=[keyset(kic='15', kid='15'),
keyset(kic='15', kid='15')]))
self.assertIn('duplicate key version', str(ctx.exception))
self.assertIn('duplicate keyset number', str(ctx.exception))
# the byte must be a full hex byte
with self.assertRaises(presets.PresetError):
self.store.add(preset(keysets=[keyset(kic='1', kid='1')]))
@@ -237,7 +237,7 @@ class StoreCounterTests(unittest.TestCase):
self.assertEqual(self.counter(), '0000000005')
def test_each_key_version_has_its_own_counter(self):
# TS 102 225 A.1: a dedicated counter per key version
# TS 102 225 A.1: a dedicated counter per keyset
p = self.store.update(self.p['id'], {'keysets': [
keyset(kic='15', kid='15', cntr='0000000002'),
keyset(kic='29', kid='29', cntr='0000000007')]})