Skip to content

chore(deps): update buffa requirement from 0.8 to 0.9 - #182

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/cargo/buffa-0.9
Open

chore(deps): update buffa requirement from 0.8 to 0.9#182
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/cargo/buffa-0.9

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 20, 2026

Copy link
Copy Markdown
Contributor

Updates the requirements on buffa to permit the latest version.

Release notes

Sourced from buffa's releases.

v0.9.0

What's Changed

... (truncated)

Changelog

Sourced from buffa's changelog.

[0.9.0] - 2026-07-17

This release adds explicit limits on what encoding and decoding may produce and allocate, makes decoding substantially faster, and fixes a range of reflection, JSON, and codegen bugs. There are breaking changes in both the API surface and in code generation — when upgrading, regenerate all code with a version-matched buffa-codegen.

Encode and decode controls

Encoding now enforces the protobuf specification's own 2 GiB ceiling on every entry point, where it previously wrapped silently past 4 GiB and produced a corrupt message. On top of that default, try_encode_bounded takes a budget of your choosing and answers "will this message fit my frame" before writing a byte, in a single size pass rather than two. Together these are the encode twin of DecodeOptions::with_max_message_size, which has always bounded the size of a message coming off the wire.

For decoding, a further memory-amplification attack was reported — #301, a variant of the CVE-2026-55407 advisory but on expected fields rather than unknown ones. It is now mitigated through DecodeOptions::with_element_memory_limit, defaulting to 32 MiB. This will break your existing workloads if you expect and accept large numbers of absent messages within repeated fields. If a child message's struct is ~200 bytes, an absent instance of it inside a repeated field costs 2 bytes on the wire and expands to that ~200-byte allocation on decode — a 100x amplification. Millions of those 2-byte entries fit inside a perfectly reasonable 4 MiB encoded message: 4 MiB of them is 2,097,152 elements and roughly 400 MiB of allocation. with_element_memory_limit bounds the memory such fields may consume, charging each element's own footprint so that a 20-byte struct and a 200-byte struct are each handled on their merits. It complements with_unknown_field_limit rather than subsuming it — the two limits are applied separately, and map entries and view decoding are charged the same way. Adjust either default if it does not meet your needs.

Performance improvements

Multiple changes in this release improve decode performance:

  • Singular message fields are now stored inline in message structs by default (previously, they were boxed).
  • Packed fixed-width payloads decode in one bulk call.
  • Plain-varint payloads hoist the per-element buffer dispatch out of the loop.
  • Cross-crate inlining of generated view field decoding is restored (an unintentional regression).

Together the packed-decode changes cut decode latency by 36% on a 1024-element columnar batch and 16% on shorter packed arrays, primarily through fewer allocation calls and better inlining; restoring view inlining is worth up to 32% on view decoding. Results depend heavily on code layout. Our benchmarks build with lto = true, codegen-units = 1, and -Cllvm-args=-align-all-nofallthru-blocks=6 -Cllvm-args=-align-loops=64 in an attempt to get consistent results, and even then there is a reproducibility floor of roughly ±5%. We have watched a hot loop move ~20% with byte-identical machine code, purely from where it landed relative to a cache line — so keep that in mind for your own benchmarking and production builds.

Breaking changes at a glance

Each of these has fuller migration notes in the section below.

  • WirePayload is now opaque — a struct with private fields and WirePayload::borrowed(..) / ::owned(..) constructors, in place of the 0.8.0 Borrowed(&[u8]) / Owned(Bytes) enum. The reason is that a variant holding exactly the field's bytes cannot see the wire buffer around them, which is what the slack-aware UTF-8 validator needs to take its fast path; the opaque payload carries that tail, so to_str now reaches the validator on the custom-ProtoString decode path — the one string site the 0.8.0 UTF-8 work missed. Code that only calls the accessors is unaffected. Constructing a payload lowercases to ::borrowed(..) / ::owned(..), and matching on the variants moves to the accessors, with is_owned() covering the take-the-Bytes-only-when-free pattern.
  • Singular message fields are inline — codegen emits MessageField<T, ::buffa::Inline<T>>, laid out as Option<T>, so a singular submessage no longer costs a heap allocation per field. Recursive fields are detected and stay boxed automatically. Reading and writing fields through the MessageField API is unchanged, so most code that touches the structs needs no edit at all; what breaks is an explicit MessageField<Foo> type annotation, which still names the boxed form and will now mismatch the field's declared type. Drop the annotation and let the representation infer. Note the tradeoff this makes: an unset inline field costs size_of::<T>() where a boxed one cost a pointer, so for a large submessage that is usually absent, box_type_in(PointerRepr::Box, &[".pkg.Msg.field"]) restores the old behaviour per field.
  • OwnedView::to_owned_message is now infallible. It became fallible in 0.8.0 as a deliberate part of the CVE-2026-55407 fix, which put view-to-owned conversion under the decode-time limit; 0.8.1's accounting fix then made that error unreachable for any wire-decoded view, so the Result is now dead weight. Delete the ? / .unwrap() at call sites whose receiver is an OwnedView or a generated FooOwnedView. Plain view types (FooView::to_owned_message) stay fallible, because hand-written impls and push_raw-built views can still legitimately fail.
  • Encoders take &mut impl EncodeSink instead of &mut impl BufMut, so that a sink can flush segments without copying them. Callers passing Vec<u8>, BytesMut, or any other BufMut are source-compatible through the blanket impl and need no change; a hand-written Message / ViewEncode impl updates its signature, and one that reached for BufMut methods beyond the encoders' own subset (put_u8, put_slice, the little-endian fixed-width writers) must assemble into a concrete buffer first.
  • The size helpers take u64types::put_len_delimited_header, and map_codec::field_len / message_field_len. This is what makes the 2 GiB ceiling enforceable: sizes have to accumulate in a type that cannot wrap before anything can check them against a limit. Bare integer literals still infer, and a u32 variable widens with u64::from(..), so hand-written call sites are usually a small edit or none. Checked-in generated code will not compile until it is regenerated — code from earlier buffa-codegen passes a u32 into put_len_delimited_header and accumulates field_len into a u32. An external ExtensionCodec impl also swaps its required method to the fallible try_encode / try_encode_one.

MSRV remains 1.75.

Breaking changes

  • WirePayload is now an opaque struct. The 0.8.0 public-variant enum (Borrowed(&[u8]) / Owned(Bytes)) is replaced by a struct with private fields, the same accessors (as_slice, to_str, into_bytes, plus new len / is_empty / is_owned), and WirePayload::borrowed(&[u8]) / WirePayload::owned(Bytes) constructors. ProtoString / ProtoBytes from_wire implementations that use the accessors are unaffected; code that constructed WirePayload::Borrowed(..) / ::Owned(..) migrates by lowercasing to ::borrowed(..) / ::owned(..); code that matched on the variants moves to the accessors — is_owned() covers the take-the-Bytes-only-when-free pattern. The reshape lets a borrowed payload carry the surrounding wire-buffer tail, so to_str now reaches the slack-aware UTF-8 validator on the custom-ProtoString decode path (it was the one string decode site #241 didn't cover).

  • Singular message fields are now stored inline by default: codegen emits MessageField<T, ::buffa::Inline<T>> (laid out as Option<T>, no per-field heap allocation) for every non-recursive field. Recursive fields are detected and stay on Box automatically. (#248)

To restore the old behaviour for specific fields (e.g. large or rarely-set submessages), use box_type_in(PointerRepr::Box, &[".pkg.Msg.field"]); for the old global default, box_type(PointerRepr::Box). Explicit MessageField<Foo> type annotations now mean the boxed form and will mismatch the new default — drop the annotation and let P infer from the field's declared type (or, for a standalone value with no pinning context, write MessageField::<Foo, buffa::Inline<Foo>>::some(x)).

box_type_in / box_type_custom_in now normalize a missing leading dot on each path; previously a dotless path silently matched nothing.

  • OwnedView::to_owned_message and the generated FooOwnedView::to_owned_message are now infallible, returning the owned message directly instead of Result<_, DecodeError>. Every OwnedView constructor wire-decodes its view (or, for unsafe from_parts, requires wire-decode provenance as part of its strengthened safety contract), and since 0.8.1 a view produced by wire decoding always converts — so the Result only ever encoded an unreachable error path. Migration: on call sites whose receiver is an OwnedView or a generated FooOwnedView handle, delete the ? / .unwrap() / .expect(...) — or otherwise unwrap the previously-returned Result (a match or .map_err(...) needs the same treatment). Call sites on plain view types (FooView::to_owned_message via the MessageView trait) are unchanged and stay fallible, since hand-written impls and push_raw-built views can still legitimately fail. unsafe from_parts callers: the safety contract now requires that the view was produced by wire-decoding the buffer, not merely that its borrows point into it — a hand-assembled view that was legal under the old wording still compiles but now panics at to_owned_message, so audit from_parts call sites for provenance. A contract violation by a buggy hand-written MessageView impl wrapped in OwnedView likewise panics with a descriptive message instead of surfacing an error that correct code could never observe. (#268)

  • 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.

  • Message::write_to/encode (and ViewEncode, the types::put_*/encode_* helpers, and generated code) now take &mut impl EncodeSink instead of &mut impl BufMut. Callers passing Vec<u8>, BytesMut, or any other BufMut are source-compatible via the blanket impl; manual Message/ViewEncode implementations must update their method signatures, and generated code must be regenerated with the matching codegen version. Note that EncodeSink deliberately exposes only the BufMut subset the encoders use (put_u8, put_slice, and the little-endian fixed-width writers) — a manual write_to that used other BufMut methods must assemble into a concrete buffer first. Generated write_to bodies now emit put_shared_bytes_field for bytes fields — copy-equivalent for Vec<u8>, segment-aware for bytes::Bytes.

Added

... (truncated)

Commits
  • 07b3c2c release: v0.9.0 (#322)
  • f3d1abd Add try_encode_bounded: budget-checked single-pass encode entry points (#320)
  • 7b8a768 decode: bound the memory repeated elements and map entries materialize (#319)
  • 029d1a4 benchmarks: add a columnar batch with long packed columns (#318)
  • 9c62014 decode: slice-specialized loop for packed plain-varint payloads (#315)
  • d1db9a7 decode: bulk path for packed fixed-width payloads (#314)
  • 5a5bc5e reflect: preserve closed enum unknowns (#304)
  • 43b140b view: mark the unknown-field pushers cold (#317)
  • 08edc52 descriptor: validate service and method symbols (#302)
  • 940dfac descriptor: reject ambiguous field identities (#300)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Updates the requirements on [buffa](https://github.com/anthropics/buffa) to permit the latest version.
- [Release notes](https://github.com/anthropics/buffa/releases)
- [Changelog](https://github.com/anthropics/buffa/blob/main/CHANGELOG.md)
- [Commits](anthropics/buffa@v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: buffa
  dependency-version: 0.9.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file rust Pull requests that update Rust code labels Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants