v3.19.0: the applet-agent script batch - por objects, alpha/sms checks, scp80 format
The test scripts get the pieces an applet test needed:
- scp80 check.por accepts an object {status?, sw?, data?} asserting the
decoded PoR of this very exchange (inline or via SEND SHORT MESSAGE);
the evaluation is shared with the expect-step por check
(_por_check_results). The editor's PoR select gains 'custom' with the
three fields (an empty object refuses on save).
- a new alpha content check reads the command's Alpha identifier (05/85,
Annex A coded) - the text check stays bound to the 8D Text string, so a
command carrying both keeps them distinct.
- a new sms content check asserts a SEND SHORT MESSAGE's submit TPDU:
DA / PID / DCS / UDL / UD (the parsed fields are logged and reported;
an applet's own SMS, not the OTA PoR the por check decodes).
- scp80.params.format wraps the C-APDU in the TS 102 226 5.2.1 Command
Scripting template (expanded = AA..., expanded-ae = AE 80...00 00); the
log shows WRAPPED=, the report C-APDU=<effective> format=<fmt>.
- docs: Worksheet 1 addresses an RFM TAR (B00000/B00001/B00010 compact,
B00120/B00140/B00130 expanded) with the own-TAR and MSL/spi1 notes, and
file-write is documented as a direct APDU that does not fire
EVENT_REMOTE_FILE_UPDATE.
Tests: 819 frontend + 724 Python; sw cache simple-v387.
This commit is contained in:
@@ -54,6 +54,21 @@ class ScriptError(ValueError):
|
||||
|
||||
# ─── normalisation helpers ──────────────────────────────────────────────
|
||||
|
||||
def _por_fields(c, what='por check'):
|
||||
"""The decoded-PoR assertion fields (status/sw/data); at least one."""
|
||||
out = {}
|
||||
status = c.get('status')
|
||||
if status is not None and str(status).strip():
|
||||
out['status'] = str(status).strip()
|
||||
if c.get('sw') not in (None, ''):
|
||||
out['sw'] = _check_spec(c.get('sw'), what + ' sw')
|
||||
if c.get('data') not in (None, ''):
|
||||
out['data'] = _check_spec(c.get('data'), what + ' data')
|
||||
if not (out.get('status') or out.get('sw') or out.get('data')):
|
||||
raise ScriptError(what + ': status, sw or data is required')
|
||||
return out
|
||||
|
||||
|
||||
def _fail_level(value, default='error'):
|
||||
if value in (None, ''):
|
||||
return default
|
||||
@@ -312,6 +327,15 @@ def _normalise_params(kind, p):
|
||||
# the counter stay preset-owned, the step only picks which keyset.
|
||||
if p.get('kvn') not in (None, ''):
|
||||
out['kvn'] = _int(p.get('kvn'), 'scp80 keyset number', 1, 15)
|
||||
# The command format: the C-APDU verbatim (compact, the default) or
|
||||
# wrapped in the expanded Command Scripting template (TS 102 226
|
||||
# 5.2.1) - `expanded` = AA/<len>/22/..., `expanded-ae` = AE 80/22/00 00.
|
||||
fmt = str(p.get('format') or '').strip().lower()
|
||||
if fmt:
|
||||
if fmt not in ('compact', 'expanded', 'expanded-ae'):
|
||||
raise ScriptError('scp80: format must be compact, expanded or '
|
||||
'expanded-ae')
|
||||
out['format'] = fmt
|
||||
return out
|
||||
if kind == 'status':
|
||||
attempts = p.get('attempts')
|
||||
@@ -337,14 +361,20 @@ def _normalise_check(check, kind, params):
|
||||
sw = {'mode': 'exact', 'value': '9000'}
|
||||
data = _check_spec(check.get('data'), 'check.data')
|
||||
por = check.get('por')
|
||||
if por is None:
|
||||
if isinstance(por, dict):
|
||||
# The decoded-PoR assertion (available on scp80 actions whether the
|
||||
# PoR arrived inline or via SEND SHORT MESSAGE).
|
||||
if kind != 'scp80':
|
||||
raise ScriptError('check.por is only valid for scp80 actions')
|
||||
por = _por_fields(por, 'check.por')
|
||||
elif por is None:
|
||||
por = 'any'
|
||||
else:
|
||||
por = str(por).lower()
|
||||
if kind != 'scp80':
|
||||
raise ScriptError('check.por is only valid for scp80 actions')
|
||||
if por not in POR_CHECKS:
|
||||
raise ScriptError('check.por must be none, ok or any')
|
||||
raise ScriptError('check.por must be none, ok, any or an object')
|
||||
return {'sw': sw, 'data': data, 'por': por}
|
||||
|
||||
|
||||
@@ -416,15 +446,36 @@ def _normalise_content_check(c, default_level):
|
||||
'on_fail': level}
|
||||
if kind == 'por':
|
||||
out = {'kind': 'por', 'on_fail': level}
|
||||
status = c.get('status')
|
||||
if status is not None and str(status).strip():
|
||||
out['status'] = str(status).strip()
|
||||
if c.get('sw') not in (None, ''):
|
||||
out['sw'] = _check_spec(c.get('sw'), 'por check sw')
|
||||
if c.get('data') not in (None, ''):
|
||||
out['data'] = _check_spec(c.get('data'), 'por check data')
|
||||
if not (out.get('status') or out.get('sw') or out.get('data')):
|
||||
raise ScriptError('por check: status, sw or data is required')
|
||||
out.update(_por_fields(c, 'por check'))
|
||||
return out
|
||||
if kind == 'alpha':
|
||||
mode = str(c.get('mode') or 'contains').lower()
|
||||
if mode not in ('contains', 'exact'):
|
||||
raise ScriptError('alpha check: mode must be contains or exact')
|
||||
if c.get('value') is None:
|
||||
raise ScriptError('alpha check: value is required')
|
||||
return {'kind': 'alpha', 'mode': mode, 'value': str(c['value']),
|
||||
'case_sensitive': bool(c.get('case_sensitive', True)),
|
||||
'on_fail': level}
|
||||
if kind == 'sms':
|
||||
# The SEND SHORT MESSAGE TPDU fields (an applet's own SMS, as opposed
|
||||
# to the PoR of an OTA exchange).
|
||||
out = {'kind': 'sms', 'on_fail': 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):
|
||||
raise ScriptError('sms check: da must be up to 20 digits')
|
||||
out['da'] = d
|
||||
for key in ('pid', 'dcs'):
|
||||
if c.get(key) not in (None, ''):
|
||||
out[key] = _check_spec(c.get(key), 'sms check %s' % key)
|
||||
if c.get('udl') not in (None, ''):
|
||||
out['udl'] = _int(c.get('udl'), 'sms check udl', 0, 255)
|
||||
if c.get('ud') not in (None, ''):
|
||||
out['ud'] = _check_spec(c.get('ud'), 'sms check ud')
|
||||
if not any(k in out for k in ('da', 'pid', 'dcs', 'udl', 'ud')):
|
||||
raise ScriptError('sms check: da, pid, dcs, udl or ud is required')
|
||||
return out
|
||||
if kind == 'files':
|
||||
val = c.get('files', c.get('value'))
|
||||
|
||||
Reference in New Issue
Block a user