fix: v3.8.0 preset-store review findings
- /api/send-ota no longer 500s on a counter-less body: the plain `sp` path
reports final_cntr only when the counter is usable (_counter_valid, 1-10 hex
digits) - before, the new plain-path report ran int('') and the request died
in the generic handler. The failure path is guarded the same way.
- _decode_por wraps the keyset/SPI construction in the same "never raise" rule
as the decode: a malformed counter or missing keys return None instead of
escaping the handler.
- presets.add() never accepts a taken id (a fresh uuid is assigned, like the
import path), the counter is validated as 1-10 hex digits instead of being
silently masked/kept, and set_counter() ignores a malformed value with a log
line instead of writing it.
- the preset endpoints answer a JSON 500 when the store write fails (read-only
home, disk full) instead of dropping the connection.
- tests: counter-less send-ota, store-write 500, duplicate id, counter
validation (add/update/set_counter), and the temp-file check no longer
matches the audit file; docs/api.md documents final_cntr for the plain path
and preset_id.
This commit is contained in:
@@ -208,7 +208,9 @@ class PresetStore:
|
||||
out[k] = v
|
||||
out['spi1'] = out['spi1'] or '16'
|
||||
out['spi2'] = out['spi2'] or '01'
|
||||
out['cntr'] = counter_hex(out['cntr'] or '1')
|
||||
# the counter is kept as typed here and validated + padded in
|
||||
# _validate(): a bad value must be refused, never silently masked
|
||||
out['cntr'] = out['cntr'] or '1'
|
||||
for k, d in TAR_DEFAULTS.items():
|
||||
out[k] = out[k] or d
|
||||
pid = str(fields.get('id') or '').strip().lower()
|
||||
@@ -225,6 +227,10 @@ class PresetStore:
|
||||
raise PresetError('PSK identity and PSK key must be set together')
|
||||
if p['pskKey'] and not re.fullmatch(r'[0-9A-F]{32}', p['pskKey']):
|
||||
raise PresetError('PSK key must be 32 hex characters')
|
||||
if not re.fullmatch(r'[0-9A-F]{1,10}', p['cntr']):
|
||||
raise PresetError('counter must be 1-10 hex digits')
|
||||
# validated: store the protocol's fixed-width form
|
||||
p['cntr'] = counter_hex(p['cntr'])
|
||||
norm = normalize_iccid(p['iccid'])
|
||||
if norm:
|
||||
for other in self._presets:
|
||||
@@ -267,6 +273,10 @@ class PresetStore:
|
||||
def add(self, fields):
|
||||
with self._lock:
|
||||
p = self._normalise(fields or {})
|
||||
if self._find(p['id']):
|
||||
# a caller-supplied id that is already taken never wins: ids
|
||||
# must stay unique (the same guard the import uses)
|
||||
p['id'] = uuid.uuid4().hex
|
||||
self._validate(p)
|
||||
self._presets.append(p)
|
||||
self._save()
|
||||
@@ -315,7 +325,12 @@ class PresetStore:
|
||||
p = self._find(pid)
|
||||
if p is None:
|
||||
return None
|
||||
new = counter_hex(cntr)
|
||||
new = str(cntr or '').strip().upper()
|
||||
if not re.fullmatch(r'[0-9A-F]{1,10}', new):
|
||||
sys.stderr.write('PRESETS: ignoring malformed counter %r (%s)\n'
|
||||
% (cntr, source))
|
||||
return dict(p)
|
||||
new = counter_hex(new)
|
||||
if new == p['cntr']:
|
||||
return dict(p)
|
||||
if not counter_ahead(p['cntr'], new):
|
||||
|
||||
Reference in New Issue
Block a user