EnvelopeCodec message layer; scripts are thin business-API wrappers
This commit is contained in:
1 parent
f15ae42db7
commit
363c7cc6a2
14 files changed
+551
-900
No files matched your search
@@ -0,0 +1,64 @@
|
||||
"""Low-level wire messages: envelopes to bytes and back.
|
||||
|
||||
:class:`EnvelopeCodec` owns everything byte-level about one device key:
|
||||
serialising a :class:`CommunicationObject` to encrypted TX bytes and
|
||||
parsing received RX bytes back into an envelope. Business logic (which
|
||||
command/arguments to send, retries, SoC extraction) lives one layer up in
|
||||
``device_local`` / ``battery`` / ``power``; standalone scripts must go
|
||||
through those layers, never reimplement this.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict
|
||||
|
||||
from .crypto import (
|
||||
DEFAULT_KEY_RADIX,
|
||||
decrypt_wire,
|
||||
encrypt_object,
|
||||
generate_secret_key,
|
||||
)
|
||||
from .envelope import CommunicationObject
|
||||
|
||||
|
||||
class EnvelopeCodec:
|
||||
"""Encrypt/decrypt envelopes under one device key.
|
||||
|
||||
:param key: ``generate_secret_key`` output for the device serial.
|
||||
:param radix: key-parse radix (16 — verified against live captures).
|
||||
"""
|
||||
|
||||
def __init__(self, key: str, radix: int = DEFAULT_KEY_RADIX) -> None:
|
||||
self.key = str(key)
|
||||
self.radix = int(radix)
|
||||
|
||||
@classmethod
|
||||
def for_serial(cls, equip_no: str,
|
||||
radix: int = DEFAULT_KEY_RADIX) -> "EnvelopeCodec":
|
||||
"""Codec keyed by a device serial (derives ``generate_secret_key``)."""
|
||||
return cls(generate_secret_key(equip_no), radix=radix)
|
||||
|
||||
# ---------------------------------------------------------------- encode
|
||||
def encode(self, envelope: CommunicationObject) -> bytes:
|
||||
"""``toJsonString`` → ``S1 || transform(S2)`` TX bytes."""
|
||||
return encrypt_object(envelope.to_json_string(), self.key,
|
||||
radix=self.radix)
|
||||
|
||||
# ---------------------------------------------------------------- decode
|
||||
def decode(self, wire: bytes) -> CommunicationObject:
|
||||
"""RX bytes → envelope (2nd-comma split, key from ``equipNo``).
|
||||
|
||||
Raises :class:`ValueError` on truncated/non-JSON/undecodable
|
||||
input — callers accumulating a stream treat that as "keep
|
||||
reading".
|
||||
"""
|
||||
return CommunicationObject.from_dict(
|
||||
json.loads(decrypt_wire(bytes(wire), radix=self.radix)))
|
||||
|
||||
def try_decode(self, wire: bytes) -> Dict[str, Any] | None:
|
||||
"""Like :meth:`decode` but returns the dict, or ``None``."""
|
||||
try:
|
||||
return self.decode(wire).to_dict()
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
Reference in new issue
Block a user