#[cfg(feature = "document-read")]
use super::DocumentError;
#[cfg(feature = "document-read")]
const MAX_VARINT_BYTES: usize = 10;
#[cfg(feature = "document-write")]
#[derive(Debug, Default)]
pub(crate) struct Writer {
buf: Vec<u8>,
tables: super::intern::WriteTables,
}
#[cfg(feature = "document-write")]
impl Writer {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn tables(&mut self) -> &mut super::intern::WriteTables {
&mut self.tables
}
pub(crate) fn detached<F: FnOnce(&mut Writer)>(&mut self, f: F) -> Vec<u8> {
let mut sub = Writer {
buf: Vec::new(),
tables: std::mem::take(&mut self.tables),
};
f(&mut sub);
self.tables = sub.tables;
sub.buf
}
pub(crate) fn raw(&mut self, bytes: &[u8]) {
self.buf.extend_from_slice(bytes);
}
pub(crate) fn len(&self) -> usize {
self.buf.len()
}
pub(crate) fn finish(self) -> Vec<u8> {
self.buf
}
pub(crate) fn u8(&mut self, v: u8) {
self.buf.push(v);
}
pub(crate) fn varint(&mut self, mut v: u64) {
loop {
let byte = (v & 0x7f) as u8;
v >>= 7;
if v == 0 {
self.buf.push(byte);
return;
}
self.buf.push(byte | 0x80);
}
}
pub(crate) fn varint_signed(&mut self, v: i64) {
self.varint(((v << 1) ^ (v >> 63)) as u64);
}
pub(crate) fn f32(&mut self, v: f32) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
pub(crate) fn f64(&mut self, v: f64) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
pub(crate) fn bytes(&mut self, v: &[u8]) {
self.varint(v.len() as u64);
self.buf.extend_from_slice(v);
}
pub(crate) fn str(&mut self, v: &str) {
self.bytes(v.as_bytes());
}
pub(crate) fn patch_u32_at(&mut self, at: usize, v: u32) {
self.buf[at..at + 4].copy_from_slice(&v.to_le_bytes());
}
pub(crate) fn u32_fixed(&mut self, v: u32) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
pub(crate) fn u16_fixed(&mut self, v: u16) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
}
#[cfg(feature = "document-read")]
#[derive(Debug)]
pub(crate) struct Reader<'a> {
buf: &'a [u8],
pos: usize,
ctx: &'a super::ReadContext,
tables: super::intern::ReadTables,
}
#[cfg(feature = "document-read")]
impl<'a> Reader<'a> {
pub(crate) fn new(buf: &'a [u8]) -> Self {
Self::with_context(buf, super::read::default_context())
}
pub(crate) fn with_context(buf: &'a [u8], ctx: &'a super::ReadContext) -> Self {
Self {
buf,
pos: 0,
ctx,
tables: super::intern::ReadTables::default(),
}
}
pub(crate) fn ctx(&self) -> &'a super::ReadContext {
self.ctx
}
pub(crate) fn tables(&self) -> &super::intern::ReadTables {
&self.tables
}
pub(crate) fn with_tables(
buf: &'a [u8],
ctx: &'a super::ReadContext,
tables: super::intern::ReadTables,
) -> Self {
Self {
buf,
pos: 0,
ctx,
tables,
}
}
pub(crate) fn pos(&self) -> usize {
self.pos
}
pub(crate) fn remaining(&self) -> usize {
self.buf.len() - self.pos
}
pub(crate) fn is_empty(&self) -> bool {
self.remaining() == 0
}
pub(crate) fn take(&mut self, n: usize) -> Result<&'a [u8], DocumentError> {
if self.remaining() < n {
return Err(DocumentError::UnexpectedEof {
offset: self.pos,
wanted: n,
available: self.remaining(),
});
}
let out = &self.buf[self.pos..self.pos + n];
self.pos += n;
Ok(out)
}
pub(crate) fn u8(&mut self) -> Result<u8, DocumentError> {
Ok(self.take(1)?[0])
}
pub(crate) fn varint(&mut self) -> Result<u64, DocumentError> {
let start = self.pos;
let mut out: u64 = 0;
for i in 0..MAX_VARINT_BYTES {
let byte = self.u8()?;
out |= u64::from(byte & 0x7f) << (7 * i);
if byte & 0x80 == 0 {
return Ok(out);
}
}
Err(DocumentError::BadVarint { offset: start })
}
pub(crate) fn varint_signed(&mut self) -> Result<i64, DocumentError> {
let raw = self.varint()?;
Ok(((raw >> 1) as i64) ^ -((raw & 1) as i64))
}
pub(crate) fn count(&mut self) -> Result<usize, DocumentError> {
let raw = self.varint()?;
let n = usize::try_from(raw).map_err(|_| DocumentError::UnexpectedEof {
offset: self.pos,
wanted: usize::MAX,
available: self.remaining(),
})?;
if n > self.remaining() {
return Err(DocumentError::UnexpectedEof {
offset: self.pos,
wanted: n,
available: self.remaining(),
});
}
Ok(n)
}
pub(crate) fn f32(&mut self) -> Result<f32, DocumentError> {
let b = self.take(4)?;
Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
pub(crate) fn f64(&mut self) -> Result<f64, DocumentError> {
let b = self.take(8)?;
Ok(f64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
pub(crate) fn u32_fixed(&mut self) -> Result<u32, DocumentError> {
let b = self.take(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
pub(crate) fn u16_fixed(&mut self) -> Result<u16, DocumentError> {
let b = self.take(2)?;
Ok(u16::from_le_bytes([b[0], b[1]]))
}
pub(crate) fn bytes(&mut self) -> Result<&'a [u8], DocumentError> {
let n = self.count()?;
self.take(n)
}
pub(crate) fn str(&mut self) -> Result<&'a str, DocumentError> {
let offset = self.pos;
let raw = self.bytes()?;
std::str::from_utf8(raw).map_err(|_| DocumentError::BadUtf8 { offset })
}
}
#[cfg(feature = "document-write")]
pub(crate) trait Encode {
fn encode(&self, w: &mut Writer);
}
#[cfg(feature = "document-read")]
pub(crate) trait Decode: Sized {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError>;
}
#[cfg(feature = "document-read")]
macro_rules! replace_expr {
($_tt:tt, $sub:expr) => {
$sub
};
}
macro_rules! impl_codec_scalar {
($ty:ty, $write:ident, $read:ident $(, $cast_out:ty)?) => {
#[cfg(feature = "document-write")]
impl Encode for $ty {
fn encode(&self, w: &mut Writer) {
w.$write((*self) $(as $cast_out)?);
}
}
#[cfg(feature = "document-read")]
impl Decode for $ty {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
#[allow(clippy::useless_conversion)]
r.$read()?.try_into().map_err(|_| DocumentError::Invalid {
what: stringify!($ty),
why: "value out of range".to_string(),
})
}
}
};
}
impl_codec_scalar!(u8, varint, varint, u64);
impl_codec_scalar!(u16, varint, varint, u64);
impl_codec_scalar!(u32, varint, varint, u64);
impl_codec_scalar!(u64, varint, varint);
impl_codec_scalar!(usize, varint, varint, u64);
impl_codec_scalar!(i32, varint_signed, varint_signed, i64);
impl_codec_scalar!(i64, varint_signed, varint_signed);
#[cfg(feature = "document-write")]
impl Encode for f32 {
fn encode(&self, w: &mut Writer) {
w.f32(*self);
}
}
#[cfg(feature = "document-read")]
impl Decode for f32 {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
r.f32()
}
}
#[cfg(feature = "document-write")]
impl Encode for f64 {
fn encode(&self, w: &mut Writer) {
w.f64(*self);
}
}
#[cfg(feature = "document-read")]
impl Decode for f64 {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
r.f64()
}
}
#[cfg(feature = "document-write")]
impl Encode for bool {
fn encode(&self, w: &mut Writer) {
w.u8(u8::from(*self));
}
}
#[cfg(feature = "document-read")]
impl Decode for bool {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok(r.u8()? != 0)
}
}
#[cfg(feature = "document-write")]
impl Encode for char {
fn encode(&self, w: &mut Writer) {
w.varint(u32::from(*self) as u64);
}
}
#[cfg(feature = "document-read")]
impl Decode for char {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let raw = r.varint()?;
u32::try_from(raw)
.ok()
.and_then(char::from_u32)
.ok_or(DocumentError::Invalid {
what: "char",
why: format!("{raw} is not a Unicode scalar value"),
})
}
}
#[cfg(feature = "document-write")]
impl Encode for str {
fn encode(&self, w: &mut Writer) {
w.str(self);
}
}
#[cfg(feature = "document-write")]
impl Encode for String {
fn encode(&self, w: &mut Writer) {
w.str(self);
}
}
#[cfg(feature = "document-read")]
impl Decode for String {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok(r.str()?.to_string())
}
}
#[cfg(feature = "document-write")]
impl<T: Encode> Encode for Option<T> {
fn encode(&self, w: &mut Writer) {
match self {
None => w.u8(0),
Some(v) => {
w.u8(1);
v.encode(w);
}
}
}
}
#[cfg(feature = "document-read")]
impl<T: Decode> Decode for Option<T> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
match r.u8()? {
0 => Ok(None),
_ => Ok(Some(T::decode(r)?)),
}
}
}
#[cfg(feature = "document-write")]
impl<T: Encode> Encode for Vec<T> {
fn encode(&self, w: &mut Writer) {
w.varint(self.len() as u64);
for v in self {
v.encode(w);
}
}
}
#[cfg(feature = "document-read")]
impl<T: Decode> Decode for Vec<T> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let n = r.count()?;
let mut out = Vec::with_capacity(n);
for _ in 0..n {
out.push(T::decode(r)?);
}
Ok(out)
}
}
#[cfg(feature = "document-write")]
impl<T: Encode> Encode for [T] {
fn encode(&self, w: &mut Writer) {
w.varint(self.len() as u64);
for v in self {
v.encode(w);
}
}
}
#[cfg(feature = "document-write")]
impl<T: Encode> Encode for std::sync::Arc<[T]> {
fn encode(&self, w: &mut Writer) {
w.varint(self.len() as u64);
for v in self.iter() {
v.encode(w);
}
}
}
#[cfg(feature = "document-read")]
impl<T: Decode> Decode for std::sync::Arc<[T]> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok(std::sync::Arc::from(Vec::<T>::decode(r)?))
}
}
#[cfg(feature = "document-write")]
impl<T: Encode> Encode for Box<T> {
fn encode(&self, w: &mut Writer) {
(**self).encode(w);
}
}
#[cfg(feature = "document-read")]
impl<T: Decode> Decode for Box<T> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok(Box::new(T::decode(r)?))
}
}
#[cfg(feature = "document-write")]
impl<T: Encode, const N: usize> Encode for [T; N] {
fn encode(&self, w: &mut Writer) {
for v in self {
v.encode(w);
}
}
}
#[cfg(feature = "document-read")]
impl<T: Decode, const N: usize> Decode for [T; N] {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let mut out = Vec::with_capacity(N);
for _ in 0..N {
out.push(T::decode(r)?);
}
out.try_into().map_err(|_| DocumentError::Invalid {
what: "fixed-length array",
why: format!("expected {N} elements"),
})
}
}
#[cfg(feature = "document-write")]
impl<A: Encode, B: Encode> Encode for (A, B) {
fn encode(&self, w: &mut Writer) {
self.0.encode(w);
self.1.encode(w);
}
}
#[cfg(feature = "document-read")]
impl<A: Decode, B: Decode> Decode for (A, B) {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok((A::decode(r)?, B::decode(r)?))
}
}
#[cfg(feature = "document-write")]
impl<V: Encode> Encode for std::collections::HashMap<String, V> {
fn encode(&self, w: &mut Writer) {
let mut keys: Vec<&String> = self.keys().collect();
keys.sort_unstable();
w.varint(keys.len() as u64);
for k in keys {
k.encode(w);
self[k].encode(w);
}
}
}
#[cfg(feature = "document-read")]
impl<V: Decode> Decode for std::collections::HashMap<String, V> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let n = r.count()?;
let mut out = std::collections::HashMap::with_capacity(n);
for _ in 0..n {
let k = String::decode(r)?;
out.insert(k, V::decode(r)?);
}
Ok(out)
}
}
macro_rules! impl_codec {
() => {};
(
struct $ty:ident < $($gen:ident),+ $(,)? > { $($field:ident),+ $(,)? }
$($rest:tt)*
) => {
#[cfg(feature = "document-write")]
impl<$($gen: Encode),+> Encode for $ty<$($gen),+> {
fn encode(&self, w: &mut Writer) {
$( self.$field.encode(w); )+
}
}
#[cfg(feature = "document-read")]
impl<$($gen: Decode),+> Decode for $ty<$($gen),+> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok($ty { $( $field: Decode::decode(r)?, )+ })
}
}
impl_codec! { $($rest)* }
};
(
enum $ty:ident < $($gen:ident),+ $(,)? > {
$(
$tag:literal => $variant:ident
$( ( $($bind:ident),+ $(,)? ) )?
$( { $($vfield:ident),+ $(,)? } )?
),+ $(,)?
}
$($rest:tt)*
) => {
#[cfg(feature = "document-write")]
impl<$($gen: Encode),+> Encode for $ty<$($gen),+> {
fn encode(&self, w: &mut Writer) {
match self {
$(
$ty::$variant
$( ( $($bind),+ ) )?
$( { $($vfield),+ } )?
=> {
w.varint($tag);
$( $( $bind.encode(w); )+ )?
$( $( $vfield.encode(w); )+ )?
}
)+
}
}
}
#[cfg(feature = "document-read")]
impl<$($gen: Decode),+> Decode for $ty<$($gen),+> {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let offset = r.pos();
let tag = r.varint()?;
Ok(match tag {
$(
$tag => $ty::$variant
$( ( $( $crate::document::codec::replace_expr!(
$bind,
Decode::decode(r)?
) ),+ ) )?
$( { $( $vfield: Decode::decode(r)? ),+ } )?,
)+
other => {
return Err(DocumentError::BadDiscriminant {
type_name: stringify!($ty),
tag: other,
offset,
})
}
})
}
}
impl_codec! { $($rest)* }
};
(
struct $ty:ident { $($field:ident),+ $(,)? }
$($rest:tt)*
) => {
#[cfg(feature = "document-write")]
impl Encode for $ty {
fn encode(&self, w: &mut Writer) {
$( self.$field.encode(w); )+
}
}
#[cfg(feature = "document-read")]
impl Decode for $ty {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok($ty { $( $field: Decode::decode(r)?, )+ })
}
}
impl_codec! { $($rest)* }
};
(
newtype $ty:ident;
$($rest:tt)*
) => {
#[cfg(feature = "document-write")]
impl Encode for $ty {
fn encode(&self, w: &mut Writer) {
self.0.encode(w);
}
}
#[cfg(feature = "document-read")]
impl Decode for $ty {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok($ty(Decode::decode(r)?))
}
}
impl_codec! { $($rest)* }
};
(
enum $ty:ident {
$(
$tag:literal => $variant:ident
$( ( $($bind:ident),+ $(,)? ) )?
$( { $($vfield:ident),+ $(,)? } )?
),+ $(,)?
}
$($rest:tt)*
) => {
#[cfg(feature = "document-write")]
impl Encode for $ty {
fn encode(&self, w: &mut Writer) {
match self {
$(
$ty::$variant
$( ( $($bind),+ ) )?
$( { $($vfield),+ } )?
=> {
w.varint($tag);
$( $( $bind.encode(w); )+ )?
$( $( $vfield.encode(w); )+ )?
}
)+
}
}
}
#[cfg(feature = "document-read")]
impl Decode for $ty {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let offset = r.pos();
let tag = r.varint()?;
Ok(match tag {
$(
$tag => $ty::$variant
$( ( $( $crate::document::codec::replace_expr!(
$bind,
Decode::decode(r)?
) ),+ ) )?
$( { $( $vfield: Decode::decode(r)? ),+ } )?,
)+
other => {
return Err(DocumentError::BadDiscriminant {
type_name: stringify!($ty),
tag: other,
offset,
})
}
})
}
}
impl_codec! { $($rest)* }
};
}
pub(crate) use impl_codec;
#[cfg(feature = "document-read")]
pub(crate) use replace_expr;
#[cfg(all(test, feature = "document-read", feature = "document-write"))]
pub(crate) mod test_support {
use super::*;
pub(crate) fn roundtrip<T: Encode + Decode>(v: &T) -> T {
let mut w = Writer::new();
v.encode(&mut w);
let geometries = w.tables().geometries().to_vec();
let sheets = w.tables().sheets().to_vec();
let strings = w.tables().strings().to_vec();
let bytes = w.finish();
let mut r = Reader::with_tables(
&bytes,
super::super::read::default_context(),
tables(geometries, sheets, strings),
);
let out = T::decode(&mut r).expect("decoding what we just encoded");
assert!(
r.is_empty(),
"decode left {} of {} bytes unread",
r.remaining(),
bytes.len()
);
out
}
fn tables(
geometries: Vec<std::sync::Arc<crate::scales::geometry::Geometry>>,
sheets: Vec<std::sync::Arc<crate::text::rich::RichTextStyleSheet>>,
strings: Vec<std::sync::Arc<str>>,
) -> super::super::intern::ReadTables {
let mut t = super::super::intern::ReadTables::default();
t.set_geometries(geometries);
t.set_sheets(sheets);
t.set_strings(strings);
t
}
pub(crate) fn roundtrip_with_context<T: Encode + Decode>(
v: &T,
ctx: &super::super::ReadContext,
) -> T {
let mut w = Writer::new();
v.encode(&mut w);
let geometries = w.tables().geometries().to_vec();
let sheets = w.tables().sheets().to_vec();
let strings = w.tables().strings().to_vec();
let bytes = w.finish();
let mut r = Reader::with_tables(&bytes, ctx, tables(geometries, sheets, strings));
let out = T::decode(&mut r).expect("decoding what we just encoded");
assert!(r.is_empty(), "decode left {} bytes unread", r.remaining());
out
}
pub(crate) fn assert_roundtrip<T: Encode + Decode + PartialEq + std::fmt::Debug>(v: T) {
let out = roundtrip(&v);
assert_eq!(out, v);
}
}
#[cfg(all(test, feature = "document-read", feature = "document-write"))]
mod tests {
use super::test_support::assert_roundtrip;
use super::*;
#[test]
fn varints_round_trip_across_their_whole_range() {
for v in [
0u64,
1,
127,
128,
300,
u64::from(u32::MAX),
u64::MAX - 1,
u64::MAX,
] {
let mut w = Writer::new();
w.varint(v);
let bytes = w.finish();
let mut r = Reader::new(&bytes);
assert_eq!(r.varint().expect("well-formed varint"), v);
assert!(r.is_empty());
}
}
#[test]
fn small_varints_cost_one_byte() {
let mut w = Writer::new();
w.varint(127);
assert_eq!(w.len(), 1);
}
#[test]
fn signed_varints_round_trip_either_side_of_zero() {
for v in [0i64, -1, 1, -64, 63, i64::MIN, i64::MAX] {
let mut w = Writer::new();
w.varint_signed(v);
let bytes = w.finish();
let mut r = Reader::new(&bytes);
assert_eq!(r.varint_signed().expect("well-formed varint"), v);
}
}
#[test]
fn small_negative_varints_stay_narrow() {
let mut w = Writer::new();
w.varint_signed(-1);
assert_eq!(w.len(), 1, "zigzag should keep -1 to a single byte");
}
#[test]
fn a_varint_that_never_terminates_is_rejected() {
let bytes = vec![0x80u8; MAX_VARINT_BYTES + 2];
let mut r = Reader::new(&bytes);
assert!(matches!(
r.varint(),
Err(DocumentError::BadVarint { offset: 0 })
));
}
#[test]
fn reading_past_the_end_reports_the_offset() {
let bytes = [1u8, 2];
let mut r = Reader::new(&bytes);
assert!(matches!(
r.take(5),
Err(DocumentError::UnexpectedEof {
offset: 0,
wanted: 5,
available: 2
})
));
}
#[test]
fn a_count_larger_than_the_input_is_rejected_before_allocating() {
let mut w = Writer::new();
w.varint(1_000_000);
let bytes = w.finish();
let mut r = Reader::new(&bytes);
assert!(matches!(
r.count(),
Err(DocumentError::UnexpectedEof { .. })
));
}
#[test]
fn invalid_utf8_in_a_string_reports_where_it_started() {
let mut w = Writer::new();
w.bytes(&[0xff, 0xfe]);
let bytes = w.finish();
let mut r = Reader::new(&bytes);
assert!(matches!(r.str(), Err(DocumentError::BadUtf8 { offset: 0 })));
}
#[test]
fn scalars_and_containers_round_trip() {
assert_roundtrip(0u8);
assert_roundtrip(u16::MAX);
assert_roundtrip(u32::MAX);
assert_roundtrip(i32::MIN);
assert_roundtrip(1.5f32);
assert_roundtrip(-0.25f64);
assert_roundtrip(true);
assert_roundtrip(false);
assert_roundtrip('é');
assert_roundtrip("hello".to_string());
assert_roundtrip(std::sync::Arc::<str>::from("shared"));
assert_roundtrip(Some(3u32));
assert_roundtrip(Option::<u32>::None);
assert_roundtrip(vec![1u32, 2, 3]);
assert_roundtrip(Vec::<u32>::new());
assert_roundtrip(Box::new(7u32));
assert_roundtrip([1u32, 2, 3, 4]);
assert_roundtrip((1u32, "two".to_string()));
}
#[test]
fn non_finite_floats_survive() {
let out = super::test_support::roundtrip(&f64::NAN);
assert!(out.is_nan());
assert_roundtrip(f64::INFINITY);
assert_roundtrip(f64::NEG_INFINITY);
assert_roundtrip(-0.0f64);
}
#[test]
fn maps_round_trip_and_write_the_same_bytes_whatever_the_insertion_order() {
use std::collections::HashMap;
let mut a: HashMap<String, u32> = HashMap::new();
a.insert("one".into(), 1);
a.insert("two".into(), 2);
a.insert("three".into(), 3);
let mut b: HashMap<String, u32> = HashMap::new();
b.insert("three".into(), 3);
b.insert("one".into(), 1);
b.insert("two".into(), 2);
let bytes_of = |m: &HashMap<String, u32>| {
let mut w = Writer::new();
m.encode(&mut w);
w.finish()
};
assert_eq!(bytes_of(&a), bytes_of(&b));
assert_eq!(super::test_support::roundtrip(&a), a);
}
#[test]
fn a_chunk_length_can_be_backfilled_once_its_extent_is_known() {
let mut w = Writer::new();
let at = w.len();
w.u32_fixed(0);
w.varint(1);
w.varint(2);
let body = (w.len() - at - 4) as u32;
w.patch_u32_at(at, body);
let bytes = w.finish();
let mut r = Reader::new(&bytes);
assert_eq!(r.u32_fixed().expect("length prefix"), 2);
}
}