fix: report the real PoR/remote-SW result of every RAM install step (v3.6.2)
The RAM installer read `por['decoded']['response_status']`, but the compact
response decoder's `decoded` only carries {number_of_commands,
last_status_word, last_response_data} - so the suffix was always empty and
every step showed the useless `por_error_`, even when the PoR was por_ok.
The PoR verdict is the top-level `response_status`.
With that fixed, the decoded details also show that the remote command's own
status word was the real verdict all along: a captured live install returned
por_ok with `last_status_word` 6700/6F00 (the card rejected every RAM APDU)
while the installer reported success.
- new `_ram_step_result()`/`_por_remote_sw()`/`_ram_remote_sw_ok()`: a step
fails on a non-por_ok PoR, on a remote SW outside the success set (9000,
61xx more data, 62xx/63xx warnings, CAFE GP "more data"), or on an
undecodable PoR (a 9000 transport SW with no PoR at all is `no_por`, not a
failure). A decoded 61xx is explicitly success, per GP/ISO.
- step records gain por_sw/por_type/por_cntr/por_data/por_raw and
`por_error`; the response carries `failed_step` and a detailed error such
as `LOAD (1/9): remote SW 6700`; the log line now prints
`status=por_ok remote_sw=6700`.
- PWA: new pure `ramStepLine()` renders e.g.
`❌ Шаг 2: LOAD (1/9) — PoR ok · remote SW 6700 (Wrong length in Lc) · 274 B / 3 SMS`
(SW meaning via the existing lookupSw decoder) and the transport SW only
when it is not 9000.
- tests: tests/test_ota_helpers.py RamPorStepTest (success set incl. 61xx,
the captured 6700 failure, no-PoR/undecodable, expanded responses);
ram.test.js ramStepLine cases.
- docs/api.md RAM install step fields + failure semantics; AGENTS updated.
603 frontend / 487 Python green; version 3.6.2; sw simple-v275.
This commit is contained in:
+16
-5
@@ -477,7 +477,7 @@ Clears a finished run report (409 while a run is active).
|
||||
|
||||
### `POST /api/ram-install`
|
||||
|
||||
Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL[for load] → LOAD ×N → INSTALL[for install (+ make selectable)]) wrapped in SCP80 secured packets. Each step is sent via ENVELOPE and its PoR is checked; the sequence aborts on the first PoR error. The `.cap` archive (a ZIP of nested components) is parsed server-side in `_cap_parse`; no external tooling is required.
|
||||
Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL[for load] → LOAD ×N → INSTALL[for install (+ make selectable)]) wrapped in SCP80 secured packets. Each step is sent via ENVELOPE; the PoR verdict and the remote command's own status word are both checked and the sequence aborts on the first failure (a non-`por_ok` PoR, a remote SW outside the success set, or an undecodable PoR). The `.cap` archive (a ZIP of nested components) is parsed server-side in `_cap_parse`; no external tooling is required.
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
@@ -511,9 +511,9 @@ Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL
|
||||
**Response (success):**
|
||||
```json
|
||||
{"success": true, "failed_step": null,
|
||||
"steps": [{"name": "install_for_load", "apdu": "80E60200...", "por_status": "por_ok", "sw": "9000", "bytes": 58, "segments": 1},
|
||||
{"name": "load_0", "apdu": "80E80000...", "por_status": "por_ok", "sw": "9000", "bytes": 274, "segments": 3},
|
||||
{"name": "install_for_install", "apdu": "80E60C00...", "por_status": "por_ok", "sw": "9000", "bytes": 66, "segments": 1}],
|
||||
"steps": [{"name": "INSTALL [for load]", "por_status": "por_ok", "por_sw": "9000", "por_type": "compact", "por_cntr": "000000011B", "sw": "9000", "bytes": 58, "segments": 1},
|
||||
{"name": "LOAD (1/9)", "por_status": "por_ok", "por_sw": "9000", "por_type": "compact", "sw": "9000", "bytes": 274, "segments": 3},
|
||||
{"name": "INSTALL [for install]", "por_status": "por_ok", "por_sw": "9000", "por_type": "compact", "sw": "9000", "bytes": 66, "segments": 1}],
|
||||
"final_cntr": "0000000004",
|
||||
"load_file_aid": "A000000003000000",
|
||||
"module_aid": "A000000003000000",
|
||||
@@ -527,7 +527,18 @@ Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL
|
||||
default), `load_block_size_requested` echoes an explicit `load_block_size`
|
||||
(null = the default was used) and `load_block_size_auto` marks that default.
|
||||
Each step reports the secured packet size `bytes` and the number of SMS
|
||||
`segments` it took.
|
||||
`segments` it took. `sw` is the transport (ENVELOPE/GET RESPONSE) status
|
||||
word; the RAM results are in `por_status` (the PoR verdict: `por_ok`,
|
||||
`rc_cc_ds_failed`, `cntr_low`, ... or `no_por` when the card sent none) and
|
||||
`por_sw` (the remote command's own status word from the compact/expanded
|
||||
response, with `por_type`/`por_cntr`/`por_data`/`por_raw` for context).
|
||||
|
||||
A step fails when the PoR is not `por_ok`, when `por_sw` is outside the
|
||||
success set (`9000`, `61xx` more data, `62xx`/`63xx` warnings, `CAFE` GP
|
||||
"more data"), or when response data arrived but could not be decoded. The
|
||||
failing step carries `por_error` (e.g. `remote SW 6700`) and the response
|
||||
sets `success: false`, `failed_step` and a detailed `error` such as
|
||||
`LOAD (1/9): remote SW 6700`.
|
||||
|
||||
**Response (failure):**
|
||||
```json
|
||||
|
||||
+35
-7
@@ -1665,7 +1665,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.6.1';
|
||||
const SIMPLE_VERSION = '3.6.2';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -8859,6 +8859,33 @@ async function ramDeleteFromExplorer(aid, withCascade) {
|
||||
await ramExplore(sp);
|
||||
}
|
||||
|
||||
// One RAM install step line: the PoR verdict plus the remote command's
|
||||
// status word (9000/61xx/62xx/63xx/CAFE are success; anything else is the
|
||||
// card's error) - the details that used to be lost behind a bare
|
||||
// "por_error_" label. The envelope/GET RESPONSE SW is only shown when it is
|
||||
// not a plain 9000.
|
||||
function ramStepLine(s, idx) {
|
||||
const ok = !s.por_error && (s.por_status === 'por_ok' || s.por_status === 'no_por');
|
||||
let line = (ok ? '\u2705' : '\u274c') + ' ' + t('Step') + ' ' + (idx + 1) + ': ' +
|
||||
s.name + ' \u2014 ';
|
||||
if (s.por_status === 'por_ok') line += t('PoR ok');
|
||||
else if (s.por_status === 'no_por') line += t('no PoR');
|
||||
else if (s.por_status && s.por_status.indexOf('por_error') === 0) {
|
||||
const legacy = s.por_status.replace(/^por_error_?/, '');
|
||||
line += t('PoR error') + (legacy ? ' ' + legacy : '');
|
||||
} else if (s.por_status) line += t('PoR error') + ' ' + s.por_status;
|
||||
else line += t('PoR unknown');
|
||||
if (s.por_sw) {
|
||||
const name = lookupSw(s.por_sw.substr(0, 2), s.por_sw.substr(2, 2), 'gp');
|
||||
line += ' \u00b7 ' + t('remote SW') + ' ' + s.por_sw + (name ? ' (' + name + ')' : '');
|
||||
}
|
||||
if (s.sw && s.sw !== '9000') line += ' \u00b7 ' + t('transport SW') + ' ' + s.sw;
|
||||
if (s.bytes && s.segments) {
|
||||
line += ' \u00b7 ' + s.bytes + ' ' + t('bytes') + ' / ' + s.segments + ' SMS';
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
async function ramInstallCap(sp) {
|
||||
const fileInput = document.getElementById('ram-cap-file');
|
||||
const file = fileInput.files[0];
|
||||
@@ -8894,12 +8921,7 @@ async function ramInstallCap(sp) {
|
||||
stepsEl.classList.remove('hidden');
|
||||
|
||||
let txt = '';
|
||||
(data.steps || []).forEach((s, idx) => {
|
||||
const mark = s.por_status === 'por_ok' ? '✅' : '❌';
|
||||
let line = mark + ' ' + t('Step') + ' ' + (idx + 1) + ': ' + s.name + ' — ' + s.por_status + ' (SW ' + s.sw + ')';
|
||||
if (s.bytes && s.segments) line += ' · ' + s.bytes + ' ' + t('bytes') + ' / ' + s.segments + ' SMS';
|
||||
txt += line + '\n';
|
||||
});
|
||||
(data.steps || []).forEach((s, idx) => { txt += ramStepLine(s, idx) + '\n'; });
|
||||
stepsEl.textContent = txt;
|
||||
|
||||
if (data.success) {
|
||||
@@ -16787,6 +16809,12 @@ const LANG_RU = {
|
||||
'default: 9000 (91?? when polling)': 'по умолчанию: 9000 (91?? при опросе)',
|
||||
'Expected data (hex, optional)': 'Ожидаемые данные (hex, опционально)',
|
||||
'PoR check': 'Проверка PoR',
|
||||
'PoR ok': 'PoR: успешно',
|
||||
'no PoR': 'PoR: нет',
|
||||
'PoR error': 'Ошибка PoR',
|
||||
'PoR unknown': 'PoR: неизвестно',
|
||||
'remote SW': 'SW команды',
|
||||
'transport SW': 'транспортный SW',
|
||||
'Any': 'Любой',
|
||||
'No PoR expected': 'PoR не ожидается',
|
||||
'On mismatch': 'При несовпадении',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v274';
|
||||
const CACHE = 'simple-v275';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -22,7 +22,7 @@ function extractFunc(src, name) {
|
||||
}
|
||||
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml',
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute',
|
||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
@@ -259,6 +259,25 @@ function fakeRamDocument(ids) {
|
||||
return els;
|
||||
}
|
||||
|
||||
test('ramStepLine shows the PoR verdict and the remote status word', () => {
|
||||
globalThis.t = s => s;
|
||||
globalThis.lookupSw = (a, b) => (a + b === '6700' ? 'Wrong length in Lc' : '');
|
||||
const okLine = ramStepLine({ name: 'INSTALL [for load]', por_status: 'por_ok',
|
||||
por_sw: '9000', sw: '9000', bytes: 50, segments: 1 }, 0);
|
||||
assert.ok(okLine.startsWith('\u2705'), okLine);
|
||||
assert.ok(okLine.includes('PoR ok'), okLine);
|
||||
assert.ok(okLine.includes('remote SW 9000'), okLine);
|
||||
assert.ok(okLine.includes('50 bytes / 1 SMS'), okLine);
|
||||
const badLine = ramStepLine({ name: 'LOAD (1/9)', por_status: 'por_ok', por_sw: '6700',
|
||||
por_error: 'remote SW 6700', sw: '9000', bytes: 274, segments: 3 }, 1);
|
||||
assert.ok(badLine.startsWith('\u274c'), badLine);
|
||||
assert.ok(badLine.includes('remote SW 6700 (Wrong length in Lc)'), badLine);
|
||||
const porLine = ramStepLine({ name: 'LOAD', por_status: 'por_error_cntr_low' }, 2);
|
||||
assert.ok(porLine.includes('PoR error cntr_low'), porLine);
|
||||
const noPor = ramStepLine({ name: 'LOAD', por_status: 'no_por' }, 3);
|
||||
assert.ok(noPor.startsWith('\u2705') && noPor.includes('no PoR'), noPor);
|
||||
});
|
||||
|
||||
test('ramOpChanged clears the executed status only on a real op change', () => {
|
||||
const els = fakeRamDocument(['ram-op', 'ram-install-params', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress']);
|
||||
_ramOpLast = null;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.6.1"
|
||||
version = "3.6.2"
|
||||
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.
|
||||
|
||||
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.6.1'
|
||||
VERSION = '3.6.2'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -1095,6 +1095,69 @@ def _send_secured_packet(scc, sp_hex, oa_number, sm_sc=None, include_cpi=True,
|
||||
'response_data': data if data else None}
|
||||
|
||||
|
||||
# Remote-command status words that count as success in the RAM dialog:
|
||||
# 9000 = normal; 61xx = more data available (GET RESPONSE); 62xx/63xx =
|
||||
# warnings (63xx/63Cx often carry "more data available" too); CAFE =
|
||||
# GlobalPlatform "more data available" (GET STATUS pages).
|
||||
_RAM_REMOTE_SW_OK = ('9000', 'CAFE')
|
||||
|
||||
|
||||
def _ram_remote_sw_ok(sw):
|
||||
s = str(sw or '').upper()
|
||||
return bool(s) and (s in _RAM_REMOTE_SW_OK or s[:2] in ('61', '62', '63'))
|
||||
|
||||
|
||||
def _por_remote_sw(por):
|
||||
"""Last remote-command status word from a decoded PoR, '' when none:
|
||||
compact responses carry last_status_word, expanded ones a status word
|
||||
per command."""
|
||||
dec = (por or {}).get('decoded') or {}
|
||||
sw = str(dec.get('last_status_word') or '').upper()
|
||||
if sw:
|
||||
return sw
|
||||
for r in reversed((por or {}).get('responses') or []):
|
||||
if r.get('status_word'):
|
||||
return str(r['status_word']).upper()
|
||||
return ''
|
||||
|
||||
|
||||
def _ram_step_result(step_name, last_sw, por, por_hex, bytes_, segments):
|
||||
"""Assemble a RAM-install step record and its failure reason.
|
||||
|
||||
The PoR verdict is the top-level `response_status`; the remote command's
|
||||
own status word lives in `decoded.last_status_word` (compact) or in the
|
||||
expanded `responses` list. A step fails on a non-por_ok PoR, on a remote
|
||||
SW outside `_RAM_REMOTE_SW_OK`, or when a PoR arrived but could not be
|
||||
decoded (a 9000 transport SW with no PoR at all is 'no_por', not a
|
||||
failure)."""
|
||||
step = {'name': step_name, 'sw': last_sw, 'bytes': bytes_, 'segments': segments}
|
||||
error = None
|
||||
if por:
|
||||
pstatus = str(por.get('response_status') or '')
|
||||
remote_sw = _por_remote_sw(por)
|
||||
step['por_status'] = pstatus or 'unknown'
|
||||
step['por_type'] = por.get('response_type')
|
||||
step['por_cntr'] = por.get('cntr')
|
||||
step['por_data'] = (por.get('decoded') or {}).get('last_response_data', '')
|
||||
step['por_raw'] = por.get('raw')
|
||||
if remote_sw:
|
||||
step['por_sw'] = remote_sw
|
||||
if pstatus != 'por_ok':
|
||||
error = 'PoR %s' % (pstatus or 'unknown')
|
||||
elif remote_sw and not _ram_remote_sw_ok(remote_sw):
|
||||
error = 'remote SW %s' % remote_sw
|
||||
elif last_sw == '9000' and not por_hex:
|
||||
step['por_status'] = 'no_por'
|
||||
else:
|
||||
step['por_status'] = 'unknown'
|
||||
if por_hex:
|
||||
step['por_raw'] = por_hex
|
||||
error = 'PoR undecodable'
|
||||
if error:
|
||||
step['por_error'] = error
|
||||
return step, error
|
||||
|
||||
|
||||
def _decode_por(spi1, spi2, kic, kid, cntr_hex, kic_key_hex, kid_key_hex, response_hex):
|
||||
from pySim.ota import OtaDialectSms, CompactRemoteResp
|
||||
from osmocom.utils import h2b, b2h
|
||||
@@ -5990,6 +6053,8 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
spi2_val = int(spi2, 16)
|
||||
por_in_submit = bool(spi2_val & 0x20)
|
||||
|
||||
failure = {}
|
||||
|
||||
def _send_gp_apdu(apdu_hex, step_name):
|
||||
nonlocal cntr, encode_error
|
||||
try:
|
||||
@@ -6031,18 +6096,20 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
por_hex = tpdu_b[idx:].hex()
|
||||
por_src = 'sms-submit'
|
||||
por = _decode_por(spi1, spi2, kic, kid, cntr, kic_key, kid_key, por_hex)
|
||||
por_status = 'unknown'
|
||||
if por and por.get('decoded'):
|
||||
ps = por['decoded'].get('response_status', '')
|
||||
por_status = 'por_ok' if ps == '9100' else 'por_error_%s' % ps
|
||||
sys.stderr.write('RAM-INSTALL: %s PoR[%s] status=%s (%d B, %d SM)\n' % (
|
||||
step_name, por_src, ps, result['bytes'], result['segments']))
|
||||
elif last_sw == '9000' and not por_hex:
|
||||
por_status = 'no_por'
|
||||
steps.append({'name': step_name, 'por_status': por_status, 'sw': last_sw,
|
||||
'bytes': result['bytes'], 'segments': result['segments']})
|
||||
step, step_error = _ram_step_result(
|
||||
step_name, last_sw, por, por_hex,
|
||||
result['bytes'], result['segments'])
|
||||
steps.append(step)
|
||||
sys.stderr.write('RAM-INSTALL: %s PoR[%s] status=%s remote_sw=%s%s (%d B, %d SM)\n' % (
|
||||
step_name, por_src, step.get('por_status', '?'),
|
||||
step.get('por_sw', '-'),
|
||||
(' error=%s' % step_error) if step_error else '',
|
||||
result['bytes'], result['segments']))
|
||||
# Increment counter
|
||||
cntr = '%010X' % ((int(cntr, 16) + 1) % (2 ** 32))
|
||||
if step_error:
|
||||
failure['error'] = '%s: %s' % (step_name, step_error)
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
if submit_handler and hasattr(scc, '_tp'):
|
||||
@@ -6065,7 +6132,8 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
step_name = 'LOAD (%d/%d)' % (apdu_idx, len(seq) - 2)
|
||||
if not _send_gp_apdu(gp_apdu, step_name):
|
||||
resp = {'success': False, 'steps': steps, 'failed_step': len(steps),
|
||||
'error': encode_error or ('%s failed' % step_name),
|
||||
'error': (encode_error or failure.get('error')
|
||||
or ('%s failed' % step_name)),
|
||||
'load_file_aid': loadfile_aid, 'module_aid': module_aid,
|
||||
'load_block_size': block_size,
|
||||
'load_block_size_requested': block_size_req,
|
||||
|
||||
@@ -30,6 +30,9 @@ from pysim_simple_server.server import (
|
||||
_ota_reference,
|
||||
_parse_select_item,
|
||||
_parse_setup_menu_items,
|
||||
_por_remote_sw,
|
||||
_ram_remote_sw_ok,
|
||||
_ram_step_result,
|
||||
_record_tr,
|
||||
_send_secured_packet,
|
||||
_spi_from_bytes,
|
||||
@@ -1243,3 +1246,62 @@ class TerminalProfileTest(unittest.TestCase):
|
||||
self.assertEqual(server_obj.event_list, [0x09])
|
||||
self.assertEqual(server_obj.sim_menu, 'menu')
|
||||
self.assertTrue(resp['menu'])
|
||||
|
||||
|
||||
class RamPorStepTest(unittest.TestCase):
|
||||
"""The RAM install reports the PoR verdict and the remote command's own
|
||||
status word. Vectors: a captured live response where every remote
|
||||
command returned SW 6700/6F00 while the PoR itself was por_ok, and
|
||||
synthetic success responses (9000, 6113)."""
|
||||
|
||||
# 02 71 00 | 000e 0a | TAR b00011/000000 | CNTR | PCNT | STS=00 | compact
|
||||
POR_INSTALL = '027100000e0a000000000000011b0000026700'
|
||||
POR_INSTALL_OK = '027100000e0ab0001100000000000000019000'
|
||||
POR_INSTALL_61XX = '027100000e0ab0001100000000000000016113'
|
||||
|
||||
@staticmethod
|
||||
def _por(hexstr):
|
||||
return _decode_por('00', '00', '01', '01', '0000000000',
|
||||
'00' * 16, '00' * 16, hexstr)
|
||||
|
||||
def test_remote_sw_success_set(self):
|
||||
for sw in ('9000', '6113', '62F1', '6310', 'CAFE'):
|
||||
self.assertTrue(_ram_remote_sw_ok(sw), sw)
|
||||
for sw in ('6700', '6F00', '6A82', '6400', '', None):
|
||||
self.assertFalse(_ram_remote_sw_ok(sw), sw)
|
||||
|
||||
def test_step_reports_the_remote_sw_failure(self):
|
||||
por = self._por(self.POR_INSTALL)
|
||||
self.assertEqual(por['response_status'], 'por_ok')
|
||||
step, err = _ram_step_result('LOAD (1/9)', '9000', por,
|
||||
self.POR_INSTALL, 274, 3)
|
||||
self.assertEqual(step['por_status'], 'por_ok')
|
||||
self.assertEqual(step['por_sw'], '6700')
|
||||
self.assertEqual(step['por_type'], 'compact')
|
||||
self.assertEqual(err, 'remote SW 6700')
|
||||
self.assertEqual(step['por_error'], 'remote SW 6700')
|
||||
|
||||
def test_step_accepts_9000_and_61xx_remote_sw(self):
|
||||
for hexstr, sw in ((self.POR_INSTALL_OK, '9000'),
|
||||
(self.POR_INSTALL_61XX, '6113')):
|
||||
por = self._por(hexstr)
|
||||
step, err = _ram_step_result('LOAD (1/9)', '9000', por, hexstr, 10, 1)
|
||||
self.assertIsNone(err, sw)
|
||||
self.assertNotIn('por_error', step)
|
||||
self.assertEqual(step['por_sw'], sw)
|
||||
|
||||
def test_step_no_por_and_undecodable(self):
|
||||
# transport SW 9000 with no response data at all: no PoR expected
|
||||
step, err = _ram_step_result('LOAD', '9000', None, '', 10, 1)
|
||||
self.assertIsNone(err)
|
||||
self.assertEqual(step['por_status'], 'no_por')
|
||||
# response data present but undecodable: a failure with the raw kept
|
||||
step, err = _ram_step_result('LOAD', '9000', None, 'DEADBEEF', 10, 1)
|
||||
self.assertEqual(err, 'PoR undecodable')
|
||||
self.assertEqual(step['por_raw'], 'DEADBEEF')
|
||||
|
||||
def test_remote_sw_from_expanded_response(self):
|
||||
por = {'response_status': 'por_ok', 'decoded': {},
|
||||
'responses': [{'status_word': '9000'}, {'status_word': '6A82'}]}
|
||||
self.assertEqual(_por_remote_sw(por), '6A82')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user