Skip to content

Handle short buffers

Every rbe::deserialize and rbe::serialize call assumes the buffer is big enough. The try_ forms check and return std::nullopt instead of undefined behavior.

Narrow and wider contracts

rbe::deserialize has a narrow contract: some inputs it accepts, a short buffer, have undefined behavior. The try_ forms have a wider one, not a wide one: they check what can be checked, the size and the alignment, and still assume the span points to live memory. See the deserialization reference.

Source: example/howto/wide_contracts.cpp, CMake target howto_wide_contracts.

Reading

// deserialize() assumes the buffer holds a whole Order; try_deserialize() checks it first
std::optional<Order> order = rbe::try_deserialize<Order>(bytes, rbe::dsrl::eager);

auto truncated = bytes.first(bytes.size() - 1);
auto missing   = rbe::try_deserialize<Order>(truncated, rbe::dsrl::eager);  // std::nullopt, one byte short

// the same for a lazy view, which is only safe to read if the whole value is there
std::optional<rbe::dsrl::proxy<Order>> view = rbe::try_deserialize<Order>(bytes, rbe::dsrl::lazy);

For the in-place strategies the check covers alignment too (in_place_mut takes a mutable span and returns std::optional<T&>):

// in_place also needs the buffer aligned for T: try_deserialize checks that too
auto aligned    = rbe::try_deserialize<Tick>(bytes, rbe::dsrl::in_place);              // a Tick const&
auto misaligned = rbe::try_deserialize<Tick>(bytes.subspan(1), rbe::dsrl::in_place);   // std::nullopt

Frames

A frame checks its header, then that the payload the header announces fits in what is left.

// A frame checks its header, then that the payload the header announces fits
std::optional<message::dsrl_type> msg = rbe::try_deserialize<message>(bytes);

auto cut = rbe::try_deserialize<message>(bytes.first(bytes.size() - 2));  // std::nullopt: the Order is cut

A run of frames (rbe::frames::many) never fails this way: it stops at the first incomplete element and hands back the rest. See Walk a run of messages.

Writing

std::optional<std::size_t> written = rbe::try_serialize(out, Order {.order_id = 1, .price = 2500});

Note

The try_ forms check sizes and alignment. They do not, and cannot, check that the memory is live or that the bytes mean what the type says: a lazy view over a buffer that is freed later is still a bug.

See also: Deserialization reference, Serialization reference.