1use crate::pui;
17
18pub const PUBLIC_KEY_LEN: usize = 32;
21pub const CHANNEL_KEY_LEN: usize = 32;
24pub const CHANNEL_ID_LEN: usize = 2;
27pub const REGION_CODE_LEN: usize = 2;
31pub const REGION_STRING_MAX_LEN: usize = 24;
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum ItemError {
38 Truncated,
40 BadLength,
42 UnknownFilterType,
44 BufferTooSmall,
46 BadPrefix,
48}
49
50#[derive(Clone, Copy, PartialEq, Eq)]
58pub struct PeerKeyEntry {
59 pub public_key: [u8; PUBLIC_KEY_LEN],
60 pub k_enc: [u8; 32],
61 pub k_mic: [u8; 32],
62}
63
64impl PeerKeyEntry {
65 pub const WIRE_LEN: usize = 96;
66
67 pub fn encode(&self, out: &mut [u8]) -> Result<usize, ItemError> {
68 if out.len() < Self::WIRE_LEN {
69 return Err(ItemError::BufferTooSmall);
70 }
71 out[..32].copy_from_slice(&self.public_key);
72 out[32..64].copy_from_slice(&self.k_enc);
73 out[64..96].copy_from_slice(&self.k_mic);
74 Ok(Self::WIRE_LEN)
75 }
76
77 pub fn decode(input: &[u8]) -> Result<Self, ItemError> {
79 if input.len() != Self::WIRE_LEN {
80 return Err(ItemError::BadLength);
81 }
82 let mut entry = Self {
83 public_key: [0; PUBLIC_KEY_LEN],
84 k_enc: [0; 32],
85 k_mic: [0; 32],
86 };
87 entry.public_key.copy_from_slice(&input[..32]);
88 entry.k_enc.copy_from_slice(&input[32..64]);
89 entry.k_mic.copy_from_slice(&input[64..96]);
90 Ok(entry)
91 }
92
93 pub fn digest(&self) -> &[u8; PUBLIC_KEY_LEN] {
96 &self.public_key
97 }
98}
99
100impl core::fmt::Debug for PeerKeyEntry {
103 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 formatter
105 .debug_struct("PeerKeyEntry")
106 .field("public_key", &self.public_key)
107 .finish_non_exhaustive()
108 }
109}
110
111pub const FILTER_DEST_HINT: u8 = 0;
113pub const FILTER_CHANNEL_ID: u8 = 1;
115pub const FILTER_PKT_TYPE: u8 = 2;
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub enum Filter {
122 DestHint([u8; 3]),
124 ChannelId([u8; CHANNEL_ID_LEN]),
127 PktType(u8),
129}
130
131impl Filter {
132 pub const MAX_WIRE_LEN: usize = 4;
134
135 pub const fn wire_len(&self) -> usize {
136 match self {
137 Self::DestHint(_) => 4,
138 Self::ChannelId(_) => 3,
139 Self::PktType(_) => 2,
140 }
141 }
142
143 pub fn encode(&self, out: &mut [u8]) -> Result<usize, ItemError> {
144 let len = self.wire_len();
145 if out.len() < len {
146 return Err(ItemError::BufferTooSmall);
147 }
148 match self {
149 Self::DestHint(hint) => {
150 out[0] = FILTER_DEST_HINT;
151 out[1..4].copy_from_slice(hint);
152 }
153 Self::ChannelId(id) => {
154 out[0] = FILTER_CHANNEL_ID;
155 out[1..3].copy_from_slice(id);
156 }
157 Self::PktType(pkt_type) => {
158 out[0] = FILTER_PKT_TYPE;
159 out[1] = *pkt_type;
160 }
161 }
162 Ok(len)
163 }
164
165 pub fn decode(input: &[u8]) -> Result<Self, ItemError> {
171 let [filter_type, value @ ..] = input else {
172 return Err(ItemError::Truncated);
173 };
174 match (*filter_type, value) {
175 (FILTER_DEST_HINT, &[a, b, c]) => Ok(Self::DestHint([a, b, c])),
176 (FILTER_CHANNEL_ID, &[a, b]) => Ok(Self::ChannelId([a, b])),
177 (FILTER_PKT_TYPE, &[pkt_type]) => Ok(Self::PktType(pkt_type)),
178 (FILTER_DEST_HINT | FILTER_CHANNEL_ID | FILTER_PKT_TYPE, _) => {
179 Err(ItemError::BadLength)
180 }
181 _ => Err(ItemError::UnknownFilterType),
182 }
183 }
184}
185
186pub fn fixed_items<const N: usize>(
192 value: &[u8],
193) -> Result<impl ExactSizeIterator<Item = &[u8; N]> + Clone, ItemError> {
194 const { assert!(N > 0) };
195 if value.len() % N != 0 {
196 return Err(ItemError::BadLength);
197 }
198 Ok(value
199 .chunks_exact(N)
200 .map(|chunk| chunk.try_into().expect("chunks_exact yields N-byte chunks")))
201}
202
203pub fn prefixed_items(value: &[u8]) -> PrefixedItems<'_> {
210 PrefixedItems { rest: value }
211}
212
213#[derive(Clone)]
214pub struct PrefixedItems<'a> {
215 rest: &'a [u8],
216}
217
218impl<'a> Iterator for PrefixedItems<'a> {
219 type Item = Result<&'a [u8], ItemError>;
220
221 fn next(&mut self) -> Option<Self::Item> {
222 if self.rest.is_empty() {
223 return None;
224 }
225 let (len, consumed) = match pui::decode(self.rest) {
226 Ok(decoded) => decoded,
227 Err(_) => {
228 self.rest = &[];
229 return Some(Err(ItemError::BadPrefix));
230 }
231 };
232 let body = &self.rest[consumed..];
233 let Ok(len) = usize::try_from(len) else {
234 self.rest = &[];
235 return Some(Err(ItemError::BadPrefix));
236 };
237 if body.len() < len {
238 self.rest = &[];
239 return Some(Err(ItemError::Truncated));
240 }
241 let (item, rest) = body.split_at(len);
242 self.rest = rest;
243 Some(Ok(item))
244 }
245}
246
247pub fn encode_prefixed_item(item: &[u8], out: &mut [u8]) -> Result<usize, ItemError> {
250 let len = u32::try_from(item.len()).map_err(|_| ItemError::BadLength)?;
251 let prefix = pui::encode(len, out).map_err(|error| match error {
252 pui::Error::BufferTooSmall => ItemError::BufferTooSmall,
253 _ => ItemError::BadLength,
254 })?;
255 let end = prefix + item.len();
256 if out.len() < end {
257 return Err(ItemError::BufferTooSmall);
258 }
259 out[prefix..end].copy_from_slice(item);
260 Ok(end)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn peer_key_entry_round_trip_and_digest_is_secret_free() {
269 let entry = PeerKeyEntry {
270 public_key: [0x11; 32],
271 k_enc: [0x22; 32],
272 k_mic: [0x33; 32],
273 };
274 let mut buf = [0u8; PeerKeyEntry::WIRE_LEN];
275 assert_eq!(entry.encode(&mut buf).unwrap(), PeerKeyEntry::WIRE_LEN);
276 assert_eq!(PeerKeyEntry::decode(&buf).unwrap(), entry);
277
278 assert_eq!(entry.digest(), &[0x11; 32]);
280 let debug = std::format!("{entry:?}");
282 assert!(
283 !debug.contains("k_enc") && !debug.contains("k_mic"),
284 "{debug}"
285 );
286 }
287
288 #[test]
289 fn peer_key_entry_rejects_wrong_lengths() {
290 assert_eq!(PeerKeyEntry::decode(&[0; 95]), Err(ItemError::BadLength));
291 assert_eq!(PeerKeyEntry::decode(&[0; 97]), Err(ItemError::BadLength));
292 assert_eq!(PeerKeyEntry::decode(&[]), Err(ItemError::BadLength));
293 }
294
295 #[test]
296 fn filter_round_trip_every_type() {
297 let filters = [
298 Filter::DestHint([0xAA, 0xBB, 0xCC]),
299 Filter::ChannelId([0x12, 0x34]),
300 Filter::PktType(0),
301 ];
302 for filter in filters {
303 let mut buf = [0u8; Filter::MAX_WIRE_LEN];
304 let len = filter.encode(&mut buf).unwrap();
305 assert_eq!(len, filter.wire_len());
306 assert_eq!(Filter::decode(&buf[..len]).unwrap(), filter);
307 }
308 }
309
310 #[test]
311 fn filter_rejects_malformed_entries() {
312 assert_eq!(Filter::decode(&[]), Err(ItemError::Truncated));
313 assert_eq!(
315 Filter::decode(&[FILTER_DEST_HINT, 1, 2]),
316 Err(ItemError::BadLength)
317 );
318 assert_eq!(
319 Filter::decode(&[FILTER_CHANNEL_ID, 1, 2, 3]),
320 Err(ItemError::BadLength)
321 );
322 assert_eq!(
323 Filter::decode(&[FILTER_PKT_TYPE]),
324 Err(ItemError::BadLength)
325 );
326 assert_eq!(Filter::decode(&[3, 0]), Err(ItemError::UnknownFilterType));
328 }
329
330 #[test]
331 fn fixed_items_iterates_and_validates_alignment() {
332 let value = [1u8, 1, 2, 2, 3, 3];
333 let items: Vec<[u8; 2]> = fixed_items::<2>(&value).unwrap().copied().collect();
334 assert_eq!(items, [[1, 1], [2, 2], [3, 3]]);
335
336 assert!(fixed_items::<2>(&[0; 5]).is_err());
337 assert_eq!(fixed_items::<32>(&[]).unwrap().len(), 0);
338 }
339
340 #[test]
341 fn prefixed_items_round_trip() {
342 let mut table = [0u8; 32];
343 let mut len = 0;
344 let items: [&[u8]; 3] = [&[0xAA, 0xBB], &[], &[0xCC; 5]];
345 for item in items {
346 len += encode_prefixed_item(item, &mut table[len..]).unwrap();
347 }
348 let decoded: Vec<&[u8]> = prefixed_items(&table[..len])
349 .collect::<Result<_, _>>()
350 .unwrap();
351 assert_eq!(decoded, items);
352 assert_eq!(prefixed_items(&[]).count(), 0);
353 }
354
355 #[test]
356 fn prefixed_items_reports_truncation_and_stops() {
357 let mut iterator = prefixed_items(&[4, 0xAA, 0xBB]);
359 assert_eq!(iterator.next(), Some(Err(ItemError::Truncated)));
360 assert_eq!(iterator.next(), None);
361
362 let mut iterator = prefixed_items(&[0x80, 0x80, 0x80, 0x80]);
364 assert_eq!(iterator.next(), Some(Err(ItemError::BadPrefix)));
365 assert_eq!(iterator.next(), None);
366 }
367
368 #[test]
369 fn encode_prefixed_item_reports_overflow() {
370 let mut small = [0u8; 3];
371 assert_eq!(
372 encode_prefixed_item(&[0; 8], &mut small),
373 Err(ItemError::BufferTooSmall)
374 );
375 }
376}