v3.33.0: Edit decoded — SPN, PLMNwAcT family, ADN family, PNN

The file manager gains a client-side decoded editor.  EF_ENCODERS pairs
each editable EF with a pure encoder and a form schema (fields / list /
items); the file is read raw, the decoder fills the form, the browser
re-encodes it and the existing update path writes it.

Shapes: SPN (display-condition bits + Annex A text), PLMNwAcT/
OPLMNwAcT/HPLMNwAcT (rows with PLMN + ACT, add/remove),
ADN/FDN/SDN/MSISDN/LND/BDN (one form per record with a checkbox that
auto-checks on change; BCD, length and EXT handling; BDN's own X+15
layout) and PNN (per-record TLV items 43/45/80, add/remove, unknown
TLVs preserved).

Unchanged fields are copied verbatim (SSC/extended-BCD numbers, PLMN
rows including unmodelled satellite ACT bits, PNN codings/AddCI bits,
all-FF files, the BDN comparison-method byte), every checked record is
validated and encoded before anything is written, and edits that would
break a reference are refused.

Also: docs/decoded-editing.md (the spec survey behind the design and
the release waves), the efPlmnAct EC-GSM-IoT-only 0x88 fix, the PNN
spare-bit decode fix, and the /api/write|read cmd2-exception
false-success fix.

1022 Python + 1013 frontend green; simple-v466.
This commit is contained in:
2026-10-23 11:55:31 +03:00
parent 9411fd775d
commit d036ef79a1
16 changed files with 2508 additions and 27 deletions
+31 -1
View File
@@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
VERSION = '3.32.7'
VERSION = '3.33.0'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -249,6 +249,24 @@ def _strip_ansi(text):
return re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', text)
def _cmd_exception(output):
"""The first non-empty line of a cmd2/pySim exception captured from a
command's output, or None.
cmd2 catches a command's exception, prints ``EXCEPTION of type ...`` and
(in debug mode) a traceback instead of re-raising, so the HTTP handlers
that only look for ``got (XXXX)`` reported success while nothing was
written. Both markers are command-level failure evidence.
"""
text = output or ''
if 'EXCEPTION of type' not in text and 'Traceback (most recent call last)' not in text:
return None
for line in text.splitlines():
if line.strip():
return line.strip()
return 'command failed'
def _parse_help_text(text):
result = {'usage': '', 'description': '', 'args': []}
lines = text.split('\n')
@@ -9843,6 +9861,12 @@ class PysimHandler(BaseHTTPRequestHandler):
sys.stderr = old_stderr
finally:
apdu_times = _end_apdu_time_collection()
exc = _cmd_exception(output)
if exc:
resp = {'success': False, 'error': exc}
self._send_json(resp)
self._log_resp(resp)
return
sw_match = re.search(r'SW:\s*(\w+)', output)
err_match = re.search(r'got (\w+)', output)
if err_match:
@@ -9931,6 +9955,12 @@ class PysimHandler(BaseHTTPRequestHandler):
finally:
app.stdout = old_stdout
sys.stderr = old_stderr
exc = _cmd_exception(output)
if exc:
resp = {'success': False, 'error': exc}
self._send_json(resp)
self._log_resp(resp)
return
sw_match = re.search(r'SW:\s*(\w+)', output)
err_match = re.search(r'got (\w+)', output)
if err_match: