Files
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

104 lines
3.8 KiB
Python

"""Bounded, all-or-nothing decode of an already admitted local log segment."""
import bz2
import io
from collections.abc import Callable
from typing import Any
import zstandard as zstd
from openpilot.cereal import log
MAX_COMPRESSED_BYTES = 32 * 1024 * 1024
MAX_EXPANDED_BYTES = 64 * 1024 * 1024
MAX_MESSAGES = 250_000
EVENT_TRAVERSAL_WORDS = 1_000_000
CHUNK_BYTES = 64 * 1024
class LogDecodeError(ValueError):
"""The whole segment is unavailable; callers must discard every event."""
def _not_cancelled(cancelled: Callable[[], bool]) -> None:
try:
if cancelled():
raise LogDecodeError("segment decode cancelled")
except LogDecodeError:
raise
except Exception as error:
raise LogDecodeError("segment cancellation check unavailable") from error
def _bounded_read(reader: Any, cancelled: Callable[[], bool]) -> bytes:
chunks: list[bytes] = []
total = 0
while True:
_not_cancelled(cancelled)
chunk = reader.read(min(CHUNK_BYTES, MAX_EXPANDED_BYTES + 1 - total))
if not chunk:
break
total += len(chunk)
if total > MAX_EXPANDED_BYTES:
raise LogDecodeError("expanded segment exceeds 64 MiB")
chunks.append(chunk)
return b"".join(chunks)
def _expand(data: bytes, codec: str, cancelled: Callable[[], bool]) -> bytes:
try:
if codec == "zst":
with zstd.ZstdDecompressor(max_window_size=MAX_EXPANDED_BYTES).stream_reader(io.BytesIO(data)) as reader:
expanded = _bounded_read(reader, cancelled)
# stream_reader in pinned zstandard 0.25 returns short data for a
# truncated frame. Validate the complete frame after proving its output
# fits our bound, and reject a concatenated/trailing second frame.
decoder = zstd.ZstdDecompressor(max_window_size=MAX_EXPANDED_BYTES).decompressobj()
verified = decoder.decompress(data)
if not decoder.eof or decoder.unused_data or verified != expanded:
raise LogDecodeError("incomplete or trailing zstd frame")
elif codec == "bz2":
with bz2.BZ2File(io.BytesIO(data), "rb") as reader:
expanded = _bounded_read(reader, cancelled)
decoder = bz2.BZ2Decompressor()
verified = decoder.decompress(data)
if not decoder.eof or decoder.unused_data or verified != expanded:
raise LogDecodeError("incomplete or trailing bzip2 stream")
else:
raise LogDecodeError("unsupported segment compression")
_not_cancelled(cancelled)
return expanded
except LogDecodeError:
raise
except (OSError, EOFError, ValueError, zstd.ZstdError) as error:
raise LogDecodeError("corrupt or incomplete compressed segment") from error
def decode_segment(data: bytes, codec: str, *, cancelled: Callable[[], bool] = lambda: False,
retain: frozenset[str] | None = None) -> tuple[Any, ...]:
"""Return all complete Event readers, or reject the entire segment."""
if type(data) is not bytes or len(data) > MAX_COMPRESSED_BYTES or type(codec) is not str or \
codec not in ("zst", "bz2"):
raise LogDecodeError("unsupported or oversized compressed segment")
_not_cancelled(cancelled)
expanded = _expand(data, codec, cancelled)
events: list[Any] = []
try:
for count, event in enumerate(log.Event.read_multiple_bytes(expanded, traversal_limit_in_words=EVENT_TRAVERSAL_WORDS)):
_not_cancelled(cancelled)
if count >= MAX_MESSAGES:
raise LogDecodeError("segment message count exceeded")
# Force envelope and union decoding before a reader can escape; a later
# malformed record invalidates the entire segment, never a prefix.
int(event.logMonoTime)
kind = event.which()
if retain is None or kind in retain:
events.append(event)
except LogDecodeError:
raise
except Exception as error:
raise LogDecodeError("corrupt Cereal Event segment") from error
_not_cancelled(cancelled)
return tuple(events)