v3.19.0 (docs/engine): test-script guide audit; sms da accepts a leading +
An audit of docs/test-scripts.md against the engine (testscript.py, events.py, the runner) found a handful of doc-level gaps, now fixed: - the scp80 param defaults (tar = the preset's ISD role TAR, spi1 = that TAR's MSL, spi2 = 01, kvn = the first keyset); - the counter rule as implemented: the run advances/persists the counter after every answered scp80 step when SPI1 requests a counter check (refused packets move the local value forward too - it only moves up); - respond is optional (an omitted object answers ok/0x00); - the run-log section: the exact READ BINARY/READ RECORD/UPDATE BINARY, SCP80 WRAPPED=/SP= and 'TEST-RUN step N failed:' lines; - Known limitations: an expanded-format 61xx has no in-packet GET RESPONSE (TS 102 226 5.2.1.1) - send the GET RESPONSE as a follow-up scp80 step; - sms check da: a leading + and whitespace are ignored. The engine now drops the + (the TPDU carries the international flag in the dialling type, so '+79...' could otherwise never match the parsed digits); docs/api.md and the local AGENTS note it. Tests: 821 frontend + 726 Python.
This commit is contained in:
+2
-1
@@ -702,7 +702,8 @@ SHORT MESSAGE carrying the PoR of the
|
||||
preceding `scp80` step - PoR-in-submit, SPI2 bit `20`: `status` (the response
|
||||
status name, `ok` = `por_ok`), `sw` (the R-APDU status word, exact/mask) and
|
||||
`data` (the R-APDU response data, exact/mask)), `sms` (the SEND SHORT MESSAGE
|
||||
TPDU: `da` digits, `pid`/`dcs`/`ud` hex exact/mask, `udl` decimal; SMS-SUBMIT
|
||||
TPDU: `da` digits with whitespace skipped and a leading `+` ignored,
|
||||
`pid`/`dcs`/`ud` hex exact/mask, `udl` decimal; SMS-SUBMIT
|
||||
only) or `files` (the File List of a
|
||||
REFRESH, TS 102 223 8.18; `files` is a list of path hex strings, matched
|
||||
order-insensitively); `respond` is the TERMINAL RESPONSE in the TS 102 223
|
||||
|
||||
+31
-11
@@ -103,8 +103,9 @@ PoR-in-submit:
|
||||
4. **Iterate**: a mismatch on `data` usually means the mask length differs from
|
||||
the actual value, or the value changed between runs - mask the varying
|
||||
bytes (counter, date-time) with `?`.
|
||||
5. **Counters**: the run starts from the preset's stored counter and persists
|
||||
each accepted packet's next value; consecutive runs continue correctly.
|
||||
5. **Counters**: the run starts from the preset's stored counter and advances
|
||||
it after every scp80 step the card answers (when SPI1 requests a counter
|
||||
check); a refused packet moves it forward too - see the presets section.
|
||||
|
||||
### Reading the run log
|
||||
|
||||
@@ -129,8 +130,13 @@ TEST-RUN step 4: MENU-SELECT ENVELOPE=80c2000009d30702020181900101 item=1 ('One'
|
||||
- A SEND SHORT MESSAGE expectation also logs its parsed TPDU
|
||||
(`SMS DA=… PID=… DCS=… UDL=… UD=…`) after the `CMD` line, so an applet's own
|
||||
SMS can be read even without an `sms` check.
|
||||
- A wrapped scp80 step logs both forms:
|
||||
`SCP80 C-APDU=<plain> WRAPPED=<wrapped> (<format>)`; a pre-built packet logs
|
||||
`SCP80 SP=…`.
|
||||
- Other actions log their send/response pair too (`APDU TX=… -> SW=…`,
|
||||
`ENVELOPE(Event Download) … -> SW=…`, `READ MF/… -> SW=…`).
|
||||
`ENVELOPE(Event Download) … -> SW=…`, `READ BINARY <path>`,
|
||||
`READ RECORD <n> <path>`, `UPDATE BINARY <path>`, each with `-> SW=…`).
|
||||
- A failing step adds `TEST-RUN step N failed: …` with the reason.
|
||||
- The shared secured-packet sender logs each segment's answer
|
||||
(`OTA SEND: ENVELOPE i/N -> <SW>`).
|
||||
- `--apdu-trace` adds the raw transport view
|
||||
@@ -164,9 +170,16 @@ preset's id** with the run; the server resolves it and takes:
|
||||
|
||||
So before a run the matched preset (ICCID) must contain the keyset and, for
|
||||
every TAR the script addresses, either a TAR entry with its MSL or an explicit
|
||||
`spi1` in the step. Counter handling: the run starts from the keyset's stored
|
||||
counter and persists each accepted packet's next value, so consecutive runs
|
||||
continue correctly.
|
||||
`spi1` in the step. A step that omits them gets the preset's ISD role TAR as
|
||||
`tar`, that TAR's MSL as `spi1` and `01` as `spi2`; `kvn` defaults to the
|
||||
first keyset.
|
||||
|
||||
Counter handling: the run starts from the keyset's stored counter and advances
|
||||
(and persists) its next value after **every** scp80 step the card answers, when
|
||||
the packet requests a counter check (SPI1 b5b4 ≠ 00, TS 102 225 §5.1.1) - a
|
||||
refused packet moves the local value forward too. The local counter therefore
|
||||
only ever moves up, so a later packet is never below what the card has seen;
|
||||
consecutive runs continue correctly.
|
||||
|
||||
## Actions
|
||||
|
||||
@@ -232,7 +245,8 @@ the default is the terminal, matching the Phone tab's forms.
|
||||
(`response_status == por_ok`), `"none"` (no PoR expected), or an **object**
|
||||
`{"status"?, "sw"?, "data"?}` asserting the decoded PoR of this very
|
||||
exchange (at least one field; the same fields as the expectation's `por`
|
||||
check) - it works for both the inline and the SEND SHORT MESSAGE transport.
|
||||
check, `status` accepting `ok` for `por_ok`) - it works for both the inline
|
||||
and the SEND SHORT MESSAGE transport.
|
||||
Example: `"por": {"status": "por_ok", "sw": "9000"}`.
|
||||
- A plain hex string is exact; a value containing `?` is a mask. **A mask
|
||||
must have exactly the same length as the actual value** (`?` is a per-nibble
|
||||
@@ -276,7 +290,9 @@ the `por` check on the step that completes the sequence (intermediate steps
|
||||
should use `raw` or no PoR check).
|
||||
|
||||
`sms` parses the fetched SEND SHORT MESSAGE's TPDU (SMS-SUBMIT only - any other
|
||||
MTI fails the check): `da` the destination digits (exact, whitespace ignored),
|
||||
MTI fails the check): `da` the destination digits as the TPDU carries them
|
||||
(exact; whitespace and a leading `+` in the script are ignored - the
|
||||
international flag lives in the TPDU's type-of-number),
|
||||
`pid`/`dcs` single bytes and `ud` the raw user data (a UDH included) as hex
|
||||
exact/mask, `udl` the user-data length in decimal (octets, or septets for the
|
||||
7-bit alphabet - whatever the TPDU carries). Use it for an applet's own SMS;
|
||||
@@ -285,9 +301,9 @@ field is required.
|
||||
|
||||
### `respond`
|
||||
|
||||
The TERMINAL RESPONSE for the fetched command, in the TS 102 223 §6.8.0
|
||||
object order (Command details, Device identities, **Result**, Duration, Text
|
||||
string, Item identifier, …):
|
||||
Optional - an omitted (or empty) object answers `ok` (`0x00`). The TERMINAL
|
||||
RESPONSE follows the TS 102 223 §6.8.0 object order (Command details, Device
|
||||
identities, **Result**, Duration, Text string, Item identifier, …):
|
||||
|
||||
- `result` - name or hex (`ok`, `partial`, `missing`, `refused`,
|
||||
`not_understood`, `modified`, `cancel`, `back`, `timeout`, `no_response`).
|
||||
@@ -443,6 +459,10 @@ PoR-in-submit (with the transport asserted by the expectation):
|
||||
`status` action.
|
||||
- A `por` check only decodes SEND SHORT MESSAGE commands; the inline transport
|
||||
is checked on the action (`por`/`data`).
|
||||
- An expanded-format command (`"format": "expanded"`/`"expanded-ae"`) has no
|
||||
in-packet GET RESPONSE (TS 102 226 §5.2.1.1): if the card answers `61xx`,
|
||||
send the GET RESPONSE as a follow-up `scp80` step (its own secured packet,
|
||||
same format).
|
||||
- `files` matches the File List as a set (order-insensitive); duplicate paths
|
||||
are kept.
|
||||
- Semantic `event` actions cover four events (0x03/0x0B/0x12/0x1D); every
|
||||
|
||||
@@ -467,7 +467,11 @@ def _normalise_content_check(c, default_level):
|
||||
da = c.get('da')
|
||||
if da is not None and str(da).strip():
|
||||
d = re.sub(r'\s', '', str(da))
|
||||
if not re.fullmatch(r'[0-9*#+]{1,20}', d):
|
||||
# The international flag lives in the TPDU's type-of-number, not
|
||||
# in the digits: a leading '+' is accepted and dropped.
|
||||
if d.startswith('+'):
|
||||
d = d[1:]
|
||||
if not re.fullmatch(r'[0-9*#]{1,20}', d):
|
||||
raise ScriptError('sms check: da must be up to 20 digits')
|
||||
out['da'] = d
|
||||
for key in ('pid', 'dcs'):
|
||||
|
||||
@@ -276,6 +276,13 @@ class TestValidation(unittest.TestCase):
|
||||
self.assertEqual(sms['dcs'], {'mode': 'exact', 'value': '00'})
|
||||
self.assertEqual(sms['udl'], 5)
|
||||
self.assertEqual(sms['ud'], {'mode': 'mask', 'value': '0271??AABB'})
|
||||
# a leading '+' (the TPDU keeps the international flag in its
|
||||
# type-of-number) and whitespace are dropped from `da`
|
||||
script = T.normalise_script({'steps': [
|
||||
{'type': 'expect', 'command': 'SEND SHORT MESSAGE',
|
||||
'checks': [{'kind': 'sms', 'da': '+79 332 505 884'}]},
|
||||
]}, _resolver)
|
||||
self.assertEqual(script['steps'][0]['checks'][0]['da'], '79332505884')
|
||||
for check in ({'kind': 'alpha'}, # no value
|
||||
{'kind': 'alpha', 'mode': 'nope', 'value': 'x'},
|
||||
{'kind': 'sms'}, # no fields
|
||||
@@ -1075,7 +1082,7 @@ class TestRunnerActions(RunnerTestCase):
|
||||
'check': {'sw': {'mode': 'mask', 'value': '91??'}}},
|
||||
{'type': 'expect', 'command': 'SEND SHORT MESSAGE',
|
||||
'checks': [{'kind': 'alpha', 'mode': 'exact', 'value': 'Отправка...'},
|
||||
{'kind': 'sms', 'da': '79332505884', 'pid': '00',
|
||||
{'kind': 'sms', 'da': '+79 332 505 884', 'pid': '00',
|
||||
'dcs': '00', 'udl': 8, 'ud': '4B1C12579C9D83'}],
|
||||
'respond': {'result': 'ok'}},
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user