Skip to main content

wire_codec/
packed.rs

1//! Packed struct encoding.
2//!
3//! The [`packed_struct!`][`crate::packed_struct`] macro declares a struct
4//! whose fields are packed, most-significant-bit first, into a fixed number
5//! of bytes, and implements [`Encode`][`crate::Encode`] and
6//! [`Decode`][`crate::Decode`] for it. Each field names its width in bits:
7//!
8//! ```
9//! use wire_codec::{packed_struct, Decode, Encode, ReadBuf, WriteBuf};
10//!
11//! packed_struct! {
12//!     /// An IPv4 header's first 32 bits.
13//!     #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14//!     pub struct Ipv4Head {
15//!         pub version: u8 = 4,
16//!         pub ihl: u8 = 4,
17//!         pub dscp: u8 = 6,
18//!         pub ecn: u8 = 2,
19//!         pub total_length: u16 = 16,
20//!     }
21//! }
22//!
23//! assert_eq!(Ipv4Head::BITS, 32);
24//! assert_eq!(Ipv4Head::SIZE, 4);
25//!
26//! let head = Ipv4Head { version: 4, ihl: 5, dscp: 0, ecn: 0, total_length: 84 };
27//! let mut out = [0u8; 4];
28//! head.encode(&mut WriteBuf::new(&mut out)).unwrap();
29//! assert_eq!(out, [0x45, 0x00, 0x00, 0x54]);
30//!
31//! let decoded = Ipv4Head::decode(&mut ReadBuf::new(&out)).unwrap();
32//! assert_eq!(decoded, head);
33//! ```
34//!
35//! # Field types
36//!
37//! Any type implementing [`PackedField`] can be a field: all unsigned and
38//! signed integers up to 64 bits (signed fields use two's complement in their
39//! declared width) and `bool` (one bit). Implement [`PackedField`] on your
40//! own enums to pack them directly.
41//!
42//! # Rules
43//!
44//! - Field widths are checked at compile time: a width of `0`, or wider than
45//!   the field type (for example `u8 = 9`), is a compile error.
46//! - Encoding a value that does not fit its width returns
47//!   [`Error::BitOverflow`] and writes nothing.
48//! - When the total width is not a multiple of eight, the last byte is padded
49//!   with zero bits. Decoding rejects non-zero padding with
50//!   [`Error::InvalidEncoding`], so every value has exactly one encoding.
51//!   Declare reserved bits as an explicit field if a protocol lets them vary.
52//! - Decoding is atomic: on error the cursor does not move.
53
54use crate::error::{Error, Result};
55
56/// A type that can be stored as a fixed-width bit field in a
57/// [`packed_struct!`][`crate::packed_struct`].
58///
59/// # Example
60///
61/// ```
62/// use wire_codec::packed::PackedField;
63/// use wire_codec::{packed_struct, Decode, Error, ReadBuf, Result};
64///
65/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
66/// enum Kind { Data, Ack, Nak }
67///
68/// impl PackedField for Kind {
69///     const MAX_BITS: u32 = 2;
70///     fn to_bits(self, _bits: u32) -> Result<u64> {
71///         Ok(self as u64)
72///     }
73///     fn from_bits(raw: u64, _bits: u32) -> Result<Self> {
74///         match raw {
75///             0 => Ok(Kind::Data),
76///             1 => Ok(Kind::Ack),
77///             2 => Ok(Kind::Nak),
78///             _ => Err(Error::InvalidEncoding),
79///         }
80///     }
81/// }
82///
83/// packed_struct! {
84///     #[derive(Debug, PartialEq, Eq)]
85///     struct Header {
86///         kind: Kind = 2,
87///         seq: u8 = 6,
88///     }
89/// }
90///
91/// let header = Header::decode(&mut ReadBuf::new(&[0b01_000011])).unwrap();
92/// assert_eq!(header, Header { kind: Kind::Ack, seq: 3 });
93/// ```
94pub trait PackedField: Sized + Copy {
95    /// The widest field, in bits, this type can be packed into.
96    const MAX_BITS: u32;
97
98    /// Convert `self` into the raw contents of a `bits`-wide field.
99    ///
100    /// # Errors
101    ///
102    /// Returns [`Error::BitOverflow`] if the value does not fit in `bits`
103    /// bits.
104    fn to_bits(self, bits: u32) -> Result<u64>;
105
106    /// Rebuild a value from the raw contents of a `bits`-wide field.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error (typically [`Error::InvalidEncoding`]) if `raw` is
111    /// not a valid representation.
112    fn from_bits(raw: u64, bits: u32) -> Result<Self>;
113}
114
115macro_rules! unsigned_fields {
116    ($($ty:ty),*) => {$(
117        impl PackedField for $ty {
118            const MAX_BITS: u32 = <$ty>::BITS;
119
120            #[inline]
121            fn to_bits(self, bits: u32) -> Result<u64> {
122                let raw = u64::from(self);
123                if bits == 0 || bits > Self::MAX_BITS || (bits < 64 && raw >> bits != 0) {
124                    return Err(Error::BitOverflow);
125                }
126                Ok(raw)
127            }
128
129            #[inline]
130            fn from_bits(raw: u64, _bits: u32) -> Result<Self> {
131                <$ty>::try_from(raw).map_err(|_| Error::BitOverflow)
132            }
133        }
134    )*};
135}
136
137macro_rules! signed_fields {
138    ($($ty:ty),*) => {$(
139        impl PackedField for $ty {
140            const MAX_BITS: u32 = <$ty>::BITS;
141
142            #[inline]
143            fn to_bits(self, bits: u32) -> Result<u64> {
144                if bits > Self::MAX_BITS {
145                    return Err(Error::BitOverflow);
146                }
147                crate::bitfield::signed_to_bits(i64::from(self), bits).ok_or(Error::BitOverflow)
148            }
149
150            #[inline]
151            fn from_bits(raw: u64, bits: u32) -> Result<Self> {
152                if bits == 0 || bits > Self::MAX_BITS {
153                    return Err(Error::BitOverflow);
154                }
155                let value = crate::bitfield::sign_extend(raw, bits);
156                <$ty>::try_from(value).map_err(|_| Error::BitOverflow)
157            }
158        }
159    )*};
160}
161
162unsigned_fields!(u8, u16, u32, u64);
163signed_fields!(i8, i16, i32, i64);
164
165impl PackedField for bool {
166    const MAX_BITS: u32 = 1;
167
168    #[inline]
169    fn to_bits(self, _bits: u32) -> Result<u64> {
170        Ok(u64::from(self))
171    }
172
173    #[inline]
174    fn from_bits(raw: u64, _bits: u32) -> Result<Self> {
175        match raw {
176            0 => Ok(false),
177            1 => Ok(true),
178            _ => Err(Error::InvalidEncoding),
179        }
180    }
181}
182
183/// Declare a bit-packed struct and implement [`Encode`][`crate::Encode`] and
184/// [`Decode`][`crate::Decode`] for it.
185///
186/// Syntax: an ordinary struct definition in which every field is followed by
187/// `= <width in bits>`. Attributes, doc comments, and visibility on the
188/// struct and on each field are passed through. Fields are packed in
189/// declaration order, most-significant bit first, using
190/// [`BitWriter`][`crate::BitWriter`] / [`BitReader`][`crate::BitReader`].
191///
192/// The macro adds two associated constants, `BITS` (total field width) and
193/// `SIZE` (encoded bytes), and two convenience methods, `pack` and
194/// `unpack`, that work on a `[u8; SIZE]` array. See the
195/// [`packed`][`crate::packed`] module for the full rules.
196///
197/// # Example
198///
199/// ```
200/// use wire_codec::packed_struct;
201///
202/// packed_struct! {
203///     #[derive(Debug, Clone, Copy, PartialEq, Eq)]
204///     pub struct Sample {
205///         pub valid: bool = 1,
206///         pub channel: u8 = 3,
207///         pub delta: i16 = 12,
208///     }
209/// }
210///
211/// let sample = Sample { valid: true, channel: 5, delta: -2 };
212/// let bytes = sample.pack().unwrap();
213/// assert_eq!(bytes, [0b1_101_1111, 0b1111_1110]);
214/// assert_eq!(Sample::unpack(&bytes).unwrap(), sample);
215/// ```
216///
217/// A field wider than its type does not compile:
218///
219/// ```compile_fail
220/// wire_codec::packed_struct! {
221///     struct Bad { x: u8 = 9 }
222/// }
223/// ```
224#[macro_export]
225macro_rules! packed_struct {
226    (
227        $(#[$meta:meta])*
228        $vis:vis struct $name:ident {
229            $(
230                $(#[$fmeta:meta])*
231                $fvis:vis $field:ident : $ty:ty = $bits:expr
232            ),+ $(,)?
233        }
234    ) => {
235        $(#[$meta])*
236        $vis struct $name {
237            $(
238                $(#[$fmeta])*
239                $fvis $field: $ty,
240            )+
241        }
242
243        const _: () = {
244            $(
245                assert!(
246                    ($bits) >= 1 && ($bits) <= <$ty as $crate::packed::PackedField>::MAX_BITS,
247                    concat!(
248                        "packed_struct!: field `",
249                        stringify!($field),
250                        "` has a width of zero or wider than its type"
251                    )
252                );
253            )+
254        };
255
256        impl $name {
257            /// Total width of the packed fields, in bits.
258            pub const BITS: u32 = 0 $(+ ($bits))+;
259
260            /// Encoded size in bytes: [`Self::BITS`] rounded up to a whole byte.
261            pub const SIZE: usize = ($name::BITS as usize).div_ceil(8);
262
263            /// Pack `self` into a byte array.
264            ///
265            /// # Errors
266            ///
267            /// Returns `Error::BitOverflow` if a field value does not fit in
268            /// its declared width.
269            #[allow(dead_code)]
270            pub fn pack(&self) -> $crate::Result<[u8; $name::SIZE]> {
271                let mut bytes = [0u8; $name::SIZE];
272                let mut writer = $crate::BitWriter::new(&mut bytes);
273                $(
274                    writer.write_bits(
275                        $crate::packed::PackedField::to_bits(self.$field, $bits)?,
276                        $bits,
277                    )?;
278                )+
279                let _written = writer.finish();
280                Ok(bytes)
281            }
282
283            /// Unpack a value from a byte array.
284            ///
285            /// # Errors
286            ///
287            /// Returns `Error::InvalidEncoding` if the padding bits are not
288            /// zero or a field rejects its raw value.
289            #[allow(dead_code)]
290            pub fn unpack(bytes: &[u8; $name::SIZE]) -> $crate::Result<Self> {
291                let mut reader = $crate::BitReader::new(bytes);
292                $(
293                    let $field = <$ty as $crate::packed::PackedField>::from_bits(
294                        reader.read_bits($bits)?,
295                        $bits,
296                    )?;
297                )+
298                let padding = reader.bits_remaining();
299                if padding > 0 && reader.read_bits(padding as u32)? != 0 {
300                    return Err($crate::Error::InvalidEncoding);
301                }
302                Ok(Self { $($field),+ })
303            }
304        }
305
306        impl $crate::Encode for $name {
307            #[inline]
308            fn encoded_size(&self) -> usize {
309                $name::SIZE
310            }
311
312            fn encode(&self, buf: &mut $crate::WriteBuf<'_>) -> $crate::Result<()> {
313                buf.write_bytes(&self.pack()?)
314            }
315        }
316
317        impl<'de> $crate::Decode<'de> for $name {
318            fn decode(buf: &mut $crate::ReadBuf<'de>) -> $crate::Result<Self> {
319                let mut probe = buf.clone();
320                let value = $name::unpack(probe.read_array_ref::<{ $name::SIZE }>()?)?;
321                *buf = probe;
322                Ok(value)
323            }
324        }
325    };
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use crate::{Decode, Encode, ReadBuf, WriteBuf};
332
333    crate::packed_struct! {
334        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
335        struct Mixed {
336            flag: bool = 1,
337            small: u8 = 3,
338            signed: i8 = 4,
339            wide: u32 = 20,
340            neg: i64 = 64,
341            tail: u16 = 3,
342        }
343    }
344
345    #[test]
346    fn layout_constants() {
347        assert_eq!(Mixed::BITS, 1 + 3 + 4 + 20 + 64 + 3);
348        assert_eq!(Mixed::SIZE, 12);
349    }
350
351    #[test]
352    fn round_trip_and_padding() {
353        let value = Mixed {
354            flag: true,
355            small: 7,
356            signed: -8,
357            wide: 0xF_FFFF,
358            neg: i64::MIN,
359            tail: 5,
360        };
361        let mut out = [0xEEu8; 16];
362        let mut buf = WriteBuf::new(&mut out);
363        value.encode(&mut buf).unwrap();
364        assert_eq!(buf.position(), Mixed::SIZE);
365        assert_eq!(value.encoded_size(), Mixed::SIZE);
366        // 95 bits: the final bit of byte 12 is zero padding.
367        assert_eq!(out[11] & 0b1, 0);
368
369        let mut r = ReadBuf::new(&out[..Mixed::SIZE]);
370        assert_eq!(Mixed::decode(&mut r).unwrap(), value);
371        assert!(r.is_empty());
372
373        let mut bad = out;
374        bad[11] |= 1;
375        let mut r = ReadBuf::new(&bad[..Mixed::SIZE]);
376        assert_eq!(Mixed::decode(&mut r), Err(Error::InvalidEncoding));
377        assert_eq!(r.position(), 0, "decode is atomic");
378    }
379
380    #[test]
381    fn out_of_range_values_are_rejected() {
382        let mut value = Mixed {
383            flag: false,
384            small: 8,
385            signed: 0,
386            wide: 0,
387            neg: 0,
388            tail: 0,
389        };
390        let mut out = [0u8; 16];
391        let mut buf = WriteBuf::new(&mut out);
392        assert_eq!(value.encode(&mut buf), Err(Error::BitOverflow));
393        value.small = 0;
394        value.signed = 8;
395        assert_eq!(value.encode(&mut buf), Err(Error::BitOverflow));
396        value.signed = -9;
397        assert_eq!(value.pack(), Err(Error::BitOverflow));
398        assert_eq!(buf.position(), 0);
399    }
400
401    #[test]
402    fn short_input_is_eof() {
403        let mut r = ReadBuf::new(&[0u8; 11]);
404        assert_eq!(Mixed::decode(&mut r), Err(Error::UnexpectedEof));
405    }
406
407    #[test]
408    fn primitive_field_conversions() {
409        assert_eq!(u8::from_bits(256, 8), Err(Error::BitOverflow));
410        assert_eq!(u64::to_bits(u64::MAX, 64), Ok(u64::MAX));
411        assert_eq!(u16::to_bits(1, 0), Err(Error::BitOverflow));
412        assert_eq!(u16::to_bits(1, 17), Err(Error::BitOverflow));
413        assert_eq!(i8::to_bits(-1, 9), Err(Error::BitOverflow));
414        assert_eq!(i8::from_bits(0xFF, 0), Err(Error::BitOverflow));
415        assert_eq!(i8::from_bits(0xFF, 9), Err(Error::BitOverflow));
416        assert_eq!(i16::from_bits(0b111, 3), Ok(-1));
417        assert_eq!(i32::to_bits(-1, 32), Ok(0xFFFF_FFFF));
418        assert_eq!(i64::from_bits(1, 64), Ok(1));
419        assert_eq!(bool::from_bits(2, 1), Err(Error::InvalidEncoding));
420        assert_eq!(bool::to_bits(true, 1), Ok(1));
421        assert_eq!(u32::from_bits(5, 3), Ok(5));
422    }
423}