init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
@@ -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