sccp_protocol/message/
bounded.rs1use std::error::Error;
8use std::fmt;
9use std::ops::Deref;
10
11#[derive(Clone, Default, Eq, Hash, PartialEq)]
16pub struct BoundedBytes<const MAX: usize>(Box<[u8]>);
17
18impl<const MAX: usize> BoundedBytes<MAX> {
19 pub fn new(bytes: impl Into<Box<[u8]>>) -> Result<Self, BoundedBytesError> {
24 let bytes = bytes.into();
25 if bytes.len() > MAX {
26 return Err(BoundedBytesError {
27 maximum: MAX,
28 actual: bytes.len(),
29 });
30 }
31 Ok(Self(bytes))
32 }
33
34 pub const fn maximum_len() -> usize {
35 MAX
36 }
37
38 pub fn as_bytes(&self) -> &[u8] {
39 &self.0
40 }
41
42 pub fn len(&self) -> usize {
43 self.0.len()
44 }
45
46 pub fn is_empty(&self) -> bool {
47 self.0.is_empty()
48 }
49
50 pub fn into_boxed_slice(self) -> Box<[u8]> {
51 self.0
52 }
53}
54
55impl<const MAX: usize> AsRef<[u8]> for BoundedBytes<MAX> {
56 fn as_ref(&self) -> &[u8] {
57 self.as_bytes()
58 }
59}
60
61impl<const MAX: usize> Deref for BoundedBytes<MAX> {
62 type Target = [u8];
63
64 fn deref(&self) -> &Self::Target {
65 self.as_bytes()
66 }
67}
68
69impl<const MAX: usize> TryFrom<Vec<u8>> for BoundedBytes<MAX> {
70 type Error = BoundedBytesError;
71
72 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
73 Self::new(bytes)
74 }
75}
76
77impl<const MAX: usize> TryFrom<Box<[u8]>> for BoundedBytes<MAX> {
78 type Error = BoundedBytesError;
79
80 fn try_from(bytes: Box<[u8]>) -> Result<Self, Self::Error> {
81 Self::new(bytes)
82 }
83}
84
85impl<const MAX: usize> TryFrom<&[u8]> for BoundedBytes<MAX> {
86 type Error = BoundedBytesError;
87
88 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
89 Self::new(bytes)
90 }
91}
92
93impl<const MAX: usize> From<BoundedBytes<MAX>> for Box<[u8]> {
94 fn from(bytes: BoundedBytes<MAX>) -> Self {
95 bytes.into_boxed_slice()
96 }
97}
98
99impl<const MAX: usize> From<BoundedBytes<MAX>> for Vec<u8> {
100 fn from(bytes: BoundedBytes<MAX>) -> Self {
101 bytes.into_boxed_slice().into_vec()
102 }
103}
104
105impl<const MAX: usize> fmt::Debug for BoundedBytes<MAX> {
106 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107 formatter
108 .debug_struct("BoundedBytes")
109 .field("len", &self.len())
110 .field("maximum", &MAX)
111 .finish()
112 }
113}
114
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub struct BoundedBytesError {
118 pub maximum: usize,
119 pub actual: usize,
120}
121
122impl fmt::Display for BoundedBytesError {
123 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
124 write!(
125 formatter,
126 "payload contains {} bytes, exceeding the {}-byte bound",
127 self.actual, self.maximum
128 )
129 }
130}
131
132impl Error for BoundedBytesError {}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn accepts_every_length_through_the_bound() {
140 for length in 0..=8 {
141 let bytes = BoundedBytes::<8>::try_from(vec![0xa5; length]).unwrap();
142 assert_eq!(bytes.len(), length);
143 assert!(bytes.iter().all(|byte| *byte == 0xa5));
144 }
145 }
146
147 #[test]
148 fn rejects_the_first_oversized_value_without_retaining_it() {
149 assert_eq!(
150 BoundedBytes::<8>::try_from(vec![0xa5; 9]).unwrap_err(),
151 BoundedBytesError {
152 maximum: 8,
153 actual: 9,
154 }
155 );
156 }
157
158 #[test]
159 fn debug_reports_shape_without_contents() {
160 let bytes = BoundedBytes::<8>::try_from(b"secret".as_slice()).unwrap();
161 let rendered = format!("{bytes:?}");
162 assert!(rendered.contains("len: 6"));
163 assert!(!rendered.contains("secret"));
164 }
165}