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}