dhcp_protocol/
size_constrained.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::size_of_contents::SizeOfContents;
6use serde::{Deserialize, Serialize};
7use std::ops::Deref;
8
9pub const U8_MAX_AS_USIZE: usize = u8::MAX as usize;
10
11/// AtLeast encodes a lower bound on the inner container's number of elements.
12///
13/// If `inner` is an `AtMostBytes<Vec<U>>`, it must contain at least
14/// `LOWER_BOUND_ON_NUMBER_OF_ELEMENTS` instances of `U`.
15#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
16pub struct AtLeast<const LOWER_BOUND_ON_NUMBER_OF_ELEMENTS: usize, T> {
17    inner: T,
18}
19
20// We'd have liked to make this more general, but we run into
21// "unconstrained type parameter" issues.
22impl<
23        const LOWER_BOUND_ON_NUMBER_OF_ELEMENTS: usize,
24        const UPPER_BOUND_ON_SIZE_IN_BYTES: usize,
25        T,
26    > TryFrom<AtMostBytes<UPPER_BOUND_ON_SIZE_IN_BYTES, Vec<T>>>
27    for AtLeast<
28        LOWER_BOUND_ON_NUMBER_OF_ELEMENTS,
29        AtMostBytes<UPPER_BOUND_ON_SIZE_IN_BYTES, Vec<T>>,
30    >
31{
32    type Error = (Error, Vec<T>);
33
34    fn try_from(
35        value: AtMostBytes<UPPER_BOUND_ON_SIZE_IN_BYTES, Vec<T>>,
36    ) -> Result<Self, Self::Error> {
37        if value.len() >= LOWER_BOUND_ON_NUMBER_OF_ELEMENTS {
38            Ok(AtLeast { inner: value })
39        } else {
40            let AtMostBytes { inner } = value;
41            Err((Error::SizeConstraintViolated, inner))
42        }
43    }
44}
45
46/// AtMostBytes encodes an upper bound on the inner container's size in bytes.
47///
48/// If `inner` is a Vec<U>, its size in bytes (as defined by
49/// `SizeOfContents::size_of_contents_in_bytes`) must be at most
50/// `UPPER_BOUND_ON_SIZE_IN_BYTES`.
51#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
52pub struct AtMostBytes<const UPPER_BOUND_ON_SIZE_IN_BYTES: usize, T> {
53    inner: T,
54}
55
56impl<const UPPER_BOUND_ON_SIZE_IN_BYTES: usize, T> TryFrom<Vec<T>>
57    for AtMostBytes<UPPER_BOUND_ON_SIZE_IN_BYTES, Vec<T>>
58{
59    type Error = (Error, Vec<T>);
60    fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
61        if value.size_of_contents_in_bytes() <= UPPER_BOUND_ON_SIZE_IN_BYTES {
62            Ok(AtMostBytes { inner: value })
63        } else {
64            Err((Error::SizeConstraintViolated, value))
65        }
66    }
67}
68
69impl<
70        const LOWER_BOUND_ON_NUMBER_OF_ELEMENTS: usize,
71        const UPPER_BOUND_ON_SIZE_IN_BYTES: usize,
72        T,
73    > TryFrom<Vec<T>>
74    for AtLeast<
75        LOWER_BOUND_ON_NUMBER_OF_ELEMENTS,
76        AtMostBytes<UPPER_BOUND_ON_SIZE_IN_BYTES, Vec<T>>,
77    >
78{
79    type Error = (Error, Vec<T>);
80
81    fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
82        AtLeast::try_from(AtMostBytes::try_from(value)?)
83    }
84}
85
86impl<const N: usize, T> From<AtMostBytes<N, Vec<T>>> for Vec<T> {
87    fn from(value: AtMostBytes<N, Vec<T>>) -> Self {
88        let AtMostBytes { inner } = value;
89        inner
90    }
91}
92
93impl<const M: usize, const N: usize, T> From<AtLeast<M, AtMostBytes<N, Vec<T>>>> for Vec<T> {
94    fn from(value: AtLeast<M, AtMostBytes<N, Vec<T>>>) -> Self {
95        let AtLeast { inner: AtMostBytes { inner } } = value;
96        inner
97    }
98}
99
100macro_rules! impl_for_both {
101    ($trait_name:ty, { $($tail:tt)* }) => {
102        impl<const N: usize, T> $trait_name for AtMostBytes<N, Vec<T>> {
103            $($tail)*
104        }
105
106        impl<const M: usize, const N: usize, T> $trait_name for AtLeast<M, AtMostBytes<N, Vec<T>>> {
107            $($tail)*
108        }
109    }
110}
111
112impl_for_both!(Deref, {
113    type Target = Vec<T>;
114
115    fn deref(&self) -> &Self::Target {
116        let Self { inner } = self;
117        inner
118    }
119});
120
121impl_for_both!(IntoIterator, {
122    type Item = T;
123    type IntoIter = <Vec<T> as IntoIterator>::IntoIter;
124
125    fn into_iter(self) -> Self::IntoIter {
126        let vec = Vec::from(self);
127        vec.into_iter()
128    }
129});
130
131impl_for_both!(AsRef<[T]>, {
132    fn as_ref(&self) -> &[T] {
133        self.deref().as_ref()
134    }
135});
136
137// We can delete a lot of this manual repetition once generic_const_exprs
138// is stabilized. https://github.com/rust-lang/rust/issues/76560
139
140trait IsAtMost4Bytes {}
141
142macro_rules! impl_at_most_4_bytes {
143    ($t:ty) => {
144        impl IsAtMost4Bytes for $t {}
145        static_assertions::const_assert!(
146            { std::mem::size_of::<$t>() } <= { std::mem::size_of::<u8>() * 4 }
147        );
148    };
149    ($t:ty, $($tail:ty),*) => {
150        impl_at_most_4_bytes!($t);
151        impl_at_most_4_bytes!($($tail),*);
152    }
153}
154
155impl_at_most_4_bytes!(u8, u16, u32, crate::OptionCode, std::net::Ipv4Addr);
156
157const U8_MAX_DIVIDED_BY_4: usize = u8::MAX as usize / 4;
158
159enum Num<const X: usize> {}
160
161trait GreaterThanOrEqualTo<const Y: usize> {}
162
163macro_rules! impl_ge {
164    ([ $lhs:literal ] >= $rhs:literal) => {
165        impl GreaterThanOrEqualTo<$rhs> for Num<$lhs> {}
166        ::static_assertions::const_assert!($lhs >= $rhs);
167    };
168    ([ $lhs:literal, $($ltail:literal),* ] >= $rhs:literal) => {
169        impl_ge!([ $lhs ] >= $rhs);
170
171        impl_ge!([ $($ltail),* ] >= $rhs);
172    }
173}
174
175impl_ge!([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >= 1);
176impl_ge!([2, 3, 4, 5, 6, 7, 8, 9, 10] >= 2);
177
178trait LessThanOrEqualTo<const Y: usize> {}
179
180macro_rules! impl_le {
181    ([ $lhs:literal ] <= $rhs:literal) => {
182        impl LessThanOrEqualTo<$rhs> for Num<$lhs> {}
183        ::static_assertions::const_assert!($lhs <= $rhs);
184    };
185    ([ $lhs:literal, $($ltail:literal),* ] <= $rhs:literal) => {
186        impl_le!([ $lhs ] <= $rhs);
187
188        impl_le!([ $($ltail),* ] <= $rhs);
189    }
190}
191
192impl_le!([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] <= 63);
193
194impl<T, const LOWER_BOUND_ON_NUMBER_OF_ELEMENTS: usize, const N: usize> From<[T; N]>
195    for AtLeast<LOWER_BOUND_ON_NUMBER_OF_ELEMENTS, AtMostBytes<U8_MAX_AS_USIZE, Vec<T>>>
196where
197    T: IsAtMost4Bytes,
198    Num<N>: GreaterThanOrEqualTo<LOWER_BOUND_ON_NUMBER_OF_ELEMENTS>,
199    Num<N>: LessThanOrEqualTo<{ U8_MAX_DIVIDED_BY_4 }>,
200{
201    fn from(value: [T; N]) -> Self {
202        value.into_iter().collect::<Vec<_>>().try_into().unwrap_or_else(
203            |(Error::SizeConstraintViolated, _)| {
204                panic!(
205                    "should be statically known that \
206                 {N} >= {LOWER_BOUND_ON_NUMBER_OF_ELEMENTS} and \
207                 [{type_name}; {N}] fits within {U8_MAX_AS_USIZE} bytes",
208                    type_name = std::any::type_name::<T>()
209                )
210            },
211        )
212    }
213}
214
215#[derive(Debug, PartialEq, thiserror::Error)]
216pub enum Error {
217    #[error("size constraint violated")]
218    SizeConstraintViolated,
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use std::fmt::Debug;
225
226    fn run_edge_case<T: Copy + Debug + PartialEq>(item: T, max_number_allowed: impl Into<usize>) {
227        let max_number_allowed = max_number_allowed.into();
228        let v = std::iter::repeat(item).take(max_number_allowed).collect::<Vec<_>>();
229        assert_eq!(
230            AtLeast::<1, AtMostBytes<{ U8_MAX_AS_USIZE }, _>>::try_from(v.clone()),
231            Ok(AtLeast { inner: AtMostBytes { inner: v } }),
232            "{max_number_allowed} instances of {} should fit in 255 bytes",
233            std::any::type_name::<T>(),
234        );
235
236        let v = std::iter::repeat(item).take(max_number_allowed + 1).collect::<Vec<_>>();
237        assert_eq!(
238            AtLeast::<1, AtMostBytes<{ U8_MAX_AS_USIZE }, _>>::try_from(v.clone()),
239            Err((Error::SizeConstraintViolated, v)),
240            "{max_number_allowed} instances of {} should not fit in 255 bytes",
241            std::any::type_name::<T>(),
242        );
243    }
244
245    #[test]
246    fn edge_cases() {
247        run_edge_case(1u8, u8::MAX);
248        run_edge_case(1u32, u8::MAX / 4);
249        run_edge_case(1u64, u8::MAX / 8);
250    }
251
252    #[test]
253    fn disallows_empty() {
254        assert_eq!(
255            AtLeast::<1, AtMostBytes<{ U8_MAX_AS_USIZE }, _>>::try_from(Vec::<u8>::new()),
256            Err((Error::SizeConstraintViolated, Vec::new()))
257        )
258    }
259}