Source code for sofab.types
"""Wire-format constants, enums, the :class:`Field` descriptor, and errors.
These mirror the shared SofaBuffers definitions used by ``corelib-c-cpp``,
``corelib-rs``, ``corelib-go``, ``corelib-java`` and ``corelib-cs`` so the
Python runtime produces byte-identical output.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import IntEnum
# --- limits (from corelib-c-cpp/src/include/sofab/sofab.h) -------------------
#: SofaBuffers API version (mirrors C ``SOFAB_API_VERSION``). Callers and the
#: code generator use this to verify wire compatibility.
API_VERSION = 1
#: Highest valid field ID (``INT32_MAX``).
ID_MAX = 0x7FFF_FFFF
#: Largest unsigned wire value (``UINT64_MAX``).
UNSIGNED_MAX = (1 << 64) - 1
#: Signed wire value range (``INT64_MIN`` .. ``INT64_MAX``).
SIGNED_MIN = -(1 << 63)
SIGNED_MAX = (1 << 63) - 1
#: Largest fixlen payload length in bytes (``INT32_MAX``).
FIXLEN_MAX = 0x7FFF_FFFF
#: Largest array element count (``INT32_MAX``).
ARRAY_MAX = 0x7FFF_FFFF
#: Maximum nested-sequence depth. An encoder must not open more than this many
#: nested sequences; a decoder rejects a message nesting deeper.
MAX_DEPTH = 255
#: 64-bit mask used by varint/zigzag wrap-around to match the C ``uint64_t``.
MASK64 = (1 << 64) - 1
[docs]
class WireType(IntEnum):
"""The 3 low bits of a field header."""
UNSIGNED = 0x0
SIGNED = 0x1
FIXLEN = 0x2
ARRAY_UNSIGNED = 0x3
ARRAY_SIGNED = 0x4
ARRAY_FIXLEN = 0x5
SEQUENCE_START = 0x6
SEQUENCE_END = 0x7
[docs]
class FixlenSubtype(IntEnum):
"""The 3 low bits of a fixlen length header."""
FP32 = 0x0
FP64 = 0x1
STRING = 0x2
BLOB = 0x3
[docs]
@dataclass
class Field:
"""Describes the field the decoder is currently positioned on.
Mirrors the C field callback's ``(id, size, count)`` plus the wire type.
``size`` is the fixlen byte length (or the per-element size of a fixlen
array); ``count`` is the element count of an array; ``subtype`` is set for
fixlen and fixlen-array fields.
"""
id: int
type: WireType
size: int = 0
count: int = 0
subtype: FixlenSubtype | None = None
# --- errors -----------------------------------------------------------------
[docs]
class SofaError(Exception):
"""Base class for all SofaBuffers errors."""
[docs]
class SofaDecodeError(SofaError):
"""Malformed input — invalid *regardless* of what bytes might follow: an
overflowing (>64-bit) varint, a bad fixlen subtype, an out-of-range
id/count/length, invalid UTF-8, nesting past ``MAX_DEPTH``, or a dangling
sequence end (``MESSAGE_SPEC`` §7 INVALID).
This is deliberately **not** raised for truncation — bytes that simply end
inside a field are :class:`SofaIncompleteError` (§7 INCOMPLETE), a distinct
non-error outcome that is not a subclass of this class, so
``except SofaDecodeError`` does not catch it.
"""
[docs]
class SofaIncompleteError(SofaError):
"""Truncated input — the bytes end *inside* a field (``MESSAGE_SPEC`` §7
INCOMPLETE): an unterminated varint, a fixlen/array payload shorter than its
declared length, an array element that runs off the end, or a nested
sequence that is never closed.
This is **not** malformed: more bytes could complete the message, and the
caller owns end-of-input. It is a sibling of :class:`SofaDecodeError` under
:class:`SofaError`, *not* a subclass of it, so callers can tell "need more
bytes" apart from "these bytes are garbage".
"""
[docs]
class SofaLimitError(SofaError):
"""A wire-declared array count or fixlen (string/blob) length exceeded a
**receiver-configured** decode limit (``Decoder(max_array_count=…,
max_string_len=…, max_blob_len=…)``).
This is a *policy* rejection, not wire malformation: the bytes are perfectly
well-formed and would decode fine with the limit unset — the receiver simply
declined to allocate for them. It is therefore a sibling of
:class:`SofaDecodeError` under :class:`SofaError`, **not** a subclass of it,
so ``except SofaDecodeError`` does not catch it and differential fuzzing does
not see a limit rejection as a conformance divergence from another engine.
"""
[docs]
class SofaRangeError(SofaError):
"""A value (or id/count) is outside the permitted range on encode."""
[docs]
class SofaStateError(SofaError):
"""API misuse, e.g. reading a value of the wrong type for the current
field, or ending a sequence that was never started."""
[docs]
class SofaBufferError(SofaError):
"""A fixed encoder buffer filled up and no flush sink was provided."""