init commit
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""Packet frame encoding/decoding for LibreVNA protocol stream."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from ..enums import PacketType
|
||||
from ..exceptions import CRCError, ParseError
|
||||
from ..models import Packet
|
||||
from .crc32 import crc32
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_HEADER = 0x5A
|
||||
_FRAME_OVERHEAD = 8 # header + length + type + crc
|
||||
_MAX_FRAME_LENGTH = 4096
|
||||
|
||||
|
||||
_NO_CRC_PACKET_TYPES = {
|
||||
PacketType.VNA_DATAPOINT,
|
||||
}
|
||||
|
||||
|
||||
def encode_frame(packet: Packet) -> bytes:
|
||||
"""Encode one packet into framed wire format."""
|
||||
payload = packet.payload
|
||||
if not isinstance(payload, (bytes, bytearray, memoryview)):
|
||||
raise TypeError("Packet payload must be bytes-like")
|
||||
|
||||
payload_bytes = bytes(payload)
|
||||
length = _FRAME_OVERHEAD + len(payload_bytes)
|
||||
if length > 0xFFFF:
|
||||
raise ValueError("Packet is too large for protocol frame length field")
|
||||
|
||||
frame = bytearray(length)
|
||||
frame[0] = _HEADER
|
||||
struct.pack_into("<H", frame, 1, length)
|
||||
frame[3] = int(packet.type)
|
||||
frame[4 : 4 + len(payload_bytes)] = payload_bytes
|
||||
|
||||
crc_value = 0
|
||||
if packet.type not in _NO_CRC_PACKET_TYPES:
|
||||
crc_value = crc32(frame[:-4])
|
||||
struct.pack_into("<I", frame, length - 4, crc_value)
|
||||
return bytes(frame)
|
||||
|
||||
|
||||
def decode_frame(frame: bytes) -> Packet:
|
||||
"""Decode and validate one complete frame."""
|
||||
if len(frame) < _FRAME_OVERHEAD:
|
||||
raise ParseError("Frame is too short")
|
||||
if frame[0] != _HEADER:
|
||||
raise ParseError("Invalid frame header")
|
||||
|
||||
(length,) = struct.unpack_from("<H", frame, 1)
|
||||
if length != len(frame):
|
||||
raise ParseError(f"Frame length mismatch: declared {length}, got {len(frame)}")
|
||||
|
||||
packet_type_raw = frame[3]
|
||||
try:
|
||||
packet_type = PacketType(packet_type_raw)
|
||||
except ValueError as exc:
|
||||
raise ParseError(f"Unknown packet type id {packet_type_raw}") from exc
|
||||
|
||||
(received_crc,) = struct.unpack_from("<I", frame, length - 4)
|
||||
if packet_type in _NO_CRC_PACKET_TYPES:
|
||||
if received_crc != 0:
|
||||
raise CRCError("VNADatapoint packet must carry zero CRC")
|
||||
else:
|
||||
computed_crc = crc32(frame[:-4])
|
||||
if received_crc != computed_crc:
|
||||
raise CRCError(
|
||||
f"CRC mismatch for packet {packet_type.name}: "
|
||||
f"received 0x{received_crc:08X}, computed 0x{computed_crc:08X}"
|
||||
)
|
||||
|
||||
return Packet(type=packet_type, payload=frame[4:-4])
|
||||
|
||||
|
||||
class FrameScanner:
|
||||
"""Incremental frame scanner for raw USB byte streams."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize internal undecoded byte buffer."""
|
||||
self._buffer = bytearray()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Drop all buffered undecoded bytes."""
|
||||
self._buffer.clear()
|
||||
|
||||
def feed(self, chunk: bytes) -> list[Packet]:
|
||||
"""Feed raw bytes and return every fully decoded packet."""
|
||||
if not chunk:
|
||||
return []
|
||||
self._buffer.extend(chunk)
|
||||
|
||||
decoded: list[Packet] = []
|
||||
while True:
|
||||
header_index = self._buffer.find(_HEADER)
|
||||
if header_index < 0:
|
||||
self._buffer.clear()
|
||||
break
|
||||
if header_index > 0:
|
||||
del self._buffer[:header_index]
|
||||
|
||||
if len(self._buffer) < 4:
|
||||
break
|
||||
|
||||
(length,) = struct.unpack_from("<H", self._buffer, 1)
|
||||
if length < _FRAME_OVERHEAD or length > _MAX_FRAME_LENGTH:
|
||||
logger.debug("Discarding byte due to invalid frame length=%d", length)
|
||||
del self._buffer[0]
|
||||
continue
|
||||
|
||||
if len(self._buffer) < length:
|
||||
break
|
||||
|
||||
frame = bytes(self._buffer[:length])
|
||||
del self._buffer[:length]
|
||||
decoded.append(decode_frame(frame))
|
||||
|
||||
return decoded
|
||||
Reference in New Issue
Block a user