Repository: sharksforarms/deku
Pull request #534 (2025-03-15 19:06:52)

Restore last bit read during id_pat
* Restore last leftover reading in reader for bit reads during id_pat

github-actions[bot]: ## Benchmark for 7e69ca1
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count_from_u8_specialize | 102.7±1.83ns | **96.7±0.92ns** | **-5.84%** |
| count_no_specialize | 2.4±0.02µs | 2.4±0.01µs | 0.00% |
| count_specialize | 106.6±0.62ns | **97.3±0.85ns** | **-8.72%** |
| deku_read_bits | 1289.8±16.91ns | **1218.9±30.88ns** | **-5.50%** |
| deku_read_byte | 5.3±0.05ns | 5.3±0.05ns | 0.00% |
| deku_read_enum | **4.8±0.06ns** | 5.1±0.04ns | **+6.25%** |
| deku_read_vec | 30.9±0.48ns | 30.8±0.58ns | -0.32% |
| deku_write_bits | **241.9±4.09ns** | 248.5±5.02ns | **+2.73%** |
| deku_write_byte | 24.3±0.36ns | **23.7±0.33ns** | **-2.47%** |
| deku_write_enum | 23.1±0.43ns | **22.5±0.34ns** | **-2.60%** |
| deku_write_vec | 502.1±6.51ns | 500.4±4.81ns | -0.34% |
| read_all | 5.1±0.07µs | **5.0±0.04µs** | **-1.96%** |
| read_all_bytes | **5.1±0.04µs** | 5.2±0.04µs | **+1.96%** |

  </details>
  
sharksforarms: thanks! looks like some simple CI failures
github-actions[bot]: ## Benchmark for a986f16
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count_from_u8_specialize | **96.3±1.03ns** | 99.8±0.96ns | **+3.63%** |
| count_no_specialize | **2.4±0.01µs** | 2.8±0.01µs | **+16.67%** |
| count_specialize | **96.4±1.00ns** | 103.3±0.82ns | **+7.16%** |
| deku_read_bits | **1261.3±19.16ns** | 1311.2±21.49ns | **+3.96%** |
| deku_read_byte | 5.3±0.04ns | **4.9±0.06ns** | **-7.55%** |
| deku_read_enum | 4.8±0.05ns | **4.4±0.04ns** | **-8.33%** |
| deku_read_vec | 31.3±0.37ns | 31.1±1.25ns | -0.64% |
| deku_write_bits | **225.6±3.64ns** | 245.9±3.18ns | **+9.00%** |
| deku_write_byte | 24.7±0.46ns | **24.0±0.31ns** | **-2.83%** |
| deku_write_enum | 22.8±0.30ns | **22.5±0.36ns** | **-1.32%** |
| deku_write_vec | 497.9±6.62ns | 498.1±6.11ns | +0.04% |
| read_all | **5.1±0.04µs** | 5.2±0.03µs | **+1.96%** |
| read_all_bytes | 5.2±0.13µs | 5.2±0.04µs | 0.00% |

  </details>
  

Patch:
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 5526e9d..60f1ea5 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -371,6 +371,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     let variant_read = quote! {
         __deku_reader.last_bits_read_amt = 0;
+        let __deku_last_leftover = __deku_reader.leftover.clone();
         #variant_id_read
 
         #(#pre_match_tokens)*
@@ -822,6 +823,7 @@ fn emit_field_read(
                     if let Err(e) = __deku_reader.seek_last_read() {
                         return Err(::#crate_::DekuError::Io(e.kind()));
                     }
+                    __deku_reader.leftover = __deku_last_leftover;
                     #type_as_deku_read::from_reader_with_ctx
                     (
                         __deku_reader,
diff --git a/src/impls/arc.rs b/src/impls/arc.rs
new file mode 100644
index 0000000..d00af34
--- /dev/null
+++ b/src/impls/arc.rs
@@ -0,0 +1,151 @@
+use no_std_io::io::{Read, Seek, Write};
+use std::sync::Arc;
+
+use alloc::vec::Vec;
+
+use crate::ctx::Limit;
+use crate::reader::Reader;
+use crate::writer::Writer;
+use crate::{DekuError, DekuReader, DekuWriter};
+
+impl<'a, T, Ctx> DekuReader<'a, Ctx> for Arc<T>
+where
+    T: DekuReader<'a, Ctx>,
+    Ctx: Copy,
+{
+    fn from_reader_with_ctx<R: Read + Seek>(
+        reader: &mut Reader<R>,
+        inner_ctx: Ctx,
+    ) -> Result<Self, DekuError> {
+        let val = <T>::from_reader_with_ctx(reader, inner_ctx)?;
+        Ok(Arc::new(val))
+    }
+}
+
+impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit<T, Predicate>, Ctx)> for Arc<[T]>
+where
+    T: DekuReader<'a, Ctx>,
+    Ctx: Copy,
+    Predicate: FnMut(&T) -> bool,
+{
+    fn from_reader_with_ctx<R: Read + Seek>(
+        reader: &mut Reader<R>,
+        (limit, inner_ctx): (Limit<T, Predicate>, Ctx),
+    ) -> Result<Self, DekuError> {
+        // use Vec<T>'s implementation and convert to Arc<[T]>
+        let val = <Vec<T>>::from_reader_with_ctx(reader, (limit, inner_ctx))?;
+        Ok(Arc::from(val.into_boxed_slice()))
+    }
+}
+
+impl<T, Ctx> DekuWriter<Ctx> for Arc<[T]>
+where
+    T: DekuWriter<Ctx>,
+    Ctx: Copy,
+{
+    /// Write all `T`s to bits
+    fn to_writer<W: Write + Seek>(
+        &self,
+        writer: &mut Writer<W>,
+        ctx: Ctx,
+    ) -> Result<(), DekuError> {
+        for v in self.as_ref() {
+            v.to_writer(writer, ctx)?;
+        }
+        Ok(())
+    }
+}
+
+impl<T, Ctx> DekuWriter<Ctx> for Arc<T>
+where
+    T: DekuWriter<Ctx>,
+    Ctx: Copy,
+{
+    /// Write all `T`s to bits
+    fn to_writer<W: Write + Seek>(
+        &self,
+        writer: &mut Writer<W>,
+        ctx: Ctx,
+    ) -> Result<(), DekuError> {
+        self.as_ref().to_writer(writer, ctx)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use no_std_io::io::Cursor;
+    use rstest::rstest;
+
+    use super::*;
+    use crate::ctx::*;
+    use crate::native_endian;
+    use crate::reader::Reader;
+    #[cfg(feature = "bits")]
+    use bitvec::prelude::*;
+
+    #[rstest(input, expected,
+        case(
+            &[0xEF, 0xBE],
+            Arc::new(native_endian!(0xBEEF_u16)),
+        ),
+    )]
+    fn test_boxed(input: &[u8], expected: Arc<u16>) {
+        let mut cursor = Cursor::new(input);
+        let mut reader = Reader::new(&mut cursor);
+        let res_read = <Arc<u16>>::from_reader_with_ctx(&mut reader, ()).unwrap();
+        assert_eq!(expected, res_read);
+
+        let mut writer = Writer::new(Cursor::new(vec![]));
+        res_read.to_writer(&mut writer, ()).unwrap();
+        assert_eq!(input.to_vec(), writer.inner.into_inner());
+    }
+
+    // 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,
+        case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), Arc::from(vec![0xBBAA, 0xDDCC].into_boxed_slice()), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
+        case::normal_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), 2.into(), Arc::from(vec![0xAABB, 0xCCDD].into_boxed_slice()), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
+        case::predicate_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), (|v: &u16| *v == 0xBBAA).into(), Arc::from(vec![0xBBAA].into_boxed_slice()), bits![u8, Msb0;], &[0xcc, 0xdd], vec![0xAA, 0xBB]),
+        case::predicate_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), (|v: &u16| *v == 0xAABB).into(), Arc::from(vec![0xAABB].into_boxed_slice()), bits![u8, Msb0;], &[0xcc, 0xdd], vec![0xAA, 0xBB]),
+        case::bytes_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), BitSize(16).into(), Arc::from(vec![0xBBAA].into_boxed_slice()), bits![u8, Msb0;], &[0xcc, 0xdd], vec![0xAA, 0xBB]),
+        case::bytes_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), BitSize(16).into(), Arc::from(vec![0xAABB].into_boxed_slice()), bits![u8, Msb0;], &[0xcc, 0xdd], vec![0xAA, 0xBB]),
+    )]
+    fn test_boxed_slice_from_reader_with_ctx<Predicate: FnMut(&u16) -> bool>(
+        input: &[u8],
+        endian: Endian,
+        bit_size: Option<usize>,
+        limit: Limit<u16, Predicate>,
+        expected: Arc<[u16]>,
+        expected_rest_bits: &bitvec::slice::BitSlice<u8, bitvec::prelude::Msb0>,
+        expected_rest_bytes: &[u8],
+        expected_write: Vec<u8>,
+    ) {
+        // Unwrap here because all test cases are `Some`.
+        let bit_size = bit_size.unwrap();
+
+        let mut cursor = Cursor::new(input);
+        let mut reader = Reader::new(&mut cursor);
+        let res_read =
+            <Arc<[u16]>>::from_reader_with_ctx(&mut reader, (limit, (endian, BitSize(bit_size))))
+                .unwrap();
+        assert_eq!(expected, res_read);
+        assert_eq!(
+            reader.rest(),
+            expected_rest_bits.iter().by_vals().collect::<Vec<bool>>()
+        );
+        let mut buf = vec![];
+        cursor.read_to_end(&mut buf).unwrap();
+        assert_eq!(expected_rest_bytes, buf);
+
+        assert_eq!(input[..expected_write.len()].to_vec(), expected_write);
+
+        let mut writer = Writer::new(Cursor::new(vec![]));
+        res_read
+            .to_writer(&mut writer, (endian, BitSize(bit_size)))
+            .unwrap();
+        assert_eq!(expected_write, writer.inner.into_inner());
+
+        assert_eq!(input[..expected_write.len()].to_vec(), expected_write);
+    }
+}
diff --git a/src/impls/mod.rs b/src/impls/mod.rs
index 2b9b3ed..fd4a04a 100644
--- a/src/impls/mod.rs
+++ b/src/impls/mod.rs
@@ -7,6 +7,9 @@ mod tuple;
 mod unit;
 mod vec;
 
+#[cfg(feature = "std")]
+mod arc;
+
 #[cfg(feature = "std")]
 mod cow;
 
diff --git a/src/reader.rs b/src/reader.rs
index d9ca086..286bb41 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -21,9 +21,12 @@ pub enum ReaderRet {
     Bits(Option<BitVec<u8, Msb0>>),
 }
 
-#[derive(Debug)]
-enum Leftover {
+/// Bits or Byte stored from previous read, such as the case of id_pat
+#[derive(Debug, Clone)]
+pub enum Leftover {
+    /// byte value
     Byte(u8),
+    /// bit values
     #[cfg(feature = "bits")]
     Bits(BitVec<u8, Msb0>),
 }
@@ -32,7 +35,7 @@ enum Leftover {
 pub struct Reader<'a, R: Read + Seek> {
     inner: &'a mut R,
     /// bits stored from previous reads that didn't read to the end of a byte size
-    leftover: Option<Leftover>,
+    pub leftover: Option<Leftover>,
     /// Amount of bits read after last read, reseted before reading enum ids
     pub last_bits_read_amt: usize,
     /// Amount of bits read during the use of [read_bits](Reader::read_bits) and [read_bytes](Reader::read_bytes)
@@ -75,6 +78,8 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     pub fn seek_last_read(&mut self) -> no_std_io::io::Result<()> {
         let number = self.last_bits_read_amt as i64;
         let seek_amt = (number / 8).saturating_add((number % 8).signum());
+        #[cfg(feature = "logging")]
+        log::trace!("seek_last_read: {seek_amt:?}");
         self.seek(SeekFrom::Current(seek_amt.saturating_neg()))?;
         self.bits_read -= self.last_bits_read_amt;
         self.leftover = None;


Test patch:
diff --git a/tests/test_regression.rs b/tests/test_regression.rs
index e3a7589..4d005e7 100644
--- a/tests/test_regression.rs
+++ b/tests/test_regression.rs
@@ -385,3 +385,24 @@ fn issue_397() {
     }
     let _ = Packet::from_bytes((&[0x00, 0x01], 0));
 }
+
+#[cfg(feature = "bits")]
+#[test]
+fn issue_533() {
+    #[derive(PartialEq, Debug, DekuRead)]
+    #[deku(id_type = "u8", bits = "1")]
+    pub enum BitsAndValue {
+        #[deku(id = 0)]
+        Zero,
+        #[deku(id_pat = "_")]
+        Other(#[deku(bits = 1)] u8),
+    }
+    let input = [0b0100_0000];
+    let mut cursor = Cursor::new(input);
+    let mut reader = Reader::new(&mut cursor);
+    let v = BitsAndValue::from_reader_with_ctx(&mut reader, ()).unwrap();
+    assert_eq!(v, BitsAndValue::Zero);
+
+    let v = BitsAndValue::from_reader_with_ctx(&mut reader, ()).unwrap();
+    assert_eq!(v, BitsAndValue::Other(1));
+}