v3.18.1: the raw envelope action takes the source override too
The events without a semantic builder (MT call, IMS registration, slices, ...) are network-sourced, but the raw `envelope` action had no `src` override - the only way to express them was the semantic set. `envelope` now accepts `src` (82 terminal / 83 network) exactly like the `event` action; the step editor exposes it and the authoring guide gets the row. Also: the guide marks `por` as SCP80-only (the normaliser refuses it elsewhere) and the semantic-events section names the shared src override. Tests: 813 frontend + 717 Python; sw cache simple-v382.
This commit is contained in:
+2
-1
@@ -666,7 +666,8 @@ server resolves it from the preset store, so a run always uses the stored
|
||||
keysets, TARs and counters. The inline `preset` form stays for external
|
||||
callers.
|
||||
|
||||
**Action steps** (`type: "action"`): `kind` is `envelope` (`event`, `data`),
|
||||
**Action steps** (`type: "action"`): `kind` is `envelope` (`event`, `data`,
|
||||
optional `src` - the device-identities source override),
|
||||
`event` (`event` name or hex + `fields`, optional `src` - the semantic event
|
||||
download for 0x03/0x0B/0x12/0x1D), `menu-select` (`item_id` 1-255, or `text` +
|
||||
`mode` `exact`/`contains` with an
|
||||
|
||||
@@ -14,7 +14,8 @@ localStorage export imports as-is), and any API client can create or run them
|
||||
|
||||
**Format**: server ≥ 3.15.0 (`kvn`, `por` and `files` checks), scripts stored
|
||||
server-side since 3.16.0, `menu-select` by text and the spec-order TERMINAL
|
||||
RESPONSE codings since 3.17.0, the semantic `event` action since 3.18.0.
|
||||
RESPONSE codings since 3.17.0, the semantic `event` action since 3.18.0 and
|
||||
the `envelope` action's `src` override since 3.18.1.
|
||||
|
||||
## Writing a test for an applet
|
||||
|
||||
@@ -131,7 +132,7 @@ continue correctly.
|
||||
|
||||
| kind | params | notes |
|
||||
|---|---|---|
|
||||
| `envelope` | `event` 0–255, `data` hex (optional) | ENVELOPE(Event Download) with raw data |
|
||||
| `envelope` | `event` 0–255, `data` hex (optional), optional `src` | ENVELOPE(Event Download) with raw data; `src` overrides the device-identities source (`82` terminal / `83` network) |
|
||||
| `event` | `event` (name or hex), `fields` (object), optional `src` | semantic ENVELOPE(Event Download) for 0x03/0x0B/0x12/0x1D (see below) |
|
||||
| `menu-select` | `item_id` 1–255 **or** `text` + `mode` `exact`/`contains` (+ `case_sensitive`), not both | ENVELOPE(Menu Selection); the text is resolved against the card's cached menu - refreshed whenever the card sends SET UP MENU, so it follows the applet's install parameters |
|
||||
| `file-write` | `path`, `data`, `mode` `auto`/`binary`/`record`, `record` 1–255 | UPDATE BINARY/RECORD |
|
||||
@@ -147,7 +148,8 @@ or file names - e.g. `MF/7F20/6F07`, `ADF.USIM/EF.TEST`.
|
||||
|
||||
The server builds the event objects for the events a test needs most; an
|
||||
event without a builder goes through the raw `envelope` action, whose data hex
|
||||
the Phone tab's event form shows (**Event data (hex)**, copyable).
|
||||
the Phone tab's event form shows (**Event data (hex)**, copyable). Both
|
||||
actions take the same optional `src` device-identities override.
|
||||
|
||||
```json
|
||||
{"type": "action", "kind": "event",
|
||||
@@ -177,8 +179,8 @@ the default is the terminal, matching the Phone tab's forms.
|
||||
- `sw` - default `{"mode": "exact", "value": "9000"}` (a `status` action with
|
||||
`attempts > 1` defaults to the `91??` mask).
|
||||
- `data` - the response data (for SCP80: the inline PoR packet).
|
||||
- `por` - `"any"` (default, no check), `"ok"` (`response_status == por_ok`),
|
||||
`"none"` (no PoR expected).
|
||||
- `por` (SCP80 actions only) - `"any"` (default, no check), `"ok"`
|
||||
(`response_status == por_ok`), `"none"` (no PoR expected).
|
||||
- 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
|
||||
wildcard) - e.g. `91??` matches `9102`, `027100????` matches a 4-byte
|
||||
|
||||
+9
-2
@@ -1877,7 +1877,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.18.0';
|
||||
const SIMPLE_VERSION = '3.18.1';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -7328,6 +7328,7 @@ function testStepRender() {
|
||||
if (step.kind === 'envelope') {
|
||||
html += testFormRow('Event type', testFormInput('test-f-event', p.event === undefined ? 3 : p.event, '0..255'));
|
||||
html += testFormRow('Event data (hex, optional)', testFormInput('test-f-data', p.data, '9B0100'));
|
||||
html += testFormRow('Source override (hex, optional)', testFormInput('test-f-eventsrc', p.src, '82 terminal / 83 network'));
|
||||
} else if (step.kind === 'event') {
|
||||
// The semantic event download: the four events the server builds
|
||||
// (others go through the raw envelope action with the hex the
|
||||
@@ -7537,7 +7538,12 @@ function testStepCollect() {
|
||||
const kind = val('test-step-kind') || step.kind;
|
||||
step.kind = kind;
|
||||
const p = {};
|
||||
if (kind === 'envelope') { p.event = num('test-f-event', 3); p.data = hexNoMask('test-f-data'); }
|
||||
if (kind === 'envelope') {
|
||||
p.event = num('test-f-event', 3);
|
||||
p.data = hexNoMask('test-f-data');
|
||||
const src = hexNoMask('test-f-eventsrc');
|
||||
if (src) p.src = src;
|
||||
}
|
||||
else if (kind === 'menu-select') {
|
||||
const text = val('test-f-itemtext');
|
||||
if (text) {
|
||||
@@ -21110,6 +21116,7 @@ const LANG_RU = {
|
||||
'event is required': 'нужен тип события',
|
||||
'event fields must be an object': 'поля события должны быть объектом',
|
||||
'Event data (hex)': 'Данные события (hex)',
|
||||
'Source override (hex, optional)': 'Переопределение источника (hex, опционально)',
|
||||
'Could not load the test scripts from the server': 'Не удалось загрузить тест-скрипты с сервера',
|
||||
'Could not save the test script': 'Не удалось сохранить тест-скрипт',
|
||||
'Could not delete the test script': 'Не удалось удалить тест-скрипт',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v381';
|
||||
const CACHE = 'simple-v382';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -209,6 +209,16 @@ test('the menu-select editor collects the item id or the text', () => {
|
||||
assert.deepStrictEqual(_testEditStep.params, { item_id: 3 });
|
||||
});
|
||||
|
||||
test('the envelope step collects the source override', () => {
|
||||
_testEditStep = { type: 'action', kind: 'envelope', params: {} };
|
||||
_testEditChecks = [];
|
||||
_testEditStepIndex = 0;
|
||||
fakeForm({ 'test-step-kind': 'envelope', 'test-f-event': '3', 'test-f-data': '9B0100',
|
||||
'test-f-eventsrc': '83', 'test-f-sw': '', 'test-f-cdata': '', 'test-f-fail': 'error' });
|
||||
testStepCollect();
|
||||
assert.deepStrictEqual(_testEditStep.params, { event: 3, data: '9B0100', src: '83' });
|
||||
});
|
||||
|
||||
test('the event step collects the type and the JSON fields', () => {
|
||||
_testEditStep = { type: 'action', kind: 'event', params: {} };
|
||||
_testEditChecks = [];
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.18.0"
|
||||
version = "3.18.1"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -34,7 +34,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.18.0'
|
||||
VERSION = '3.18.1'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -5953,7 +5953,7 @@ def _test_run_action(server, step, ctx):
|
||||
if kind == 'envelope':
|
||||
data, sw = _send_event_download(scc, p['event'],
|
||||
bytes.fromhex(p['data']) if p['data'] else None,
|
||||
drain=False)
|
||||
drain=False, src=p.get('src'))
|
||||
sent = 'ENVELOPE(Event Download) type=0x%02X' % p['event']
|
||||
elif kind == 'event':
|
||||
# The semantic event download: the builder owns the byte layout.
|
||||
|
||||
@@ -218,8 +218,15 @@ def _normalise_params(kind, p):
|
||||
if kind == 'envelope':
|
||||
if p.get('event') is None:
|
||||
raise ScriptError('envelope: event is required')
|
||||
return {'event': _int(p['event'], 'envelope event', 0, 255),
|
||||
'data': _data_hex(p.get('data'), 'envelope data', allow_empty=True)}
|
||||
out = {'event': _int(p['event'], 'envelope event', 0, 255),
|
||||
'data': _data_hex(p.get('data'), 'envelope data', allow_empty=True)}
|
||||
src = str(p.get('src') or '').strip().upper()
|
||||
if src:
|
||||
if not re.fullmatch(r'[0-9A-F]{2}', src):
|
||||
raise ScriptError('envelope: src must be one hex byte '
|
||||
'(82 terminal / 83 network)')
|
||||
out['src'] = src
|
||||
return out
|
||||
if kind == 'event':
|
||||
# The semantic event download: the builder owns the field set and the
|
||||
# byte layout (events.py mirrors the PWA's forms for the supported
|
||||
|
||||
@@ -167,6 +167,15 @@ class TestValidation(unittest.TestCase):
|
||||
with self.assertRaises(T.ScriptError):
|
||||
T.normalise_script({'steps': [{'type': 'action', 'kind': 'event',
|
||||
'params': {'event': 3, 'src': 'zz'}}]}, _resolver)
|
||||
# the raw envelope action carries the same source override
|
||||
script = T.normalise_script({'steps': [
|
||||
{'type': 'action', 'kind': 'envelope',
|
||||
'params': {'event': 0x00, 'data': '', 'src': '83'}}]}, _resolver)
|
||||
self.assertEqual(script['steps'][0]['params'],
|
||||
{'event': 0x00, 'data': '', 'src': '83'})
|
||||
with self.assertRaises(T.ScriptError):
|
||||
T.normalise_script({'steps': [{'type': 'action', 'kind': 'envelope',
|
||||
'params': {'event': 3, 'src': 'zz'}}]}, _resolver)
|
||||
|
||||
def test_scp80_keyset_number_and_new_content_checks(self):
|
||||
# kvn (1..15) selects the preset's keyset; `por`/`files` are expect
|
||||
@@ -533,6 +542,15 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
apdu = scc2.sent[0].upper()
|
||||
self.assertIn('99010B82028381' + 'BF0108', apdu)
|
||||
# the raw envelope action takes the same source override
|
||||
scc3 = FakeScc()
|
||||
scc3.push('80C2', '', '9000')
|
||||
run = self.run_script(FakeServer(scc3), [
|
||||
{'type': 'action', 'kind': 'envelope',
|
||||
'params': {'event': 0x00, 'data': '', 'src': '83'}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
self.assertIn('99010082028381', scc3.sent[0].upper())
|
||||
|
||||
def test_scp80_uses_the_preset_and_advances_the_counter(self):
|
||||
scc = FakeScc()
|
||||
|
||||
Reference in New Issue
Block a user