diff --git a/CHANGELOG.md b/CHANGELOG.md index 9271a733..bc50a90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +### Added + +- Changed the requirement that the context (`Ctx`) must be `Copy` in some calls. + It is changed to `Clone`, this **allows to add interior mutability** in the context + (like `Rc>`). This is no breaking change, since `Copy` implies `Clone`. +- Added an attribute `until_with_ctx`: **like `until`, but it also gets the context**. +- Added `writer_ctx` for fields: overrides `ctx` for the writer case - this allows + a **different context value for reader and writer** (the reader gets the original + `ctx`). +- Added an attribute `read_post_processing`: code which is inserted after a field is + read (including a temp field). You have full context access (**and you can modify + the context there**). ## [0.20.3](https://github.com/sharksforarms/deku/compare/deku-v0.20.2...deku-v0.20.3) - 2026-01-15 ### Fixed diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index c858272c..8b42b8f4 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -583,18 +583,24 @@ struct FieldData { /// tokens providing the number of bytes for the length of the container bytes_read: Option, - /// a predicate to decide when to stop reading elements into the container + /// a predicate to decide when to stop reading elements into the container (receives the current container element) until: Option, + /// a predicate to decide when to stop reading elements into the container (receives the current container element and a clone of the context) + until_with_ctx: Option, + /// read until `reader.end()` read_all: bool, /// apply a function to the field after it's read map: Option, - /// context passed to the field + /// context passed to the field (reader/writer; see writer_ctx) ctx: Option>, + /// writer_context passed to the field (overrides ctx) + writer_ctx: Option>, + /// map field when updating struct update: Option, @@ -627,6 +633,9 @@ struct FieldData { /// write given value of temp field temp_value: Option, + /// post_process + read_post_processing: Option, + /// default value code when used with skip or cond default: Option, @@ -678,8 +687,10 @@ impl FieldData { any_option_set = any_option_set || self.bytes_read.is_some() || self.until.is_some() + || self.until_with_ctx.is_some() || self.map.is_some() || self.ctx.is_some() + || self.writer_ctx.is_some() || self.update.is_some() || self.reader.is_some() || self.writer.is_some(); @@ -700,6 +711,7 @@ impl FieldData { any_option_set = any_option_set || self.pad_bytes_after.is_some() || self.temp_value.is_some() + || self.read_post_processing.is_some() || self.cond.is_some() || self.assert.is_some() || self.assert_eq.is_some() @@ -720,6 +732,11 @@ impl FieldData { .map(|s| s.parse_with(Punctuated::parse_terminated)) .transpose() .map_err(|e| e.to_compile_error())?; + let writer_ctx = receiver + .writer_ctx? + .map(|s| s.parse_with(Punctuated::parse_terminated)) + .transpose() + .map_err(|e| e.to_compile_error())?; let data = Self { ident: receiver.ident, @@ -733,9 +750,11 @@ impl FieldData { bits_read: receiver.bits_read?, bytes_read: receiver.bytes_read?, until: receiver.until?, + until_with_ctx: receiver.until_with_ctx?, read_all: receiver.read_all, map: receiver.map?, ctx, + writer_ctx, update: receiver.update?, reader: receiver.reader?, writer: receiver.writer?, @@ -748,6 +767,7 @@ impl FieldData { pad_bytes_after: receiver.pad_bytes_after?, temp: receiver.temp, temp_value: receiver.temp_value?, + read_post_processing: receiver.read_post_processing?, default: receiver.default?, cond: receiver.cond?, assert: receiver.assert?, @@ -824,12 +844,13 @@ impl FieldData { #[cfg(feature = "bits")] if data.read_all && (data.until.is_some() + || data.until_with_ctx.is_some() || data.count.is_some() || (data.bits_read.is_some() || data.bytes_read.is_some())) { return Err(cerror( data.read_all.span(), - "conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read`", + "conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read`", )); } @@ -1107,6 +1128,10 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] until: Result, ReplacementError>, + /// a predicate to decide when to stop reading elements into the container + #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] + until_with_ctx: Result, ReplacementError>, + /// read until `reader.end()` #[darling(default)] read_all: bool, @@ -1115,13 +1140,20 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] map: Result, ReplacementError>, - /// context passed to the field. + /// context passed to the field (reader/writer). See writer_ctx. /// A comma separated argument list. // TODO: The type of it should be `Punctuated` // https://github.com/TedDriggs/darling/pull/98 #[darling(default = "default_res_opt", map = "map_option_litstr")] ctx: Result, ReplacementError>, + /// writer_context passed to the field. Overrides ctx. + /// A comma separated argument list. + // TODO: The type of it should be `Punctuated` + // https://github.com/TedDriggs/darling/pull/98 + #[darling(default = "default_res_opt", map = "map_option_litstr")] + writer_ctx: Result, ReplacementError>, + /// map field when updating struct #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] update: Result, ReplacementError>, @@ -1164,6 +1196,10 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] temp_value: Result, ReplacementError>, + /// post process read values + #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] + read_post_processing: Result, ReplacementError>, + /// default value code when used with skip #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] default: Result, ReplacementError>, diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs index 9d188680..655787f2 100644 --- a/deku-derive/src/macros/deku_read.rs +++ b/deku-derive/src/macros/deku_read.rs @@ -671,11 +671,14 @@ fn emit_field_read( &f.bits_read, &f.bytes_read, &f.until, + &f.until_with_ctx, &f.cond, &f.default, &f.map, + &f.read_post_processing, &f.reader, &f.ctx.as_ref().map(|v| quote!(#v)), + &f.writer_ctx.as_ref().map(|v| quote!(#v)), &f.assert, &f.assert_eq, ]; @@ -766,6 +769,13 @@ fn emit_field_read( quote! {} }; + let read_post_processing = if f.read_post_processing.is_some() { + let code = &f.read_post_processing.clone().unwrap(); + code.clone() + } else { + quote! {} + }; + let magic_read = if let Some(magic) = &f.magic { emit_magic_read_lit(&crate_, magic) } else { @@ -876,6 +886,16 @@ fn emit_field_read( (::#crate_::ctx::Limit::new_until(#field_until), (#read_args)) )? } + } else if let Some(field_until_with_ctx) = &f.until_with_ctx { + // We wrap the input into another closure here to enforce that it is actually a callable + // Otherwise, an incorrectly passed-in integer could unexpectedly convert into a `Count` limit + quote! { + #type_as_deku_read::from_reader_with_ctx + ( + __deku_reader, + (::#crate_::ctx::Limit::new_until_with_ctx(#field_until_with_ctx), (#read_args)) + )? + } } else if f.read_all { quote! { { @@ -994,6 +1014,7 @@ fn emit_field_read( }; let #field_ident = &#internal_field_ident; + #read_post_processing #field_assert #field_assert_eq diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs index 24e43650..5eae4c34 100644 --- a/deku-derive/src/macros/deku_write.rs +++ b/deku-derive/src/macros/deku_write.rs @@ -696,6 +696,7 @@ fn emit_field_write( &f.writer, &f.cond, &f.ctx.as_ref().map(|v| quote!(#v)), + &f.writer_ctx.as_ref().map(|v| quote!(#v)), &f.assert, &f.assert_eq, ]; @@ -747,7 +748,11 @@ fn emit_field_write( #[cfg(not(feature = "bits"))] None, f.bytes.as_ref(), - f.ctx.as_ref(), + if f.writer_ctx.is_some() { + f.writer_ctx.as_ref() + } else { + f.ctx.as_ref() + }, field_bit_order, )?; diff --git a/examples/many.rs b/examples/many.rs index 29895f45..92ec2be7 100644 --- a/examples/many.rs +++ b/examples/many.rs @@ -23,7 +23,7 @@ fn main() { let mut binding = Cursor::new(custom.clone()); let mut reader = Reader::new(&mut binding); - let ret = as DekuReader>>::from_reader_with_ctx( + let ret = as DekuReader>>::from_reader_with_ctx( &mut reader, Limit::new_count(10_0000), ); diff --git a/src/attributes.rs b/src/attributes.rs index 40a2fd1f..fe1b196a 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -47,7 +47,9 @@ enum DekuEnum { | [bits_read](#bits_read) | field | Set the field representing the number of bits to read into a container | [bytes_read](#bytes_read) | field | Set the field representing the number of bytes to read into a container | [until](#until) | field | Set a predicate returning when to stop reading elements into a container +| [until_with_ctx](#context_with_interior_mutability) | field | like `until`, but the predicate also receives the context as second argument | [read_all](#read_all) | field | Read until [reader.end()] returns `true` +| [read_post_processing](#context_with_interior_mutability) | field | Code to postprocess read data (including temp values; can be used to modify the context via interior mutability) | [update](#update) | field | Apply code over the field when `.update()` is called | [temp](#temp) | field | Read the field but exclude it from the struct/enum | [temp_value](#temp_value) | field | Write the field but exclude it from the struct/enum @@ -61,7 +63,8 @@ enum DekuEnum { | [map](#map) | field | Specify a function or lambda to apply to the result of the read | [reader](#readerwriter) | variant, field | Custom reader code | [writer](#readerwriter) | variant, field | Custom writer code -| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing +| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing (reader/writer) +| [writer_ctx](#context_with_interior_mutability) | top-level, field| Context list for context sensitive parsing (overrides ctx for the writer part) | [ctx_default](#ctx_default) | top-level, field| Default context values | enum: [id](#id) | top-level, variant | enum or variant id value | enum: [id_endian](#id_endian) | top-level | Endianness of *just* the enum `id` @@ -1154,6 +1157,123 @@ assert_eq!(&*data, value); # fn main() {} ``` +#
Context with Interior Mutability (using `until_with_ctx`, `writer_ctx`, `read_post_processing`)
+ +The following example demonstrates how to read and write an array of data, where a flag in each data item (`auto_fx`) indicates whether that item is the last in a containing array. +**Importantly, this flag is not part of the actual data structure** presented to the user and is only a temporary construct used during reading and writing. +This ensures the flag is always set correctly and cannot be misused (e.g. be forgotten to be set correctly). + +The central idea is to use a **context with interior mutability** (specifically, the `fx` field) to make the temporary value available to the outer structure while traversing the array. This context can also be used to increment a counter representing the current index of the traversed array. + +**Key Steps Illustrated in This Example:** + +1. **Define a context with interior mutability**: + 1. A mutable `idx` field representing the **current index of the array** (mutable through a `Cell`, shared through an `Rc`). + 2. A mutable flag `fx` indicating the **end of the array** (mutable through a `Cell`, shared through an `Rc`). + 3. A (non-mutable) value `n` representing the **length of the array** (while writing) or zero (while reading). + +2. **Set up the context separately for reading (`ctx`) and writing (`writer_ctx`)**. This is + important, since the final array length is unknown while reading. + +3. **During reading**: + 1. The temporary information stored in the `auto_fx` field must be passed back to the caller via the context. + 2. `read_post_processing` stores the temporary value in the context. + 3. `until_with_ctx` in the surrounding array uses this context to control how much data to read (until the `fx` flag is zero). + +4. **During writing**: + - The index is updated in each step and the `auto_fx` field is set accordingly (last entry is set to `false`). + +Example: + +* Here, the user fills an array with x/y data + ```rust,ignore + A { items: vec![ + B { x: 8, y: 9 }, + B { x: 7, y: 9 }, + B { x: 6, y: 9 }, + ] }; + ``` +* This data is stored on disk as x/y/"index-in-array"/"fx=0 for the last element, else fx=1" + ```rust,ignore + vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0] + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 + ``` +* The user (providing the x/y data) is not aware of the index and the `fx` field + stored on disk (and cannot break the binary data format by providing some wrong data). + +* Note: this enables to implement "Extended Length Data Items" from the + [ASTERIX EUROCONTOL](https://www.eurocontrol.int/publication/eurocontrol-specification-surveillance-data-exchange-part-i) + standard, without making control flags (`fx`) visible in the user data. + +```rust +# #[cfg(feature = "alloc")] +# use deku::prelude::*; +# #[cfg(feature = "alloc")] +# fn main() { +#[derive(Debug, Clone)] +struct IndexContext { + idx: std::rc::Rc>, // see text, 1.1 + n: usize, // see text, 1.2 + fx: std::rc::Rc>, // see text, 1.3 +} +#[deku_derive(DekuRead, DekuWrite)] +#[derive(PartialEq, Debug, Clone)] +struct A { + #[deku( + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", // see text, 3.1 + 3.3 + + // see text, 2 + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: Vec, +} +#[deku_derive(DekuRead, DekuWrite)] +#[derive(PartialEq, Debug, Clone)] +#[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" +)] // this struct uses a context for serialization. For deserialization it also works with the default context. +struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", // see text, 3.2 + 3.3 + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" // see text, 4 + )] + auto_fx: u8, +} +# +# let test_data = A { +# items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], +# }; +# +# let ret_write: Vec = test_data.clone().try_into().unwrap(); +# assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); +# // ^ ^ ^ ^ ^ ^ +# // | fx=1 | fx=1 | fx=0 (last) +# // idx=0 idx=1 idx=2 +# +# // read the data back +# let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; +# assert_eq!(check_data, test_data); +# +# // check with fx=0 after the second element: +# let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; +# assert_eq!(check_data.items.len(), 2); +# } +# #[cfg(not(feature = "alloc"))] +# fn main() {} +``` + # update Specify custom code to run on the field when `.update()` is called on the struct/enum diff --git a/src/ctx.rs b/src/ctx.rs index 34830b85..6c8e077f 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -93,13 +93,16 @@ impl FromStr for Endian { // For details: https://github.com/rust-lang/rust-clippy/issues/9413 /// A limit placed on a container's elements #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] -pub enum Limit bool> { +pub enum Limit bool, Ctx, PredicateWithCtx: FnMut(&T, Ctx) -> bool> { /// Read a specific count of elements Count(usize), /// Read until a given predicate holds true Until(Predicate, PhantomData), + /// Read until a given predicate holds true + UntilWithCtx(PredicateWithCtx, PhantomData, PhantomData), + /// Read until a given quantity of bytes have been read ByteSize(ByteSize), @@ -110,35 +113,52 @@ pub enum Limit bool> { End, } -impl From for Limit bool> { +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(n: usize) -> Self { Limit::Count(n) } } -impl FnMut(&'a T) -> bool> From for Limit { +impl FnMut(&'a T) -> bool> From + for Limit bool> +{ #[inline] fn from(predicate: Predicate) -> Self { Limit::Until(predicate, PhantomData) } } -impl From for Limit bool> { +impl< + T, + Ctx, + Predicate: for<'a> FnMut(&'a T) -> bool, + PredicateWithCtx: for<'a> FnMut(&'a T, Ctx) -> bool, + > From for Limit +{ + #[inline] + fn from(predicate_with_ctx: PredicateWithCtx) -> Self { + Limit::UntilWithCtx(predicate_with_ctx, PhantomData, PhantomData) + } +} + +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(size: ByteSize) -> Self { Limit::ByteSize(size) } } -impl From for Limit bool> { +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(size: BitSize) -> Self { Limit::BitSize(size) } } -impl FnMut(&'a T) -> bool> Limit { +impl FnMut(&'a T) -> bool> + Limit bool> +{ /// Constructs a new Limit that reads until the given predicate returns true /// The predicate is given a reference to the latest read value and must return /// true to stop reading @@ -148,7 +168,19 @@ impl FnMut(&'a T) -> bool> Limit { } } -impl Limit bool> { +impl FnMut(&'a T, Ctx) -> bool> + Limit bool, Ctx, PredicateWithCtx> +{ + /// Constructs a new Limit that reads until the given predicate returns true + /// The predicate is given a reference to the latest read value and must return + /// true to stop reading + #[inline] + pub fn new_until_with_ctx(predicate: PredicateWithCtx) -> Self { + predicate.into() + } +} + +impl Limit bool, Ctx, fn(&T, Ctx) -> bool> { /// Read until `reader.end()` is true #[inline] pub fn end() -> Self { @@ -156,7 +188,7 @@ impl Limit bool> { } } -impl Limit bool> { +impl Limit bool, Ctx, fn(&T, Ctx) -> bool> { /// Constructs a new Limit that reads until the given number of elements are read #[inline] pub fn new_count(count: usize) -> Self { diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 92fc5a64..f2f5adc5 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -10,29 +10,31 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Arc where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Arc::new(val)) } } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Arc<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Arc<[T]> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Arc<[T]> - let val = >::from_reader_with_ctx(reader, (limit, inner_ctx))?; + let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; Ok(Arc::from(val.into_boxed_slice())) } } @@ -40,7 +42,7 @@ where impl DekuWriter for Arc<[T]> where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -49,7 +51,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self.as_ref() { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } @@ -58,7 +60,7 @@ where impl DekuWriter for Arc where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -66,7 +68,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - self.as_ref().to_writer(writer, ctx)?; + self.as_ref().to_writer(writer, ctx.clone())?; Ok(()) } } @@ -80,7 +82,6 @@ mod tests { use rstest::rstest; use super::*; - #[cfg(feature = "bits")] use crate::ctx::{BitSize, Endian}; use crate::native_endian; use crate::reader::Reader; @@ -104,6 +105,9 @@ mod tests { assert_eq!(input.to_vec(), writer.inner.into_inner()); } + type MyLimit = + Limit bool>; + // Note: Copied tests from vec.rs impl #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -118,7 +122,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit, expected: Arc<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/bool.rs b/src/impls/bool.rs index ef082bed..708331ca 100644 --- a/src/impls/bool.rs +++ b/src/impls/bool.rs @@ -6,14 +6,14 @@ use crate::{deku_error, DekuError, DekuReader, DekuWriter}; impl<'a, Ctx> DekuReader<'a, Ctx> for bool where - Ctx: Copy, + Ctx: Clone, u8: DekuReader<'a, Ctx>, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = u8::from_reader_with_ctx(reader, inner_ctx)?; + let val = u8::from_reader_with_ctx(reader, inner_ctx.clone())?; let ret = match val { 0x01 => Ok(true), diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs index 89876a69..3183ca8d 100644 --- a/src/impls/boxed.rs +++ b/src/impls/boxed.rs @@ -11,29 +11,31 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Box where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Box::new(val)) } } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Box<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Box<[T]> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Box<[T]> - let val = >::from_reader_with_ctx(reader, (limit, inner_ctx))?; + let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; Ok(val.into_boxed_slice()) } } @@ -41,7 +43,7 @@ where impl DekuWriter for Box<[T]> where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -50,7 +52,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self.as_ref() { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } @@ -59,7 +61,7 @@ where impl DekuWriter for Box where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -67,7 +69,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - self.as_ref().to_writer(writer, ctx)?; + self.as_ref().to_writer(writer, ctx.clone())?; Ok(()) } } @@ -103,6 +105,9 @@ mod tests { assert_eq!(input.to_vec(), writer.inner.into_inner()); } + type MyLimit = + Limit bool>; + // Note: Copied tests from vec.rs impl #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), vec![0xBBAA, 0xDDCC].into_boxed_slice(), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]), @@ -116,7 +121,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit, expected: Box<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/cow.rs b/src/impls/cow.rs index 335aaa0a..de3bc33a 100644 --- a/src/impls/cow.rs +++ b/src/impls/cow.rs @@ -9,13 +9,13 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Cow<'a, T> where T: DekuReader<'a, Ctx> + Clone, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Cow::Owned(val)) } } @@ -23,7 +23,7 @@ where impl DekuWriter for Cow<'_, T> where T: DekuWriter + Clone, - Ctx: Copy, + Ctx: Clone, { /// Write T from Cow fn to_writer( @@ -31,7 +31,7 @@ where writer: &mut Writer, inner_ctx: Ctx, ) -> Result<(), DekuError> { - (self.borrow() as &T).to_writer(writer, inner_ctx) + (self.borrow() as &T).to_writer(writer, inner_ctx.clone()) } } diff --git a/src/impls/cstring.rs b/src/impls/cstring.rs index 8077bd85..f5d76adc 100644 --- a/src/impls/cstring.rs +++ b/src/impls/cstring.rs @@ -8,7 +8,7 @@ use crate::writer::Writer; use crate::{ctx::*, DekuReader}; use crate::{DekuError, DekuWriter}; -impl DekuWriter for CString +impl DekuWriter for CString where u8: DekuWriter, { @@ -18,7 +18,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { let bytes = self.as_bytes_with_nul(); - bytes.to_writer(writer, ctx) + bytes.to_writer(writer, ctx.clone()) } } diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index 9ebff2e1..5aefc8f5 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -25,7 +25,7 @@ where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &(K, V)) -> bool, { let mut res = HashMap::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -34,7 +34,7 @@ where let orig_bits_read = reader.bits_read; while !found_predicate { - let val = <(K, V)>::from_reader_with_ctx(reader, ctx)?; + let val = <(K, V)>::from_reader_with_ctx(reader, ctx.clone())?; found_predicate = predicate(reader.bits_read - orig_bits_read, &val); res.insert(val.0, val.1); } @@ -51,7 +51,7 @@ where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, { let mut res = HashMap::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -59,21 +59,22 @@ where if reader.end() { break; } - let val = <(K, V)>::from_reader_with_ctx(reader, ctx)?; + let val = <(K, V)>::from_reader_with_ctx(reader, ctx.clone())?; res.insert(val.0, val.1); } Ok(res) } -impl<'a, K, V, S, Ctx, Predicate> DekuReader<'a, (Limit<(K, V), Predicate>, Ctx)> - for HashMap +impl<'a, K, V, S, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit<(K, V), Predicate, Ctx, PredicateWithCtx>, Ctx)> for HashMap where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&(K, V)) -> bool, + PredicateWithCtx: FnMut(&(K, V), Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -103,7 +104,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit<(K, V), Predicate>, Ctx), + (limit, inner_ctx): (Limit<(K, V), Predicate, Ctx, PredicateWithCtx>, Ctx), ) -> Result where Self: Sized, @@ -120,7 +121,7 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, Some(count), - inner_ctx, + inner_ctx.clone(), move |_, _| { count -= 1; count == 0 @@ -132,10 +133,20 @@ where Limit::Until(mut predicate, _) => from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |_, kv| predicate(kv), ), + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => { + from_reader_with_ctx_hashmap_with_predicate( + reader, + None, + inner_ctx.clone(), + move |_, kv| predicate(kv, inner_ctx.clone()), + ) + } + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -148,7 +159,7 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } @@ -165,28 +176,30 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } // Read until `reader.end()` is true - Limit::End => from_reader_with_ctx_hashmap_to_end(reader, None, inner_ctx), + Limit::End => from_reader_with_ctx_hashmap_to_end(reader, None, inner_ctx.clone()), } } } -impl<'a, K, V, S, Predicate> DekuReader<'a, Limit<(K, V), Predicate>> for HashMap +impl<'a, K, V, S, Predicate, PredicateWithCtx> + DekuReader<'a, Limit<(K, V), Predicate, (), PredicateWithCtx>> for HashMap where K: DekuReader<'a> + Eq + Hash, V: DekuReader<'a>, S: BuildHasher + Default, Predicate: FnMut(&(K, V)) -> bool, + PredicateWithCtx: FnMut(&(K, V), ()) -> bool, { /// Read `K, V`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit<(K, V), Predicate>, + limit: Limit<(K, V), Predicate, (), PredicateWithCtx>, ) -> Result where Self: Sized, @@ -195,7 +208,7 @@ where } } -impl, V: DekuWriter, S, Ctx: Copy> DekuWriter for HashMap { +impl, V: DekuWriter, S, Ctx: Clone> DekuWriter for HashMap { /// Write all `K, V`s in a `HashMap` to bits. /// * **inner_ctx** - The context required by `K, V`. /// @@ -235,7 +248,7 @@ impl, V: DekuWriter, S, Ctx: Copy> DekuWriter for H inner_ctx: Ctx, ) -> Result<(), DekuError> { for kv in self { - kv.to_writer(writer, inner_ctx)?; + kv.to_writer(writer, inner_ctx.clone())?; } Ok(()) } @@ -270,17 +283,13 @@ mod tests { }; ); + type MyLimit = + Limit<(u8, u8), Predicate, (Endian, BitSize), fn(&(u8, u8), (Endian, BitSize)) -> bool>; + #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashMap::default(), bits![u8, Msb0;], &[0xaa]), case::count_1([0x01, 0xAA, 0x02, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0x02, 0xbb]), case::count_2([0x01, 0xAA, 0x02, 0xBB, 0xBB].as_ref(), Endian::Little, Some(8), 2.into(), fxhashmap!{0x01 => 0xAA, 0x02 => 0xBB}, bits![u8, Msb0;], &[0xbb]), - case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, None, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::read_all([0x01, 0xAA].as_ref(), Endian::Little, None, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), - case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), case::bits_6([0b0000_0100, 0b1111_0000, 0b1000_0000].as_ref(), Endian::Little, Some(6), 2.into(), fxhashmap!{0x01 => 0x0F, 0x02 => 0}, bits![u8, Msb0;], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] case::not_enough_data([].as_ref(), Endian::Little, Some(9), 1.into(), FxHashMap::default(), bits![u8, Msb0;], &[]), @@ -299,23 +308,51 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit<(u8, u8), Predicate>, + limit: MyLimit, expected: FxHashMap, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(bit_size) => FxHashMap::::from_reader_with_ctx( - &mut reader, - (limit, (endian, BitSize(bit_size))), - ) - .unwrap(), - None => { - FxHashMap::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap() - } - }; + let res_read = FxHashMap::::from_reader_with_ctx( + &mut reader, + (limit, (endian, BitSize(bit_size.unwrap()))), + ) + .unwrap(); + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + + type MyLimit2 = Limit<(u8, u8), Predicate, Endian, fn(&(u8, u8), Endian) -> bool>; + + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::read_all([0x01, 0xAA].as_ref(), Endian::Little, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), + case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + )] + fn test_hashmap_read_no_bitsize bool + Copy>( + input: &[u8], + endian: Endian, + limit: MyLimit2, + expected: FxHashMap, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = + FxHashMap::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index bd9ba58c..2951a307 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -24,7 +24,7 @@ fn from_reader_with_ctx_hashset_with_predicate<'a, T, S, Ctx, Predicate, R: Read where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &T) -> bool, { let mut res = HashSet::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -33,7 +33,7 @@ where let orig_bits_read = reader.bits_read; while !found_predicate { - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; found_predicate = predicate(reader.bits_read - orig_bits_read, &val); res.insert(val); } @@ -49,7 +49,7 @@ fn from_reader_with_ctx_hashset_to_end<'a, T, S, Ctx, R: Read + Seek>( where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, { let mut res = HashSet::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -57,19 +57,21 @@ where if reader.end() { break; } - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.insert(val); } Ok(res) } -impl<'a, T, S, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for HashSet +impl<'a, T, S, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for HashSet where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -88,7 +90,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -105,7 +107,7 @@ where from_reader_with_ctx_hashset_with_predicate( reader, Some(count), - inner_ctx, + inner_ctx.clone(), move |_, _| { count -= 1; count == 0 @@ -117,10 +119,20 @@ where Limit::Until(mut predicate, _) => from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |_, value| predicate(value), ), + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => { + from_reader_with_ctx_hashset_with_predicate( + reader, + None, + inner_ctx.clone(), + move |_, value| predicate(value, inner_ctx.clone()), + ) + } + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -133,7 +145,7 @@ where from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } @@ -150,24 +162,29 @@ where from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } // Read until `reader.end()` is true - Limit::End => from_reader_with_ctx_hashset_to_end(reader, None, inner_ctx), + Limit::End => from_reader_with_ctx_hashset_to_end(reader, None, inner_ctx.clone()), } } } -impl<'a, T: DekuReader<'a> + Eq + Hash, S: BuildHasher + Default, Predicate: FnMut(&T) -> bool> - DekuReader<'a, Limit> for HashSet +impl< + 'a, + T: DekuReader<'a> + Eq + Hash, + S: BuildHasher + Default, + Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for HashSet { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, @@ -176,7 +193,7 @@ impl<'a, T: DekuReader<'a> + Eq + Hash, S: BuildHasher + Default, Predicate: FnM } } -impl, S, Ctx: Copy> DekuWriter for HashSet { +impl, S, Ctx: Clone> DekuWriter for HashSet { /// Write all `T`s in a `HashSet` to bits. /// * **inner_ctx** - The context required by `T`. /// @@ -205,7 +222,7 @@ impl, S, Ctx: Copy> DekuWriter for HashSet { inner_ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, inner_ctx)?; + v.to_writer(writer, inner_ctx.clone())?; } Ok(()) } @@ -225,18 +242,14 @@ mod tests { use super::*; + type MyLimit = + Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashSet::default(), bits![u8, Msb0;], &[0xaa]), case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), case::count_2([0xAA, 0xBB, 0xCC].as_ref(), Endian::Little, Some(8), 2.into(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[0xcc]), - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), - case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), case::bits_6([0b0110_1001, 0b1110_1001].as_ref(), Endian::Little, Some(6), 2.into(), vec![0b00_011010, 0b00_011110].into_iter().collect(), bits![u8, Msb0; 1, 0, 0, 1], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] case::not_enough_data([].as_ref(), Endian::Little, Some(9), 1.into(), FxHashSet::default(), bits![u8, Msb0;], &[]), @@ -255,21 +268,52 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(bit_size) => FxHashSet::::from_reader_with_ctx( - &mut reader, - (limit, (endian, BitSize(bit_size))), - ) - .unwrap(), - None => FxHashSet::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), - }; + let res_read = FxHashSet::::from_reader_with_ctx( + &mut reader, + (limit, (endian, BitSize(bit_size.unwrap()))), + ) + .unwrap(); + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + + type MyLimit2 = Limit bool>; + + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), + case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + )] + fn test_hashset_read_no_bitsize bool + Copy>( + input: &[u8], + endian: Endian, + limit: MyLimit2, + expected: FxHashSet, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = + FxHashSet::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), @@ -297,6 +341,9 @@ mod tests { .any(|u| v == u))); } + type MyLimit3 = + Limit bool>; + // Note: These tests also exist in boxed.rs #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -311,7 +358,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit3, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/option.rs b/src/impls/option.rs index 60acf368..ef5a426c 100644 --- a/src/impls/option.rs +++ b/src/impls/option.rs @@ -2,24 +2,24 @@ use no_std_io::io::{Read, Seek, Write}; use crate::{writer::Writer, DekuError, DekuReader, DekuWriter}; -impl<'a, T: DekuReader<'a, Ctx>, Ctx: Copy> DekuReader<'a, Ctx> for Option { +impl<'a, T: DekuReader<'a, Ctx>, Ctx: Clone> DekuReader<'a, Ctx> for Option { fn from_reader_with_ctx( reader: &mut crate::reader::Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Some(val)) } } -impl, Ctx: Copy> DekuWriter for Option { +impl, Ctx: Clone> DekuWriter for Option { fn to_writer( &self, writer: &mut Writer, inner_ctx: Ctx, ) -> Result<(), DekuError> { self.as_ref() - .map_or(Ok(()), |v| v.to_writer(writer, inner_ctx)) + .map_or(Ok(()), |v| v.to_writer(writer, inner_ctx.clone())) } } diff --git a/src/impls/slice.rs b/src/impls/slice.rs index 1a906ee4..419bd4b2 100644 --- a/src/impls/slice.rs +++ b/src/impls/slice.rs @@ -6,7 +6,7 @@ use crate::{DekuError, DekuReader, DekuWriter}; use core::mem::MaybeUninit; use no_std_io::io::{Read, Seek, Write}; -impl<'a, Ctx: Copy, T, const N: usize> DekuReader<'a, Ctx> for [T; N] +impl<'a, Ctx: Clone, T, const N: usize> DekuReader<'a, Ctx> for [T; N] where T: DekuReader<'a, Ctx>, { @@ -19,7 +19,7 @@ where { let mut array: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; for (n, item) in array.iter_mut().enumerate() { - match T::from_reader_with_ctx(reader, ctx) { + match T::from_reader_with_ctx(reader, ctx.clone()) { Ok(value) => { item.write(value); } @@ -46,7 +46,7 @@ where } } -impl DekuWriter for [T; N] +impl DekuWriter for [T; N] where T: DekuWriter, { @@ -56,13 +56,13 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } } -impl DekuWriter for &[T] +impl DekuWriter for &[T] where T: DekuWriter, { @@ -72,13 +72,13 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in *self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } } -impl DekuWriter for [T] +impl DekuWriter for [T] where T: DekuWriter, { @@ -88,7 +88,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } diff --git a/src/impls/tuple.rs b/src/impls/tuple.rs index 7c17b502..e819819f 100644 --- a/src/impls/tuple.rs +++ b/src/impls/tuple.rs @@ -37,7 +37,7 @@ macro_rules! ImplDekuTupleTraits { } } - impl<'a, Ctx: Copy, $($T:DekuReader<'a, Ctx>+Sized),+> DekuReader<'a, Ctx> for ($($T,)+) + impl<'a, Ctx: Clone, $($T:DekuReader<'a, Ctx>+Sized),+> DekuReader<'a, Ctx> for ($($T,)+) { fn from_reader_with_ctx( reader: &mut crate::reader::Reader, @@ -48,20 +48,20 @@ macro_rules! ImplDekuTupleTraits { { let tuple = (); $( - let val = <$T>::from_reader_with_ctx(reader, ctx)?; + let val = <$T>::from_reader_with_ctx(reader, ctx.clone())?; let tuple = tuple.append(val); )+ Ok(tuple) } } - impl),+> DekuWriter for ($($T,)+) + impl),+> DekuWriter for ($($T,)+) { #[allow(non_snake_case)] fn to_writer(&self, writer: &mut Writer, ctx: Ctx) -> Result<(), DekuError> { let ($(ref $T,)+) = *self; $( - $T.to_writer(writer, ctx)?; + $T.to_writer(writer, ctx.clone())?; )+ Ok(()) } diff --git a/src/impls/unit.rs b/src/impls/unit.rs index e2d1f5f5..86e469cc 100644 --- a/src/impls/unit.rs +++ b/src/impls/unit.rs @@ -2,7 +2,7 @@ use no_std_io::io::{Read, Seek, Write}; use crate::{reader::Reader, writer::Writer, DekuError, DekuReader, DekuWriter}; -impl DekuReader<'_, Ctx> for () { +impl DekuReader<'_, Ctx> for () { fn from_reader_with_ctx( _reader: &mut Reader, _inner_ctx: Ctx, @@ -11,7 +11,7 @@ impl DekuReader<'_, Ctx> for () { } } -impl DekuWriter for () { +impl DekuWriter for () { /// NOP on write fn to_writer( &self, diff --git a/src/impls/vec.rs b/src/impls/vec.rs index 354761a4..e1125a37 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -38,7 +38,7 @@ fn reader_vec_with_predicate<'a, T, Ctx, Predicate, R: Read + Seek>( ) -> Result, DekuError> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &T) -> bool, { // ZST detected, return empty vec @@ -51,7 +51,7 @@ where let start_read = reader.bits_read; loop { - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.push(val); // This unwrap is safe as we are pushing to the vec immediately before it, @@ -64,6 +64,44 @@ where Ok(res) } +fn reader_vec_with_predicate_with_context<'a, T, Ctx, PredicateWithCtx, R: Read + Seek>( + reader: &mut Reader, + capacity: Option, + ctx: Ctx, + mut predicate: PredicateWithCtx, +) -> Result, DekuError> +where + T: DekuReader<'a, Ctx>, + Ctx: Clone, + PredicateWithCtx: FnMut(usize, &T, Ctx) -> bool, +{ + // ZST detected, return empty vec + if mem::size_of::() == 0 { + return Ok(Vec::new()); + } + + let mut res = capacity.map_or_else(Vec::new, Vec::with_capacity); + + let start_read = reader.bits_read; + + loop { + let val = ::from_reader_with_ctx(reader, ctx.clone())?; + res.push(val); + + // This unwrap is safe as we are pushing to the vec immediately before it, + // so there will always be a last element + if predicate( + reader.bits_read - start_read, + res.last().unwrap(), + ctx.clone(), + ) { + break; + } + } + + Ok(res) +} + fn reader_vec_to_end<'a, T, Ctx, R: Read + Seek>( reader: &mut crate::reader::Reader, capacity: Option, @@ -71,7 +109,7 @@ fn reader_vec_to_end<'a, T, Ctx, R: Read + Seek>( ) -> Result, DekuError> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { // ZST detected, return empty vec if mem::size_of::() == 0 { @@ -83,22 +121,24 @@ where if reader.end() { break; } - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.push(val); } Ok(res) } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Vec +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Vec where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -112,7 +152,7 @@ where } // Otherwise, read until we have read `count` elements - reader_vec_with_predicate(reader, Some(count), inner_ctx, move |_, _| { + reader_vec_with_predicate(reader, Some(count), inner_ctx.clone(), move |_, _| { count -= 1; count == 0 }) @@ -120,9 +160,19 @@ where // Read until a given predicate returns true Limit::Until(mut predicate, _) => { - reader_vec_with_predicate(reader, None, inner_ctx, move |_, value| predicate(value)) + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |_, value| { + predicate(value) + }) } + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => reader_vec_with_predicate_with_context( + reader, + None, + inner_ctx.clone(), + move |_, value, ctx| predicate(value, ctx), + ), + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -132,7 +182,7 @@ where return Ok(Vec::new()); } - reader_vec_with_predicate(reader, None, inner_ctx, move |read_bits, _| { + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |read_bits, _| { read_bits == bit_size }) } @@ -146,23 +196,27 @@ where return Ok(Vec::new()); } - reader_vec_with_predicate(reader, None, inner_ctx, move |read_bits, _| { + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |read_bits, _| { read_bits == bit_size }) } - Limit::End => reader_vec_to_end(reader, None, inner_ctx), + Limit::End => reader_vec_to_end(reader, None, inner_ctx.clone()), } } } -impl<'a, T: DekuReader<'a>, Predicate: FnMut(&T) -> bool> DekuReader<'a, Limit> - for Vec +impl< + 'a, + T: DekuReader<'a>, + Predicate: FnMut(&T) -> bool, + PredicateWithCtx: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for Vec { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, @@ -171,7 +225,7 @@ impl<'a, T: DekuReader<'a>, Predicate: FnMut(&T) -> bool> DekuReader<'a, Limit, Ctx: Copy> DekuWriter for Vec { +impl, Ctx: Clone> DekuWriter for Vec { /// Write all `T`s in a `Vec` to bits. /// * **inner_ctx** - The context required by `T`. /// # Examples @@ -202,7 +256,7 @@ impl, Ctx: Copy> DekuWriter for Vec { inner_ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, inner_ctx)?; + v.to_writer(writer, inner_ctx.clone())?; } Ok(()) } @@ -222,6 +276,8 @@ mod tests { use super::*; + type MyLimit3 = Limit bool>; + #[cfg(feature = "bits")] #[rstest(input, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]), @@ -232,7 +288,7 @@ mod tests { )] fn test_vec_reader_no_ctx bool>( mut input: &[u8], - limit: Limit, + limit: MyLimit3, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -250,14 +306,14 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit4 = + Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]), case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), case::count_2([0xAA, 0xBB, 0xCC].as_ref(), Endian::Little, Some(8), 2.into(), vec![0xAA, 0xBB], bits![u8, Msb0;], &[0xcc]), - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), - case::end([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), case::end_bitsize([0xf0, 0xf0].as_ref(), Endian::Little, Some(4), Limit::end(), vec![0xf, 0x0, 0x0f, 0x0], bits![u8, Msb0;], &[]), case::bits_6([0b0110_1001, 0b1110_1001].as_ref(), Endian::Little, Some(6), 2.into(), vec![0b00_011010, 0b00_011110], bits![u8, Msb0; 1, 0, 0, 1], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] @@ -277,7 +333,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit4, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -289,7 +345,7 @@ mod tests { Vec::::from_reader_with_ctx(&mut reader, (limit, (endian, BitSize(bit_size)))) .unwrap() } - None => Vec::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), + None => panic!("unexpected"), }; assert_eq!(expected, res_read); assert_eq!( @@ -301,6 +357,35 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit = Limit bool>; + + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), + case::end([0xAA, 0xBB].as_ref(), Endian::Little, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), + )] + fn test_vec_reader_no_bitsize bool>( + input: &[u8], + endian: Endian, + limit: MyLimit, + expected: Vec, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = Vec::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + #[cfg(feature = "alloc")] #[rstest(input, endian, expected, case::normal(vec![0xAABB, 0xCCDD], Endian::Little, vec![0xBB, 0xAA, 0xDD, 0xCC]), @@ -311,6 +396,9 @@ mod tests { assert_eq!(expected, writer.inner.into_inner()); } + type MyLimit2 = + Limit bool>; + // Note: These tests also exist in boxed.rs #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -325,7 +413,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: MyLimit2, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/lib.rs b/src/lib.rs index d1eef61e..b076ffbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -821,7 +821,7 @@ pub trait DekuSize { impl DekuWriter for &T where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { #[inline(always)] fn to_writer( @@ -829,7 +829,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - ::to_writer(self, writer, ctx)?; + ::to_writer(self, writer, ctx.clone())?; Ok(()) } } diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index c76d6325..3e87a851 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -1,4 +1,5 @@ use core::convert::{TryFrom, TryInto}; +use std::collections::{HashMap, HashSet}; use std::io::Cursor; use deku::prelude::*; @@ -286,3 +287,243 @@ fn test_enum_endian_ctx() { let ret_write: Vec = ret_read.try_into().unwrap(); assert_eq!(ret_write, test_data) } + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + struct A { + #[deku( + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: Vec, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let test_data = A { + items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], + }; + + let ret_write: Vec = test_data.clone().try_into().unwrap(); + assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 + + // read the data back + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); + + // check with fx=0 after the second element: + let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; + assert_eq!(check_data.items.len(), 2); +} + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_hashset() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + struct A { + #[deku( + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: HashSet, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let test_data = A { + items: HashSet::from([B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }]), + }; + + let ret_write: Vec = test_data.clone().try_into().unwrap(); + let test_bytes = vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0]; + assert_eq!(ret_write.len(), test_bytes.len()); + assert_eq!(ret_write.last().unwrap(), &0); + + // read the data back + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); + + // check with fx=0 after the second element: + let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; + assert_eq!(check_data.items.len(), 2); +} + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_hashmap() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + struct A { + #[deku( + until_with_ctx = "|x:&(N,B),ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: HashMap, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct N { + n: u8, + } + + let test_data = A { + items: HashMap::from([ + (N { n: 1 }, B { x: 8, y: 9 }), + (N { n: 2 }, B { x: 7, y: 9 }), + (N { n: 3 }, B { x: 6, y: 9 }), + ]), + }; + + let ret_write: Vec = test_data.clone().try_into().unwrap(); + assert_eq!(ret_write.last().unwrap(), &0); + + // read the data back + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); +} + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let data = [B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }]; + + let mut buf = vec![]; + let mut cursor = Cursor::new(&mut buf); + let mut writer = Writer::new(&mut cursor); + let myref = &data[..]; + // auto referencing rules: we want to call the method on `&[T]`: + (&myref) + .to_writer( + &mut writer, + IndexContext { + idx: std::rc::Rc::new(std::cell::Cell::new(0)), + n: myref.len(), + fx: std::rc::Rc::new(std::cell::Cell::new(false)), + }, + ) + .unwrap(); + assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], buf); + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 +} diff --git a/tests/test_compile/cases/count_read_conflict.stderr b/tests/test_compile/cases/count_read_conflict.stderr index 9adaa4a7..ba4665e2 100644 --- a/tests/test_compile/cases/count_read_conflict.stderr +++ b/tests/test_compile/cases/count_read_conflict.stderr @@ -22,7 +22,7 @@ error: conflicting: both `count` and `bytes_read` specified on field 23 | #[deku(count = "1", bytes_read = "2")] | ^^^ -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:27:10 | 27 | #[derive(DekuRead)] @@ -30,7 +30,7 @@ error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read` | = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info) -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:33:10 | 33 | #[derive(DekuRead)] @@ -38,7 +38,7 @@ error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read` | = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info) -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:39:10 | 39 | #[derive(DekuRead)]