Repository: sharksforarms/deku (rust)

Issue:
Support for fallible use cases for mutating and serializing fields
I was wondering if there was API support for either of the following use cases, or an equivalent thereof:

```rust
#[derive(Debug, Default, PartialEq, DekuWrite, DekuRead)]
#[deku(endian = "big")]
struct Test {
  #[deku(bits = 4)]
  field: u8,
}

fn fallible_case_1() -> anyhow::Result<()> {
  let mut x = Test::default();

  // Generated methods?
  x.set_field(17)?; // Fails
  x.set_field(15)?; // Ok

  Ok(())
}

fn fallible_case_2() -> anyhow::Result<()> {
  let mut x = Test::default();
  x.field = 17;
  x.to_bytes()?; // Does not error, alarmingly
  x.to_bytes_checked()?; // Errors because 17 >= 16; is there something like this?
  Ok(())
}
```

Pull request description:
Add failure when writing less bits then set
* Check the amonut of bits that are expected to write, and make sure that containers that contains more bits that aren't going to be written is treated as an error.
* Specialize DekuWriter for u8, as this does not required bit_size calculations

Closes #457 

Patch:
diff --git a/src/impls/primitive.rs b/src/impls/primitive.rs
index 257f02e..17184c8 100644
--- a/src/impls/primitive.rs
+++ b/src/impls/primitive.rs
@@ -33,6 +33,7 @@ trait DekuRead<'a, Ctx = ()> {
 
 // specialize u8 for ByteSize
 impl DekuRead<'_, (Endian, ByteSize)> for u8 {
+    /// Ignore endian and byte_size, as this is a `u8`
     #[inline]
     fn read(
         input: &BitSlice<u8, Msb0>,
@@ -47,6 +48,7 @@ impl DekuRead<'_, (Endian, ByteSize)> for u8 {
 }
 
 impl DekuReader<'_, (Endian, ByteSize)> for u8 {
+    /// Ignore endian, as this is a `u8`
     #[inline(always)]
     fn from_reader_with_ctx<R: Read>(
         reader: &mut Reader<R>,
@@ -69,6 +71,59 @@ impl DekuReader<'_, (Endian, ByteSize)> for u8 {
     }
 }
 
+impl DekuWriter<(Endian, BitSize)> for u8 {
+    /// Ignore endian, as this is a `u8`
+    #[inline(always)]
+    fn to_writer<W: Write>(
+        &self,
+        writer: &mut Writer<W>,
+        (_, size): (Endian, BitSize),
+    ) -> Result<(), DekuError> {
+        let input = self.to_le_bytes();
+
+        let bit_size: usize = size.0;
+
+        let input_bits = input.view_bits::<Msb0>();
+
+        if bit_size > input_bits.len() {
+            return Err(DekuError::InvalidParam(Cow::from(format!(
+                "bit size {} is larger than input {}",
+                bit_size,
+                input_bits.len()
+            ))));
+        }
+
+        // Check for extra bits before sending into writer
+        const MAX_TYPE_BITS: usize = BitSize::of::<u8>().0;
+        if let Some(first) = input_bits.first_one() {
+            let max = MAX_TYPE_BITS - bit_size;
+            if max > first {
+                return Err(DekuError::InvalidParam(Cow::from(format!(
+                    "bit size {} of input is larger than bit requested size {}",
+                    MAX_TYPE_BITS - first,
+                    bit_size,
+                ))));
+            }
+        }
+        writer.write_bits(&input_bits[input_bits.len() - bit_size..])?;
+        Ok(())
+    }
+}
+
+impl DekuWriter<(Endian, ByteSize)> for u8 {
+    /// Ignore endian and byte_size, as this is a `u8`
+    #[inline(always)]
+    fn to_writer<W: Write>(
+        &self,
+        writer: &mut Writer<W>,
+        (_, _): (Endian, ByteSize),
+    ) -> Result<(), DekuError> {
+        let input = self.to_le_bytes();
+        writer.write_bytes(&input)?;
+        Ok(())
+    }
+}
+
 macro_rules! ImplDekuReadBits {
     ($typ:ty, $inner:ty) => {
         impl DekuRead<'_, (Endian, BitSize)> for $typ {
@@ -433,6 +488,19 @@ macro_rules! ImplDekuWrite {
                 }
 
                 if matches!(endian, Endian::Little) {
+                    // Check if this is a value that will fit inside the required bits, if
+                    // not, throw an error
+                    let input_bits_lsb = input.view_bits::<Lsb0>();
+                    if let Some(last) = input_bits_lsb.last_one() {
+                        let last = last + 1;
+                        let max = bit_size;
+                        if last > max {
+                            return Err(DekuError::InvalidParam(Cow::from(format!(
+                                "bit size {last} of input is larger than bit requested size {bit_size}",
+                            ))));
+                        }
+                    }
+
                     // Example read 10 bits u32 [0xAB, 0b11_000000]
                     // => [10101011, 00000011, 00000000, 00000000]
                     let mut remaining_bits = bit_size;
@@ -446,6 +514,18 @@ macro_rules! ImplDekuWrite {
                         remaining_bits -= chunk.len();
                     }
                 } else {
+                    const MAX_TYPE_BITS: usize = BitSize::of::<$typ>().0;
+                    // Check for extra bits before sending into writer
+                    if let Some(first) = input_bits.first_one() {
+                        let max = (MAX_TYPE_BITS - bit_size);
+                        if max > first {
+                            return Err(DekuError::InvalidParam(Cow::from(format!(
+                                "bit size {} of input is larger than bit requested size {}",
+                                MAX_TYPE_BITS - first,
+                                bit_size,
+                            ))));
+                        }
+                    }
                     // Example read 10 bits u32 [0xAB, 0b11_000000]
                     // => [00000000, 00000000, 00000010, 10101111]
                     writer.write_bits(&input_bits[input_bits.len() - bit_size..])?;
@@ -484,7 +564,11 @@ macro_rules! ImplDekuWrite {
                 Ok(())
             }
         }
+    };
+}
 
+macro_rules! ImplDekuWriteOnlyEndian {
+    ($typ:ty) => {
         impl DekuWriter<Endian> for $typ {
             #[inline(always)]
             fn to_writer<W: Write>(
@@ -538,6 +622,7 @@ macro_rules! ForwardDekuWrite {
 macro_rules! ImplDekuTraitsBytes {
     ($typ:ty) => {
         ImplDekuReadBytes!($typ, $typ);
+        ImplDekuWrite!($typ);
     };
     ($typ:ty, $inner:ty) => {
         ImplDekuReadBytes!($typ, $inner);
@@ -549,7 +634,7 @@ macro_rules! ImplDekuTraits {
         ImplDekuReadBits!($typ, $typ);
         ForwardDekuRead!($typ);
 
-        ImplDekuWrite!($typ);
+        ImplDekuWriteOnlyEndian!($typ);
         ForwardDekuWrite!($typ);
     };
     ($typ:ty, $inner:ty) => {
@@ -557,6 +642,7 @@ macro_rules! ImplDekuTraits {
         ForwardDekuRead!($typ);
 
         ImplDekuWrite!($typ);
+        ImplDekuWriteOnlyEndian!($typ);
         ForwardDekuWrite!($typ);
     };
 }
@@ -567,6 +653,7 @@ macro_rules! ImplDekuTraitsSignExtend {
         ForwardDekuRead!($typ);
 
         ImplDekuWrite!($typ);
+        ImplDekuWriteOnlyEndian!($typ);
         ForwardDekuWrite!($typ);
     };
 }
@@ -792,10 +879,11 @@ mod tests {
     #[rstest(input, endian, bit_size, expected, expected_leftover,
         case::normal_le(0xDDCC_BBAA, Endian::Little, None, vec![0xAA, 0xBB, 0xCC, 0xDD], vec![]),
         case::normal_be(0xDDCC_BBAA, Endian::Big, None, vec![0xDD, 0xCC, 0xBB, 0xAA], vec![]),
-        case::bit_size_le_smaller(0x03AB, Endian::Little, Some(10), vec![0xAB], vec![true, true]),
         case::bit_size_be_smaller(0x03AB, Endian::Big, Some(10), vec![0b11_1010_10], vec![true, true]),
         #[should_panic(expected = "InvalidParam(\"bit size 100 is larger than input 32\")")]
         case::bit_size_le_bigger(0x03AB, Endian::Little, Some(100), vec![0xAB, 0b11_000000], vec![true, true]),
+        #[should_panic(expected = "InvalidParam(\"bit size 10 of input is larger than bit requested size 5\")")]
+        case::bit_size_larger(0x03AB, Endian::Little, Some(5), vec![], vec![]),
     )]
     fn test_bit_writer(
         input: u32,
@@ -818,7 +906,6 @@ mod tests {
     #[rstest(input, endian, byte_size, expected,
         case::normal_le(0xDDCC_BBAA, Endian::Little, None, vec![0xAA, 0xBB, 0xCC, 0xDD]),
         case::normal_be(0xDDCC_BBAA, Endian::Big, None, vec![0xDD, 0xCC, 0xBB, 0xAA]),
-        case::byte_size_le_smaller(0x00ffABAA, Endian::Little, Some(2), vec![0xaa, 0xab]),
         case::byte_size_be_smaller(0x00ffABAA, Endian::Big, Some(2), vec![0xab, 0xaa]),
         #[should_panic(expected = "InvalidParam(\"byte size 10 is larger then input 4\")")]
         case::byte_size_le_bigger(0x03AB, Endian::Little, Some(10), vec![0xAB, 0b11_000000]),
diff --git a/src/lib.rs b/src/lib.rs
index a5f886f..b2fec92 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -515,6 +515,33 @@ pub trait DekuContainerWrite: DekuWriter<()> {
         Ok(out_buf)
     }
 
+    /// Write struct/enum to a given slice
+    ///
+    /// ```rust
+    /// # use deku::prelude::*;
+    /// #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
+    /// #[deku(endian = "little")]
+    /// struct S {
+    ///    data_00: u8,
+    ///    data_01: u16,
+    ///    data_02: u32,
+    /// }
+    ///
+    /// let mut buf = [0; 7];
+    /// let s = S { data_00: 0x01, data_01: 0x02, data_02: 0x03 };
+    /// let amt_written = s.to_slice(&mut buf).unwrap();
+    /// assert_eq!(buf, [0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00]);
+    /// assert_eq!(amt_written, 7)
+    /// ````
+    #[inline(always)]
+    fn to_slice(&self, buf: &mut [u8]) -> Result<usize, DekuError> {
+        let mut __deku_writer = Writer::new(buf);
+        DekuWriter::to_writer(self, &mut __deku_writer, ())?;
+        __deku_writer.finalize()?;
+
+        Ok(__deku_writer.bits_written / 8)
+    }
+
     /// Write struct/enum to BitVec
     ///
     /// ```rust


Test patch:
diff --git a/tests/test_alloc.rs b/tests/test_alloc.rs
index 8da0463..86072b4 100644
--- a/tests/test_alloc.rs
+++ b/tests/test_alloc.rs
@@ -78,4 +78,21 @@ mod tests {
             (2, 1, 2)
         );
     }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_to_slice() {
+        let input = hex!("aa_bbbb_cc_0102_dd_ffffff_aa_0100ff");
+        let t = TestDeku::from_reader((&mut input.as_slice(), 0)).unwrap().1;
+
+        let mut out = [0x00; 100];
+
+        assert_eq!(
+            count_alloc(|| {
+                t.to_slice(&mut out).unwrap();
+            })
+            .0,
+            (1, 0, 1)
+        );
+    }
 }
diff --git a/tests/test_bit_container_size.rs b/tests/test_bit_container_size.rs
new file mode 100644
index 0000000..35c5f5e
--- /dev/null
+++ b/tests/test_bit_container_size.rs
@@ -0,0 +1,37 @@
+use deku::prelude::*;
+use rstest::*;
+
+#[derive(Debug, Default, PartialEq, DekuWrite, DekuRead)]
+#[deku(endian = "big")]
+struct Test {
+    #[deku(bits = 4)]
+    field_u8_be: u8,
+    #[deku(bits = 4)]
+    field_be: u16,
+    #[deku(endian = "little", bits = 12)]
+    field_le: u32,
+    #[deku(endian = "big", bits = 9)]
+    field_u32_be: u32,
+}
+
+#[rstest(input,
+    #[should_panic(
+        expected = "InvalidParam(\"bit size 5 of input is larger than bit requested size 4\")"
+    )]
+    case::field_u8_be( Test { field_u8_be: 0b11111, ..Test::default()}),
+    #[should_panic(
+        expected = "InvalidParam(\"bit size 5 of input is larger than bit requested size 4\")"
+    )]
+    case::field_be( Test { field_be: 0b11111, ..Test::default()}),
+    #[should_panic(
+        expected = "InvalidParam(\"bit size 13 of input is larger than bit requested size 12\")"
+    )]
+    case::field_le( Test { field_le: 0b1111111111111, ..Test::default()}),
+    #[should_panic(
+        expected = "InvalidParam(\"bit size 13 of input is larger than bit requested size 9\")"
+    )]
+    case::field_u32_be( Test { field_u32_be: 0b1111111111111, ..Test::default()}),
+)]
+fn test_bit_container_to_big(input: Test) {
+    input.to_bytes().unwrap();
+}
diff --git a/tests/test_to_slice.rs b/tests/test_to_slice.rs
new file mode 100644
index 0000000..78c6c11
--- /dev/null
+++ b/tests/test_to_slice.rs
@@ -0,0 +1,47 @@
+use deku::prelude::*;
+
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+pub struct A {
+    #[deku(bytes = "3", endian = "big")]
+    address: u32,
+}
+
+#[test]
+fn test_to_slice_bytes() {
+    let bytes = [0x11, 0x22, 0x33];
+    let a = A::from_bytes((&bytes, 0)).unwrap().1;
+    let new_bytes = a.to_bytes().unwrap();
+    assert_eq!(bytes, &*new_bytes);
+
+    let bytes = [0x11, 0x22, 0x33];
+    let mut out = [0x00; 3];
+    let amt_written = a.to_slice(&mut out).unwrap();
+    assert_eq!(bytes, out.as_slice());
+    assert_eq!(amt_written, 3);
+}
+
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+pub struct B {
+    #[deku(bits = "3", endian = "little")]
+    inner: u8,
+}
+
+#[test]
+fn test_to_slice_bits() {
+    let b = B { inner: 0b111 };
+
+    let mut out = [0x00; 1];
+    let amt_written = b.to_slice(&mut out).unwrap();
+    assert_eq!([0b1110_0000], out.as_slice());
+    assert_eq!(amt_written, 1);
+}
+
+#[test]
+#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: Io(WriteZero)")]
+fn test_to_slice_panic_writer_failure() {
+    let bytes = [0x11, 0x22, 0x33];
+    let a = A::from_bytes((&bytes, 0)).unwrap().1;
+
+    let mut out = [0x00; 2];
+    a.to_slice(&mut out).unwrap();
+}