"""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(" 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(" 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(" _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