Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changes/unreleased/breaking-changes-20260702-200419.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
kind: Breaking changes
body: |-
The u64 size-arithmetic discipline changes three public signatures:
`buffa::types::put_len_delimited_header` takes `len: u64` (was `u32`),
and `buffa::map_codec::field_len` / `message_field_len` take and return
`u64` (`MapCodec::encoded_len` and `FIXED_LEN` likewise — that trait is
sealed, so only the signatures are visible). Bare integer literals still
infer; a `u32` variable widens with `u64::from(...)`.
**Checked-in generated code must be regenerated**: code emitted by
earlier `buffa-codegen` versions passes `__cache.consume_next()` (a
`u32`) to `put_len_delimited_header` and adds `field_len` results into a
`u32` accumulator, so it fails to compile against this runtime. Regenerate
with your build pipeline (or `buffa-build`) after updating.
`ExtensionCodec` and `extension::codecs::SingularCodec` swap their
required encode method: `try_encode` / `try_encode_one` (fallible) are
now required, and the panicking `encode` / `encode_one` are provided
wrappers — so a codec whose encode can fail cannot accidentally leave
the fallible `try_set_extension` path panicking. All in-tree codecs are
updated; an external codec impl (if any exist) renames its method and
wraps the result in `Ok`.
Runtime behavior also changes: the existing encode entry points now
**panic** on messages whose encoded size exceeds the 2 GiB protobuf
limit — see the Fixed entry for the full list and the `try_encode*`
escape hatch.
time: 2026-07-02T20:04:19.442227418Z
26 changes: 26 additions & 0 deletions .changes/unreleased/fixed-20260702-174601.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
kind: Fixed
body: |-
**Encode now enforces the protobuf 2 GiB message-size limit.** Previously
encoding was infallible: a message whose encoded size crossed 2^31-1 bytes
serialized silently into a blob that no conforming decoder — including
buffa's own (`DecodeError::MessageTooLarge`) — would read back, and sizes
past 4 GiB wrapped `u32` arithmetic into corrupt output. Generated
`compute_size` now accumulates in `u64` and saturates at each node's return
(`buffa::saturate_size`), and every provided encode entry point on
`Message`, `ViewEncode`, generated lazy views, and `DynamicMessage` checks
the total against the new `buffa::MAX_MESSAGE_BYTES` constant.
**Behavior change:** the existing entry points (`encode`, `encode_to_vec`,
`encode_to_bytes`, `encode_length_delimited`, `encoded_len`,
`encode_with_cache`) now panic on over-limit messages (previously they
returned decoder-rejected or corrupt bytes); new `try_encode`,
`try_encode_with_cache`, `try_encoded_len`, `try_encode_length_delimited`,
`try_encode_to_vec`, and `try_encode_to_bytes` twins return
`Err(EncodeError::MessageTooLarge)` instead — `EncodeError` gains its first
variant. Fallible variants also cover the eager re-encode paths:
`ExtensionSet::try_set_extension` and `Any::try_pack` (message-typed
extension values and `Any::pack` encode their payload on the spot, so the
panicking originals document the new panic and these twins return the
error instead). In debug builds `SizeCache::consume_next` additionally
rejects over-limit slots as a backstop for callers driving
`compute_size`/`write_to` directly.
time: 2026-07-02T17:46:01.12802197Z
189 changes: 100 additions & 89 deletions buffa-codegen/src/impl_message.rs

Large diffs are not rendered by default.

141 changes: 137 additions & 4 deletions buffa-codegen/src/lazy_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,37 +392,170 @@ pub(crate) fn generate_lazy_view_with_nesting(
#view_encode_methods

/// Compute size, then write. Primary encode entry point.
///
/// # Panics
///
/// Panics if the encoded size exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]) — see
/// [`try_encode`](Self::try_encode) for the error-returning
/// variant.
#[inline]
pub fn encode(&self, buf: &mut impl ::buffa::EncodeSink) {
self.try_encode(buf)
.unwrap_or_else(|_| ::buffa::encode_size_overflow())
}

/// Encode, returning an error instead of panicking if the
/// encoded size exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]).
///
/// On `Err`, nothing is written to `buf`.
///
/// # Errors
///
/// Returns [`::buffa::EncodeError::MessageTooLarge`] if the
/// encoded size exceeds the limit.
pub fn try_encode(
&self,
buf: &mut impl ::buffa::EncodeSink,
) -> ::core::result::Result<(), ::buffa::EncodeError> {
let mut __cache = ::buffa::SizeCache::new();
self.compute_size(&mut __cache);
::buffa::checked_encode_size(self.compute_size(&mut __cache))?;
self.write_to(&mut __cache, buf);
::core::result::Result::Ok(())
}

/// Encoded byte size of this view.
///
/// # Panics
///
/// Panics if the encoded size exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]) — see
/// [`try_encoded_len`](Self::try_encoded_len) for the
/// error-returning variant.
#[inline]
#[must_use]
pub fn encoded_len(&self) -> u32 {
self.compute_size(&mut ::buffa::SizeCache::new())
self.try_encoded_len()
.unwrap_or_else(|_| ::buffa::encode_size_overflow())
}

/// Encoded byte size, returning an error instead of panicking
/// if it exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]).
///
/// # Errors
///
/// Returns [`::buffa::EncodeError::MessageTooLarge`] if the
/// encoded size exceeds the limit.
pub fn try_encoded_len(
&self,
) -> ::core::result::Result<u32, ::buffa::EncodeError> {
::buffa::checked_encode_size(
self.compute_size(&mut ::buffa::SizeCache::new()),
)
}

/// Encode this view to a new `Vec<u8>`.
///
/// # Panics
///
/// Panics if the encoded size exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]) — see
/// [`try_encode_to_vec`](Self::try_encode_to_vec) for the
/// error-returning variant. In debug builds, also panics if the
/// two encode passes disagree on the byte count.
#[inline]
#[must_use]
pub fn encode_to_vec(&self) -> ::buffa::alloc::vec::Vec<u8> {
let mut __cache = ::buffa::SizeCache::new();
let __size = self.compute_size(&mut __cache) as usize;
let __size = match ::buffa::checked_encode_size(
self.compute_size(&mut __cache),
) {
::core::result::Result::Ok(__size) => __size as usize,
::core::result::Result::Err(_) => ::buffa::encode_size_overflow(),
};
let mut __buf = ::buffa::alloc::vec::Vec::with_capacity(__size);
self.write_to(&mut __cache, &mut __buf);
::buffa::debug_assert_two_pass(__buf.len(), __size);
__buf
}

/// Encode to a new `Vec<u8>`, returning an error instead of
/// panicking if the encoded size exceeds the 2 GiB protobuf
/// limit ([`::buffa::MAX_MESSAGE_BYTES`]).
///
/// # Errors
///
/// Returns [`::buffa::EncodeError::MessageTooLarge`] if the
/// encoded size exceeds the limit.
///
/// # Panics
///
/// In debug builds, panics if the two encode passes disagree
/// on the byte count.
pub fn try_encode_to_vec(
&self,
) -> ::core::result::Result<::buffa::alloc::vec::Vec<u8>, ::buffa::EncodeError>
{
let mut __cache = ::buffa::SizeCache::new();
let __size =
::buffa::checked_encode_size(self.compute_size(&mut __cache))? as usize;
let mut __buf = ::buffa::alloc::vec::Vec::with_capacity(__size);
self.write_to(&mut __cache, &mut __buf);
::buffa::debug_assert_two_pass(__buf.len(), __size);
::core::result::Result::Ok(__buf)
}

/// Encode this view to a new [`::buffa::bytes::Bytes`].
///
/// # Panics
///
/// Panics if the encoded size exceeds the 2 GiB protobuf limit
/// ([`::buffa::MAX_MESSAGE_BYTES`]) — see
/// [`try_encode_to_bytes`](Self::try_encode_to_bytes) for the
/// error-returning variant. In debug builds, also panics if the
/// two encode passes disagree on the byte count.
#[inline]
#[must_use]
pub fn encode_to_bytes(&self) -> ::buffa::bytes::Bytes {
let mut __cache = ::buffa::SizeCache::new();
let __size = self.compute_size(&mut __cache) as usize;
let __size = match ::buffa::checked_encode_size(
self.compute_size(&mut __cache),
) {
::core::result::Result::Ok(__size) => __size as usize,
::core::result::Result::Err(_) => ::buffa::encode_size_overflow(),
};
let mut __buf = ::buffa::bytes::BytesMut::with_capacity(__size);
self.write_to(&mut __cache, &mut __buf);
::buffa::debug_assert_two_pass(__buf.len(), __size);
__buf.freeze()
}

/// Encode to a new [`::buffa::bytes::Bytes`], returning an
/// error instead of panicking if the encoded size exceeds the
/// 2 GiB protobuf limit ([`::buffa::MAX_MESSAGE_BYTES`]).
///
/// # Errors
///
/// Returns [`::buffa::EncodeError::MessageTooLarge`] if the
/// encoded size exceeds the limit.
///
/// # Panics
///
/// In debug builds, panics if the two encode passes disagree
/// on the byte count.
pub fn try_encode_to_bytes(
&self,
) -> ::core::result::Result<::buffa::bytes::Bytes, ::buffa::EncodeError> {
let mut __cache = ::buffa::SizeCache::new();
let __size =
::buffa::checked_encode_size(self.compute_size(&mut __cache))? as usize;
let mut __buf = ::buffa::bytes::BytesMut::with_capacity(__size);
self.write_to(&mut __cache, &mut __buf);
::buffa::debug_assert_two_pass(__buf.len(), __size);
::core::result::Result::Ok(__buf.freeze())
}
}

#serialize_impl
Expand Down
4 changes: 3 additions & 1 deletion buffa-codegen/src/owned_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ pub(crate) fn generate_owned_view_wrapper(
///
/// # Errors
///
/// Returns [`::buffa::DecodeError`] if the re-encoded bytes are
/// Returns [`::buffa::DecodeError::MessageTooLarge`] if the
/// message's encoded size exceeds the 2 GiB protobuf limit, or
/// another [`::buffa::DecodeError`] if the re-encoded bytes are
/// somehow invalid (should not happen for well-formed messages).
pub fn from_owned(
msg: &#owned_path,
Expand Down
1 change: 1 addition & 0 deletions buffa-codegen/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ mod proto2;
mod reexports;
mod reflect_view;
mod repeated_type;
mod size_arithmetic;
mod view_codegen;

/// Wrap paths as `EnumType(Open)` feature overrides — the shape used by the
Expand Down
Loading
Loading