1use core::fmt;
7
8pub const MAX_COMPACT_SIZE: u64 = 0x0200_0000;
11
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
14#[non_exhaustive]
15pub enum EncodingError {
16 #[error("invalid hex")]
18 InvalidHex,
19 #[error("{what} must be {expected} bytes, got {actual}")]
21 InvalidLength {
22 what: &'static str,
24 expected: usize,
26 actual: usize,
28 },
29 #[error("unexpected end of data")]
31 UnexpectedEnd,
32 #[error("non-canonical compactSize: {value} encoded in {len} bytes")]
34 NonCanonicalCompactSize {
35 value: u64,
37 len: usize,
39 },
40 #[error("CompactSize too large")]
42 CompactSizeTooLarge,
43 #[error("compactSize > 32 bits not supported")]
45 CompactSize64,
46}
47
48pub fn decode_hex(s: &str) -> Result<Vec<u8>, EncodingError> {
50 let digits = s.strip_prefix("0x").unwrap_or(s);
51 hex::decode(digits).map_err(|_| EncodingError::InvalidHex)
52}
53
54pub fn decode_hex_array<const N: usize>(s: &str, what: &'static str) -> Result<[u8; N], EncodingError> {
56 let bytes = decode_hex(s)?;
57 to_array(&bytes, what)
58}
59
60pub fn to_array<const N: usize>(bytes: &[u8], what: &'static str) -> Result<[u8; N], EncodingError> {
62 <[u8; N]>::try_from(bytes).map_err(|_| EncodingError::InvalidLength { what, expected: N, actual: bytes.len() })
63}
64
65pub fn to_hex(bytes: &[u8]) -> String {
67 hex::encode(bytes)
68}
69
70pub fn to_hex_0x(bytes: &[u8]) -> String {
72 let mut out = String::with_capacity(2 + bytes.len() * 2);
73 out.push_str("0x");
74 out.push_str(&hex::encode(bytes));
75 out
76}
77
78pub fn write_compact_size(out: &mut Vec<u8>, n: u64) {
80 if n < 0xfd {
81 out.push(n as u8);
82 } else if n <= 0xffff {
83 out.push(0xfd);
84 out.extend_from_slice(&(n as u16).to_le_bytes());
85 } else if n <= 0xffff_ffff {
86 out.push(0xfe);
87 out.extend_from_slice(&(n as u32).to_le_bytes());
88 } else {
89 out.push(0xff);
90 out.extend_from_slice(&n.to_le_bytes());
91 }
92}
93
94pub fn compact_size(n: u64) -> Vec<u8> {
96 let mut out = Vec::with_capacity(9);
97 write_compact_size(&mut out, n);
98 out
99}
100
101pub const fn compact_size_len(n: u64) -> usize {
103 if n < 0xfd {
104 1
105 } else if n <= 0xffff {
106 3
107 } else if n <= 0xffff_ffff {
108 5
109 } else {
110 9
111 }
112}
113
114pub fn write_var_bytes(out: &mut Vec<u8>, data: &[u8]) {
116 write_compact_size(out, data.len() as u64);
117 out.extend_from_slice(data);
118}
119
120#[derive(Clone)]
122pub struct Reader<'a> {
123 bytes: &'a [u8],
124 pos: usize,
125}
126
127impl fmt::Debug for Reader<'_> {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 f.debug_struct("Reader").field("len", &self.bytes.len()).field("pos", &self.pos).finish()
130 }
131}
132
133impl<'a> Reader<'a> {
134 pub const fn new(bytes: &'a [u8]) -> Self {
136 Self { bytes, pos: 0 }
137 }
138
139 pub const fn position(&self) -> usize {
141 self.pos
142 }
143
144 pub fn remaining(&self) -> usize {
146 self.bytes.len().saturating_sub(self.pos)
147 }
148
149 pub fn is_done(&self) -> bool {
151 self.remaining() == 0
152 }
153
154 pub fn rest(&self) -> &'a [u8] {
156 self.bytes.get(self.pos..).unwrap_or(&[])
157 }
158
159 pub fn take(&mut self, n: usize) -> Result<&'a [u8], EncodingError> {
161 let end = self.pos.checked_add(n).ok_or(EncodingError::UnexpectedEnd)?;
162 let out = self.bytes.get(self.pos..end).ok_or(EncodingError::UnexpectedEnd)?;
163 self.pos = end;
164 Ok(out)
165 }
166
167 pub fn array<const N: usize>(&mut self) -> Result<[u8; N], EncodingError> {
169 let bytes = self.take(N)?;
170 <[u8; N]>::try_from(bytes).map_err(|_| EncodingError::UnexpectedEnd)
171 }
172
173 pub fn u8(&mut self) -> Result<u8, EncodingError> {
175 Ok(self.array::<1>()?[0])
176 }
177
178 pub fn u16(&mut self) -> Result<u16, EncodingError> {
180 Ok(u16::from_le_bytes(self.array()?))
181 }
182
183 pub fn u32(&mut self) -> Result<u32, EncodingError> {
185 Ok(u32::from_le_bytes(self.array()?))
186 }
187
188 pub fn i64(&mut self) -> Result<i64, EncodingError> {
190 Ok(i64::from_le_bytes(self.array()?))
191 }
192
193 pub fn compact_size(&mut self) -> Result<u64, EncodingError> {
195 let first = self.u8()?;
196 let n = match first {
197 0..=0xfc => u64::from(first),
198 0xfd => {
199 let n = u64::from(self.u16()?);
200 if n < 0xfd {
201 return Err(EncodingError::NonCanonicalCompactSize { value: n, len: 3 });
202 }
203 n
204 }
205 0xfe => {
206 let n = u64::from(self.u32()?);
207 if n <= 0xffff {
208 return Err(EncodingError::NonCanonicalCompactSize { value: n, len: 5 });
209 }
210 n
211 }
212 0xff => return Err(EncodingError::CompactSize64),
213 };
214 if n > MAX_COMPACT_SIZE {
215 return Err(EncodingError::CompactSizeTooLarge);
216 }
217 Ok(n)
218 }
219
220 pub fn count(&mut self, min_item_size: usize) -> Result<usize, EncodingError> {
223 let n = usize::try_from(self.compact_size()?).map_err(|_| EncodingError::CompactSizeTooLarge)?;
224 let needed = n.checked_mul(min_item_size.max(1)).ok_or(EncodingError::UnexpectedEnd)?;
225 if needed > self.remaining() {
226 return Err(EncodingError::UnexpectedEnd);
227 }
228 Ok(n)
229 }
230
231 pub fn var_bytes(&mut self) -> Result<&'a [u8], EncodingError> {
233 let n = self.count(1)?;
234 self.take(n)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
241 use super::*;
242
243 #[test]
244 fn compact_size_round_trips_little_endian() {
245 assert_eq!(to_hex(&compact_size(0x1234)), "fd3412");
246 assert_eq!(Reader::new(&compact_size(0x1234)).compact_size().unwrap(), 0x1234);
247 assert_eq!(Reader::new(&compact_size(70_000)).compact_size().unwrap(), 70_000);
248 assert_eq!(Reader::new(&[0xfd, 1]).compact_size(), Err(EncodingError::UnexpectedEnd));
249 for n in [0u64, 1, 0xfc, 0xfd, 0xffff, 0x1_0000, MAX_COMPACT_SIZE] {
250 let enc = compact_size(n);
251 assert_eq!(enc.len(), compact_size_len(n));
252 assert_eq!(Reader::new(&enc).compact_size().unwrap(), n);
253 }
254 }
255
256 #[test]
257 fn compact_size_rejects_non_canonical_and_huge() {
258 assert_eq!(
259 Reader::new(&[0xfd, 0x01, 0x00]).compact_size(),
260 Err(EncodingError::NonCanonicalCompactSize { value: 1, len: 3 })
261 );
262 assert_eq!(
263 Reader::new(&[0xfe, 0xff, 0xff, 0, 0]).compact_size(),
264 Err(EncodingError::NonCanonicalCompactSize { value: 0xffff, len: 5 })
265 );
266 assert_eq!(Reader::new(&[0xff, 0, 0, 0, 0, 1, 0, 0, 0]).compact_size(), Err(EncodingError::CompactSize64));
267 assert_eq!(
268 Reader::new(&compact_size(MAX_COMPACT_SIZE + 1)).compact_size(),
269 Err(EncodingError::CompactSizeTooLarge)
270 );
271 }
272
273 #[test]
274 fn counts_are_bounded_by_the_remaining_bytes() {
275 let mut data = compact_size(1000);
276 data.extend_from_slice(&[0u8; 10]);
277 assert_eq!(Reader::new(&data).count(1), Err(EncodingError::UnexpectedEnd));
278 }
279
280 #[test]
281 fn hex_accepts_an_optional_prefix() {
282 assert_eq!(decode_hex("0xABcd").unwrap(), vec![0xab, 0xcd]);
283 assert_eq!(decode_hex("abcd").unwrap(), vec![0xab, 0xcd]);
284 assert_eq!(decode_hex("abc"), Err(EncodingError::InvalidHex));
285 assert_eq!(decode_hex("zz"), Err(EncodingError::InvalidHex));
286 assert_eq!(to_hex_0x(&[1, 2]), "0x0102");
287 }
288}