Skip to main content

wire_repr/codec/
fixed.rs

1//! Fixed-width codec contracts.
2
3use super::EncodePlan;
4
5/// A codec whose encoded representation always has one fixed width.
6///
7/// [`Self::WIDTH`] must be nonzero. Every successful [`Self::plan`] must report that
8/// exact encoded length. For every such plan, [`EncodePlan::write_into`] called with
9/// an output slice of exactly [`Self::WIDTH`] bytes must write a complete representation
10/// whose decoding recovers the same semantic value supplied to [`Self::plan`]. Decoding
11/// is total for every exact-width byte pattern. A codec that violates these requirements
12/// is contract-invalid.
13///
14/// When a layout builder derives a byte-range source through
15/// `Self::Value<'static>: TryFrom<usize>`, the complete conversion and codec round trip
16/// must preserve that source value: converting the decoded planned representation back to
17/// `usize` must produce the original relative length or absolute endpoint.
18///
19/// [`Self::plan`] completes all fallible encoding work before a caller mutates an output
20/// buffer. Layout parsing establishes exact-width bounds before calling [`Self::decode`].
21pub trait FixedCodec {
22    /// Semantic value represented by an exact-width wire representation.
23    type Value<'wire>
24    where
25        Self: 'wire;
26
27    /// Error returned while preparing an encoded value.
28    type EncodeError: core::fmt::Debug;
29
30    /// Prepared fixed-width encoded bytes.
31    type Plan<'value>: EncodePlan
32    where
33        Self: 'value;
34
35    /// Number of bytes in every encoded representation.
36    const WIDTH: usize;
37
38    /// Decodes an exact-width encoded representation.
39    fn decode<'wire>(bytes: &'wire [u8]) -> Self::Value<'wire>;
40
41    /// Prepares the complete encoded representation without mutating a caller buffer.
42    fn plan<'value>(value: Self::Value<'value>) -> Result<Self::Plan<'value>, Self::EncodeError>;
43}
44
45/// Error returned when an exact-width byte value has the wrong length.
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct ExactWidthError {
48    expected: usize,
49    actual: usize,
50}
51
52impl ExactWidthError {
53    /// Creates an error for an exact-width mismatch.
54    #[must_use]
55    pub const fn new(expected: usize, actual: usize) -> Self {
56        Self { expected, actual }
57    }
58
59    /// Returns the required byte length.
60    #[must_use]
61    pub const fn expected(&self) -> usize {
62        self.expected
63    }
64
65    /// Returns the supplied byte length.
66    #[must_use]
67    pub const fn actual(&self) -> usize {
68        self.actual
69    }
70}
71
72impl core::fmt::Display for ExactWidthError {
73    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74        write!(
75            formatter,
76            "fixed codec expected {} bytes, got {}",
77            self.expected, self.actual
78        )
79    }
80}
81
82impl core::error::Error for ExactWidthError {}
83
84/// A borrowed fixed-width span of wire bytes with no content interpretation.
85///
86/// `N` must be nonzero. Using `Bytes<0>` as a [`FixedCodec`] fails during constant
87/// evaluation rather than exposing a codec that violates [`FixedCodec::WIDTH`].
88///
89/// ```compile_fail
90/// use wire_repr::{Bytes, FixedCodec};
91///
92/// let _ = <Bytes<0> as FixedCodec>::WIDTH;
93/// ```
94///
95/// `Bytes<N>` decodes to the exact borrowed wire slice and plans an equally borrowed input
96/// slice for copying at write time. It does not validate magic values, reserved bytes, or
97/// any other domain semantics; consumers own those policies.
98#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
99pub struct Bytes<const N: usize>;
100
101impl<const N: usize> FixedCodec for Bytes<N> {
102    type Value<'wire>
103        = &'wire [u8]
104    where
105        Self: 'wire;
106    type EncodeError = ExactWidthError;
107    type Plan<'value>
108        = &'value [u8]
109    where
110        Self: 'value;
111
112    const WIDTH: usize = {
113        assert!(N != 0, "Bytes<N> requires a nonzero width");
114        N
115    };
116
117    #[inline]
118    fn decode<'wire>(bytes: &'wire [u8]) -> Self::Value<'wire> {
119        const { assert!(N != 0, "Bytes<N> requires a nonzero width") };
120        bytes
121    }
122
123    #[inline]
124    fn plan<'value>(value: Self::Value<'value>) -> Result<Self::Plan<'value>, Self::EncodeError> {
125        const { assert!(N != 0, "Bytes<N> requires a nonzero width") };
126        if value.len() == N {
127            Ok(value)
128        } else {
129            Err(ExactWidthError::new(N, value.len()))
130        }
131    }
132}
133
134/// Macro-support fixed-width codec for opaque owned byte arrays.
135///
136/// This implementation detail exists for generated mapped `bytes(N)` fields. `N` must be
137/// nonzero; zero-width instantiations fail during constant evaluation just like [`Bytes`].
138///
139/// ```compile_fail
140/// use wire_repr::{__private::OwnedBytes, FixedCodec};
141///
142/// let _ = <OwnedBytes<0> as FixedCodec>::WIDTH;
143/// ```
144#[doc(hidden)]
145#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
146pub struct OwnedBytes<const N: usize>;
147
148impl<const N: usize> FixedCodec for OwnedBytes<N> {
149    type Value<'wire>
150        = [u8; N]
151    where
152        Self: 'wire;
153    type EncodeError = core::convert::Infallible;
154    type Plan<'value>
155        = [u8; N]
156    where
157        Self: 'value;
158
159    const WIDTH: usize = {
160        assert!(N != 0, "OwnedBytes<N> requires a nonzero width");
161        N
162    };
163
164    #[inline]
165    fn decode<'wire>(bytes: &'wire [u8]) -> Self::Value<'wire> {
166        const { assert!(N != 0, "OwnedBytes<N> requires a nonzero width") };
167        let mut value = [0_u8; N];
168        value.copy_from_slice(bytes);
169        value
170    }
171
172    #[inline]
173    fn plan<'value>(value: Self::Value<'value>) -> Result<Self::Plan<'value>, Self::EncodeError> {
174        const { assert!(N != 0, "OwnedBytes<N> requires a nonzero width") };
175        Ok(value)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::{FixedCodec, OwnedBytes};
182    use crate::codec::EncodePlan;
183
184    #[test]
185    fn owned_bytes_decode_copies_exact_input() {
186        let mut source = [1, 2, 3, 4];
187        let decoded = <OwnedBytes<4> as FixedCodec>::decode(&source);
188
189        source[0] = 9;
190        assert_eq!(decoded, [1, 2, 3, 4]);
191    }
192
193    #[test]
194    fn owned_bytes_plan_is_infallible_and_writes_exact_bytes() {
195        let plan = <OwnedBytes<4> as FixedCodec>::plan([1, 2, 3, 4]).unwrap();
196        let mut output = [0; 4];
197
198        assert_eq!(plan.encoded_len(), 4);
199        plan.write_into(&mut output);
200        assert_eq!(output, [1, 2, 3, 4]);
201    }
202
203    #[test]
204    fn owned_bytes_width_is_nonzero() {
205        assert_eq!(<OwnedBytes<1> as FixedCodec>::WIDTH, 1);
206    }
207}