-
Notifications
You must be signed in to change notification settings - Fork 0
Errors Limits and Wire Behavior
Protocyte reports failures without exceptions, allocation-backed messages, or required reflection.
struct Error {
ErrorCode code {};
usize offset {};
u32 field_number {};
};Both Status and Result<T> expose .error() after a false status check:
const auto parsed = demo::Sample<>::parse(ctx, encoded);
if (!parsed) {
const auto &error = parsed.error();
report(error.code, error.offset, error.field_number);
}-
codeidentifies the failure category. -
offsetis an absolute top-level reader or writer coordinate. It is zero when no meaningful I/O position exists. -
field_numberidentifies the public field whose operation returned the failure. It is zero when no message field is known.
Nested failures are contained by their outer public field. Map-entry failures identify the map field rather than synthetic entry fields.
No field names, nested paths, source names, or formatted strings are stored in Error.
protocyte::Limits separates protobuf wire compatibility from application policy:
| Limit | Default and behavior |
|---|---|
max_total_bytes |
0x7fffffff; bounds all bytes read or skipped by one top-level parse or merge |
max_recursion_depth |
100; matches protobuf C++ |
max_message_bytes |
0x7fffffff; bounds one embedded message |
max_string_bytes |
0x7fffffff; bounds dynamically stored string and bytes values |
max_unknown_field_bytes |
0x7fffffff; bounds canonical unknown bytes stored by one message |
max_repeated_elements |
0x7fffffff; counts decoded repeated occurrences across the top-level operation |
max_map_entries |
0x7fffffff; counts decoded entries across the top-level operation |
max_total_allocation_bytes |
Unbounded for DefaultConfig; caps live allocator-requested bytes when finite |
Packed chunks, nested messages, and duplicate map keys share their applicable top-level count budgets.
Reallocation peaks charge the new block before releasing the old one. Contexts without a deallocation callback retain charged bytes because the runtime cannot observe arena reclamation.
Limit failures return size_limit or count_limit; allocation-budget exhaustion returns no_memory.
merge_from(reader) commits successfully parsed field occurrences. It is not whole-message transactional.
The failing occurrence does not change visible state:
- a singular message occurrence is merged into staged state and committed only after it parses;
- a oneof switches cases only after the incoming value parses;
- repeated and map fields commit only complete elements or entries;
- malformed packed payloads append no decoded prefix;
- staged bounded bytes do not replace the visible value after a short read.
Fields committed before a later failure remain committed.
For whole-message replacement, use parse(reader, output). It resets and fills the caller-owned output and leaves it empty on failure.
serialize(output_bytes) computes the encoded size before writing. If the contiguous buffer is too small, it returns size_limit without modifying the buffer.
serialize(writer) validates the message first but writes incrementally. A failure after writing begins does not rewind a custom writer.
Use a staging buffer or a transactional writer when publication must be all-or-nothing.
Preservation is compile-time configurable and disabled by default:
struct ForwardCompatibleConfig : protocyte::DefaultConfig {
static constexpr bool preserve_unknown_fields = true;
};
using Sample = demo::Sample<ForwardCompatibleConfig>;Enabled messages retain unknown occurrences in encounter order as canonical protobuf wire bytes and expose:
for (const auto field : message.unknown_fields()) {
if (field.wire_type() == protocyte::WireType::VARINT) {
const auto value = field.varint();
}
}Views provide field number, wire type, tag, protobuf-style type, and typed accessors for varint, fixed, length-delimited, and group payloads.
mutable_unknown_fields() provides typed add, replace, erase, subrange deletion, number-based deletion, merge, and clear operations. Writable raw storage is not exposed.
Mutations roll back on allocation, input, recursion, or size-limit failure. Added raw encoded ranges are validated and canonicalized before commit.
Unknown fields serialize after known fields. Encounter order is retained, but canonical tag and scalar encoding means forwarding is not byte-identical.
Unknown data inside map-entry messages is normally discarded while the known key/value is materialized. An undeclared closed-enum map value preserves the complete outer map occurrence as unknown instead of inserting the entry.
The default disabled storage uses PROTOCYTE_NO_UNIQUE_ADDRESS, so it adds no message object footprint.
Readers passed to generated APIs provide:
eof()
position()
can_read(count)
read_byte()
read(out, count)
skip(count)
position() is absolute in the top-level input. SliceReader(data, size, base_offset) can establish an absolute source base for a subrange.
ReaderRef, ParseBudgetReader, LimitedReader, and staged map readers preserve the wrapped coordinate. This makes nested Error::offset values meaningful to the outer caller.
Nested parse readers expose:
consume_repeated_elements(count, field_number)
consume_map_entries(count, field_number)
ParseBudgetReader owns the counters. Reader wrappers forward them so nested messages share the top-level budget.
Generated immutable string accessors return protocyte::Span<const char> by default. This avoids standard-library APIs whose contracts can throw.
Hosted users can opt into std::string_view:
target_compile_definitions(demo_proto PUBLIC PROTOCYTE_ENABLE_STD_STRING_VIEW=1)Apply the definition to the target that compiles the generated sources and propagate it to every consumer. PROTOCYTE_ENABLE_STD_STRING_VIEW changes the public protocyte::StringView type, so all translation units in that target graph must compile with the same value.
When enabled:
- the runtime includes
<string_view>; - Protocyte spans and strings convert to
std::string_view; - generated immutable string accessors return
std::string_view.
Keep the default span API in kernel and freestanding builds.
Formatting interoperability is disabled by default and is unrelated to generator source formatting.
Enable standard C++ formatting:
target_compile_definitions(demo_proto PUBLIC PROTOCYTE_ENABLE_STD_FORMAT=1)When the standard library advertises C++20 formatting support, Protocyte supplies a std::formatter for protocyte::String<Config>.
Enable {fmt} interoperability:
target_compile_definitions(demo_proto PUBLIC PROTOCYTE_ENABLE_FMT_FORMAT=1)This supplies format_as() returning std::string_view. Protocyte does not provide or link {fmt}.
Propagate either macro consistently to formatting consumers.
Protocyte follows protobuf wire behavior for supported messages:
- scalar tags and values use canonical encoding when serialized;
- singular message occurrences merge;
- repeated packed and unpacked scalar forms are accepted where protobuf permits them;
- unknown wire fields can be skipped or preserved;
- duplicate map keys use normal map materialization behavior;
- invalid, truncated, overlong, or limit-exceeding input returns a structured failure.
Application limits may intentionally reject otherwise wire-valid protobuf messages.
For attacker-controlled payloads:
- set finite total-byte and recursion limits;
- set finite string, message, repeated, and map limits;
- set a finite allocation budget;
- decide explicitly whether unknown fields should be preserved;
- use caller-owned output when stack size matters;
- check every returned status;
- test malformed packed, nested, map, oneof, and bounded-byte cases.
Repository · Releases · Issues · Apache License 2.0
Canonical source: docs/wiki/