1use std::net::{Ipv4Addr, Ipv6Addr};
13use std::time::Duration;
14
15use bytes::{Buf, BufMut, Bytes, BytesMut};
16use ipnet::{Ipv4Net, Ipv6Net};
17
18use crate::data_item::{ConnectionPointFlags, DataItem, PeerFlags, RawDataItem};
19use crate::error::{CodecError, ExpectedLen};
20use crate::ids::{DataItemType, ExtensionId, MessageType, SignalType};
21use crate::mac::MacAddress;
22use crate::message::Message;
23use crate::signal::Signal;
24use crate::status::StatusCode;
25use crate::{MIN_HEARTBEAT_INTERVAL_MS, SIGNAL_PREFIX};
26
27pub const SIGNAL_HEADER_LEN: usize = 8;
29
30pub const MESSAGE_HEADER_LEN: usize = 4;
32
33mod field {
37 pub const DATA_ITEM_FLAGS: &str = "data_item_flags";
38 pub const HEARTBEAT_INTERVAL_MS: &str = "heartbeat_interval_ms";
39 pub const LATENCY_US: &str = "latency_us";
40 pub const RESOURCES: &str = "resources";
41 pub const RLQ_RECEIVE: &str = "relative_link_quality_receive";
42 pub const RLQ_TRANSMIT: &str = "relative_link_quality_transmit";
43 pub const IPV4_ATTACHED_SUBNET_PREFIX: &str = "ipv4_attached_subnet_prefix";
44 pub const IPV6_ATTACHED_SUBNET_PREFIX: &str = "ipv6_attached_subnet_prefix";
45 pub const DATA_ITEM_VALUE_LENGTH: &str = "data_item_value_length";
46 pub const SIGNAL_BODY_LENGTH: &str = "signal_body_length";
47 pub const MESSAGE_BODY_LENGTH: &str = "message_body_length";
48}
49
50impl RawDataItem {
51 pub fn encode(&self, out: &mut BytesMut) -> Result<(), CodecError> {
55 let len = u16_length(field::DATA_ITEM_VALUE_LENGTH, self.value.len())?;
56 out.put_u16(self.type_id.0);
57 out.put_u16(len);
58 out.put_slice(&self.value);
59 Ok(())
60 }
61
62 pub fn decode(src: &mut Bytes) -> Result<Self, CodecError> {
63 ensure_len(src.remaining(), 4)?;
64 let type_id = DataItemType(src.get_u16());
65 let len = src.get_u16() as usize;
66 ensure_len(src.remaining(), len)?;
67 let value = src.split_to(len);
68 Ok(RawDataItem { type_id, value })
69 }
70}
71
72impl DataItem {
73 pub fn encode(&self, out: &mut BytesMut) -> Result<(), CodecError> {
90 let restore_to = out.len();
91 match self.encode_into(out) {
92 Ok(()) => Ok(()),
93 Err(e) => {
94 out.truncate(restore_to);
95 Err(e)
96 }
97 }
98 }
99
100 fn encode_into(&self, out: &mut BytesMut) -> Result<(), CodecError> {
101 if let DataItem::Unknown(raw) = self {
102 return raw.encode(out);
103 }
104
105 if let Some(projected) = self.projected_value_len() {
110 u16_length(field::DATA_ITEM_VALUE_LENGTH, projected)?;
111 }
112
113 out.put_u16(self.type_id().0);
116 let len_pos = out.len();
117 out.put_u16(0);
118 let value_start = out.len();
119
120 match self {
121 DataItem::Status { code, text } => {
122 out.put_u8(code.0);
123 out.put_slice(text.as_bytes());
124 }
125 DataItem::Ipv4ConnectionPoint { flags, addr, port } => {
126 out.put_u8(encode_cp_flags(*flags));
127 out.put_slice(&addr.octets());
128 if let Some(p) = port {
129 out.put_u16(*p);
130 }
131 }
132 DataItem::Ipv6ConnectionPoint { flags, addr, port } => {
133 out.put_u8(encode_cp_flags(*flags));
134 out.put_slice(&addr.octets());
135 if let Some(p) = port {
136 out.put_u16(*p);
137 }
138 }
139 DataItem::PeerType { flags, description } => {
140 out.put_u8(encode_peer_flags(*flags));
141 out.put_slice(description.as_bytes());
142 }
143 DataItem::HeartbeatInterval(d) => {
144 let ms = d.as_millis();
145 if ms < MIN_HEARTBEAT_INTERVAL_MS as u128 {
146 return Err(CodecError::OutOfRange {
147 field: field::HEARTBEAT_INTERVAL_MS,
148 value: u64::try_from(ms).unwrap_or(u64::MAX),
149 });
150 }
151 if ms > u32::MAX as u128 {
152 return Err(CodecError::OutOfRange {
153 field: field::HEARTBEAT_INTERVAL_MS,
154 value: u64::try_from(ms).unwrap_or(u64::MAX),
155 });
156 }
157 out.put_u32(ms as u32);
158 }
159 DataItem::ExtensionsSupported(ids) => {
160 for id in ids {
161 out.put_u16(id.0);
162 }
163 }
164 DataItem::MacAddress(mac) => {
165 out.put_slice(mac.as_bytes());
167 }
168 DataItem::Ipv4Address { add, addr } => {
169 out.put_u8(u8::from(*add));
170 out.put_slice(&addr.octets());
171 }
172 DataItem::Ipv6Address { add, addr } => {
173 out.put_u8(u8::from(*add));
174 out.put_slice(&addr.octets());
175 }
176 DataItem::Ipv4AttachedSubnet { add, subnet } => {
177 out.put_u8(u8::from(*add));
178 out.put_slice(&subnet.network().octets());
179 out.put_u8(subnet.prefix_len());
180 }
181 DataItem::Ipv6AttachedSubnet { add, subnet } => {
182 out.put_u8(u8::from(*add));
183 out.put_slice(&subnet.network().octets());
184 out.put_u8(subnet.prefix_len());
185 }
186 DataItem::MaxDataRateReceive(bps)
187 | DataItem::MaxDataRateTransmit(bps)
188 | DataItem::CurrentDataRateReceive(bps)
189 | DataItem::CurrentDataRateTransmit(bps) => {
190 out.put_u64(*bps);
191 }
192 DataItem::Latency(d) => {
193 let us = d.as_micros();
194 if us > u64::MAX as u128 {
195 return Err(CodecError::OutOfRange {
201 field: field::LATENCY_US,
202 value: u64::MAX,
203 });
204 }
205 out.put_u64(us as u64);
206 }
207 DataItem::Resources(pct) => {
208 check_percent(field::RESOURCES, *pct)?;
209 out.put_u8(*pct);
210 }
211 DataItem::RelativeLinkQualityReceive(pct) => {
212 check_percent(field::RLQ_RECEIVE, *pct)?;
213 out.put_u8(*pct);
214 }
215 DataItem::RelativeLinkQualityTransmit(pct) => {
216 check_percent(field::RLQ_TRANSMIT, *pct)?;
217 out.put_u8(*pct);
218 }
219 DataItem::Mtu(mtu) => {
220 out.put_u16(*mtu);
221 }
222 DataItem::Unknown(_) => unreachable!("Unknown handled by early return"),
223 }
224
225 let value_len = out.len() - value_start;
226 let len_u16 = u16_length(field::DATA_ITEM_VALUE_LENGTH, value_len)?;
227 out[len_pos..len_pos + 2].copy_from_slice(&len_u16.to_be_bytes());
228 Ok(())
229 }
230
231 fn projected_value_len(&self) -> Option<usize> {
238 match self {
239 DataItem::Status { text, .. } => Some(1usize.saturating_add(text.len())),
240 DataItem::PeerType { description, .. } => {
241 Some(1usize.saturating_add(description.len()))
242 }
243 DataItem::ExtensionsSupported(ids) => Some(ids.len().saturating_mul(2)),
244 _ => None,
245 }
246 }
247
248 pub fn decode(raw: RawDataItem) -> Result<Self, CodecError> {
258 let kind = raw.type_id;
259 let len = raw.value.len();
260 let v = &raw.value[..];
261
262 match kind {
263 DataItemType::STATUS => {
264 if len < 1 {
265 return Err(CodecError::InvalidDataItemLength {
266 kind,
267 expected: ExpectedLen::AtLeast(1),
268 got: len,
269 });
270 }
271 let code = StatusCode(v[0]);
272 let text = String::from_utf8(v[1..].to_vec())?;
273 Ok(DataItem::Status { code, text })
274 }
275 DataItemType::IPV4_CONNECTION_POINT => {
276 if len != 5 && len != 7 {
277 return Err(CodecError::InvalidDataItemLength {
278 kind,
279 expected: ExpectedLen::OneOf(&[5, 7]),
280 got: len,
281 });
282 }
283 validate_flags(v[0])?;
284 let flags = decode_cp_flags(v[0]);
285 let addr = Ipv4Addr::new(v[1], v[2], v[3], v[4]);
286 let port = (len == 7).then(|| u16::from_be_bytes([v[5], v[6]]));
287 Ok(DataItem::Ipv4ConnectionPoint { flags, addr, port })
288 }
289 DataItemType::IPV6_CONNECTION_POINT => {
290 if len != 17 && len != 19 {
291 return Err(CodecError::InvalidDataItemLength {
292 kind,
293 expected: ExpectedLen::OneOf(&[17, 19]),
294 got: len,
295 });
296 }
297 validate_flags(v[0])?;
298 let flags = decode_cp_flags(v[0]);
299 let mut octets = [0u8; 16];
300 octets.copy_from_slice(&v[1..17]);
301 let addr = Ipv6Addr::from(octets);
302 let port = (len == 19).then(|| u16::from_be_bytes([v[17], v[18]]));
303 Ok(DataItem::Ipv6ConnectionPoint { flags, addr, port })
304 }
305 DataItemType::PEER_TYPE => {
306 if len < 1 {
307 return Err(CodecError::InvalidDataItemLength {
308 kind,
309 expected: ExpectedLen::AtLeast(1),
310 got: len,
311 });
312 }
313 validate_flags(v[0])?;
314 let flags = decode_peer_flags(v[0]);
315 let description = String::from_utf8(v[1..].to_vec())?;
316 Ok(DataItem::PeerType { flags, description })
317 }
318 DataItemType::HEARTBEAT_INTERVAL => {
319 expect_exact(kind, len, 4)?;
320 let ms = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
321 if ms < MIN_HEARTBEAT_INTERVAL_MS {
322 return Err(CodecError::OutOfRange {
323 field: field::HEARTBEAT_INTERVAL_MS,
324 value: ms.into(),
325 });
326 }
327 Ok(DataItem::HeartbeatInterval(Duration::from_millis(
328 ms.into(),
329 )))
330 }
331 DataItemType::EXTENSIONS_SUPPORTED => {
332 if len % 2 != 0 {
333 return Err(CodecError::InvalidDataItemLength {
334 kind,
335 expected: ExpectedLen::Multiple(2),
336 got: len,
337 });
338 }
339 let mut ids = Vec::with_capacity(len / 2);
340 let mut i = 0;
341 while i < len {
342 ids.push(ExtensionId(u16::from_be_bytes([v[i], v[i + 1]])));
343 i += 2;
344 }
345 Ok(DataItem::ExtensionsSupported(ids))
346 }
347 DataItemType::MAC_ADDRESS => {
348 let mac = match len {
353 6 => {
354 let mut octets = [0u8; 6];
355 octets.copy_from_slice(&v[..6]);
356 MacAddress::Eui48(octets)
357 }
358 8 => {
359 let mut octets = [0u8; 8];
360 octets.copy_from_slice(&v[..8]);
361 MacAddress::Eui64(octets)
362 }
363 _ => {
364 return Err(CodecError::InvalidDataItemLength {
365 kind,
366 expected: ExpectedLen::OneOf(&[6, 8]),
367 got: len,
368 });
369 }
370 };
371 Ok(DataItem::MacAddress(mac))
372 }
373 DataItemType::IPV4_ADDRESS => {
374 expect_exact(kind, len, 5)?;
375 validate_flags(v[0])?;
376 let add = (v[0] & 0x01) != 0;
377 let addr = Ipv4Addr::new(v[1], v[2], v[3], v[4]);
378 Ok(DataItem::Ipv4Address { add, addr })
379 }
380 DataItemType::IPV6_ADDRESS => {
381 expect_exact(kind, len, 17)?;
382 validate_flags(v[0])?;
383 let add = (v[0] & 0x01) != 0;
384 let mut octets = [0u8; 16];
385 octets.copy_from_slice(&v[1..17]);
386 Ok(DataItem::Ipv6Address {
387 add,
388 addr: Ipv6Addr::from(octets),
389 })
390 }
391 DataItemType::IPV4_ATTACHED_SUBNET => {
392 expect_exact(kind, len, 6)?;
393 validate_flags(v[0])?;
394 let add = (v[0] & 0x01) != 0;
395 let addr = Ipv4Addr::new(v[1], v[2], v[3], v[4]);
396 let prefix = v[5];
397 let subnet = Ipv4Net::new(addr, prefix)
402 .map_err(|_| CodecError::OutOfRange {
403 field: field::IPV4_ATTACHED_SUBNET_PREFIX,
404 value: prefix as u64,
405 })?
406 .trunc();
407 Ok(DataItem::Ipv4AttachedSubnet { add, subnet })
408 }
409 DataItemType::IPV6_ATTACHED_SUBNET => {
410 expect_exact(kind, len, 18)?;
411 validate_flags(v[0])?;
412 let add = (v[0] & 0x01) != 0;
413 let mut octets = [0u8; 16];
414 octets.copy_from_slice(&v[1..17]);
415 let prefix = v[17];
416 let subnet = Ipv6Net::new(Ipv6Addr::from(octets), prefix)
417 .map_err(|_| CodecError::OutOfRange {
418 field: field::IPV6_ATTACHED_SUBNET_PREFIX,
419 value: prefix as u64,
420 })?
421 .trunc();
422 Ok(DataItem::Ipv6AttachedSubnet { add, subnet })
423 }
424 DataItemType::MAXIMUM_DATA_RATE_RECEIVE => {
425 expect_exact(kind, len, 8)?;
426 Ok(DataItem::MaxDataRateReceive(read_u64_be(v)))
427 }
428 DataItemType::MAXIMUM_DATA_RATE_TRANSMIT => {
429 expect_exact(kind, len, 8)?;
430 Ok(DataItem::MaxDataRateTransmit(read_u64_be(v)))
431 }
432 DataItemType::CURRENT_DATA_RATE_RECEIVE => {
433 expect_exact(kind, len, 8)?;
434 Ok(DataItem::CurrentDataRateReceive(read_u64_be(v)))
435 }
436 DataItemType::CURRENT_DATA_RATE_TRANSMIT => {
437 expect_exact(kind, len, 8)?;
438 Ok(DataItem::CurrentDataRateTransmit(read_u64_be(v)))
439 }
440 DataItemType::LATENCY => {
441 expect_exact(kind, len, 8)?;
442 Ok(DataItem::Latency(Duration::from_micros(read_u64_be(v))))
443 }
444 DataItemType::RESOURCES => Ok(DataItem::Resources(decode_percent(
445 kind,
446 v,
447 field::RESOURCES,
448 )?)),
449 DataItemType::RELATIVE_LINK_QUALITY_RECEIVE => Ok(
450 DataItem::RelativeLinkQualityReceive(decode_percent(kind, v, field::RLQ_RECEIVE)?),
451 ),
452 DataItemType::RELATIVE_LINK_QUALITY_TRANSMIT => {
453 Ok(DataItem::RelativeLinkQualityTransmit(decode_percent(
454 kind,
455 v,
456 field::RLQ_TRANSMIT,
457 )?))
458 }
459 DataItemType::MTU => {
460 expect_exact(kind, len, 2)?;
461 Ok(DataItem::Mtu(u16::from_be_bytes([v[0], v[1]])))
462 }
463 _ => Ok(DataItem::Unknown(raw)),
464 }
465 }
466}
467
468impl Signal {
469 pub fn encode(&self) -> Result<BytesMut, CodecError> {
473 let mut body = BytesMut::new();
474 for item in &self.data_items {
475 item.encode(&mut body)?;
476 }
477 let body_len = u16_length(field::SIGNAL_BODY_LENGTH, body.len())?;
478 let mut out = BytesMut::with_capacity(SIGNAL_HEADER_LEN + body.len());
479 out.put_slice(SIGNAL_PREFIX);
480 out.put_u16(self.signal_type.0);
481 out.put_u16(body_len);
482 out.put(body);
483 Ok(out)
484 }
485
486 pub fn decode(mut src: Bytes) -> Result<Self, CodecError> {
487 ensure_len(src.remaining(), SIGNAL_HEADER_LEN)?;
488 let mut prefix = [0u8; 4];
489 src.copy_to_slice(&mut prefix);
490 if &prefix != SIGNAL_PREFIX {
491 return Err(CodecError::MissingSignalPrefix);
492 }
493 let signal_type = SignalType(src.get_u16());
494 let declared = src.get_u16() as usize;
495 if src.remaining() != declared {
496 return Err(CodecError::LengthMismatch {
497 declared,
498 remaining: src.remaining(),
499 });
500 }
501 let mut body = src.split_to(declared);
502 let mut data_items = Vec::new();
503 while body.has_remaining() {
504 let raw = RawDataItem::decode(&mut body)?;
505 data_items.push(DataItem::decode(raw)?);
506 }
507 Ok(Signal {
508 signal_type,
509 data_items,
510 })
511 }
512}
513
514impl Message {
515 pub fn encode(&self) -> Result<BytesMut, CodecError> {
519 let mut body = BytesMut::new();
520 for item in &self.data_items {
521 item.encode(&mut body)?;
522 }
523 let body_len = u16_length(field::MESSAGE_BODY_LENGTH, body.len())?;
524 let mut out = BytesMut::with_capacity(MESSAGE_HEADER_LEN + body.len());
525 out.put_u16(self.message_type.0);
526 out.put_u16(body_len);
527 out.put(body);
528 Ok(out)
529 }
530
531 pub fn decode(mut src: Bytes) -> Result<Self, CodecError> {
532 ensure_len(src.remaining(), MESSAGE_HEADER_LEN)?;
533 let message_type = MessageType(src.get_u16());
534 let declared = src.get_u16() as usize;
535 if src.remaining() < declared {
536 return Err(CodecError::LengthMismatch {
537 declared,
538 remaining: src.remaining(),
539 });
540 }
541 let mut body = src.split_to(declared);
542 let mut data_items = Vec::new();
543 while body.has_remaining() {
544 let raw = RawDataItem::decode(&mut body)?;
545 data_items.push(DataItem::decode(raw)?);
546 }
547 Ok(Message {
548 message_type,
549 data_items,
550 })
551 }
552}
553
554fn ensure_len(have: usize, needed: usize) -> Result<(), CodecError> {
555 if have < needed {
556 Err(CodecError::Truncated { needed, have })
557 } else {
558 Ok(())
559 }
560}
561
562fn expect_exact(kind: DataItemType, got: usize, expected: usize) -> Result<(), CodecError> {
563 if got == expected {
564 Ok(())
565 } else {
566 Err(CodecError::InvalidDataItemLength {
567 kind,
568 expected: ExpectedLen::Exact(expected),
569 got,
570 })
571 }
572}
573
574fn read_u64_be(value: &[u8]) -> u64 {
575 let mut buf = [0u8; 8];
576 buf.copy_from_slice(&value[..8]);
577 u64::from_be_bytes(buf)
578}
579
580fn validate_flags(byte: u8) -> Result<(), CodecError> {
588 if byte & 0xfe != 0 {
589 return Err(CodecError::OutOfRange {
590 field: field::DATA_ITEM_FLAGS,
591 value: byte.into(),
592 });
593 }
594 Ok(())
595}
596
597fn encode_cp_flags(flags: ConnectionPointFlags) -> u8 {
598 u8::from(flags.use_tls)
599}
600
601fn decode_cp_flags(byte: u8) -> ConnectionPointFlags {
602 ConnectionPointFlags {
603 use_tls: (byte & 0x01) != 0,
604 }
605}
606
607fn encode_peer_flags(flags: PeerFlags) -> u8 {
608 u8::from(flags.smi)
609}
610
611fn decode_peer_flags(byte: u8) -> PeerFlags {
612 PeerFlags {
613 smi: (byte & 0x01) != 0,
614 }
615}
616
617fn check_percent(field: &'static str, pct: u8) -> Result<(), CodecError> {
618 if pct > 100 {
619 Err(CodecError::OutOfRange {
620 field,
621 value: pct as u64,
622 })
623 } else {
624 Ok(())
625 }
626}
627
628fn decode_percent(kind: DataItemType, value: &[u8], field: &'static str) -> Result<u8, CodecError> {
632 expect_exact(kind, value.len(), 1)?;
633 let pct = value[0];
634 check_percent(field, pct)?;
635 Ok(pct)
636}
637
638fn u16_length(field: &'static str, len: usize) -> Result<u16, CodecError> {
642 u16::try_from(len).map_err(|_| CodecError::OutOfRange {
643 field,
644 value: len as u64,
645 })
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651
652 fn encode_one(item: DataItem) -> Vec<u8> {
653 let mut buf = BytesMut::new();
654 item.encode(&mut buf).expect("encode should not fail");
655 buf.to_vec()
656 }
657
658 fn decode_one(bytes: &[u8]) -> DataItem {
659 let mut b = Bytes::copy_from_slice(bytes);
660 let raw = RawDataItem::decode(&mut b).unwrap();
661 DataItem::decode(raw).unwrap()
662 }
663
664 #[test]
665 fn empty_message_roundtrips() {
666 let m = Message::new(MessageType::HEARTBEAT);
667 let bytes = m.encode().unwrap().freeze();
668 let decoded = Message::decode(bytes).unwrap();
669 assert_eq!(decoded.message_type, MessageType::HEARTBEAT);
670 assert!(decoded.data_items.is_empty());
671 }
672
673 #[test]
674 fn empty_signal_roundtrips() {
675 let s = Signal::new(SignalType::PEER_DISCOVERY);
676 let bytes = s.encode().unwrap().freeze();
677 let decoded = Signal::decode(bytes).unwrap();
678 assert_eq!(decoded.signal_type, SignalType::PEER_DISCOVERY);
679 }
680
681 #[test]
682 fn signal_rejects_bad_prefix() {
683 let mut bad = BytesMut::from(&b"XLEP"[..]);
684 bad.put_u16(1);
685 bad.put_u16(0);
686 let err = Signal::decode(bad.freeze()).unwrap_err();
687 assert!(matches!(err, CodecError::MissingSignalPrefix));
688 }
689
690 #[test]
693 fn status_encodes_with_text() {
694 let item = DataItem::Status {
695 code: StatusCode::SUCCESS,
696 text: "ok".into(),
697 };
698 assert_eq!(
700 encode_one(item),
701 vec![0x00, 0x01, 0x00, 0x03, 0x00, b'o', b'k']
702 );
703 }
704
705 #[test]
706 fn status_roundtrips_terminate_code() {
707 let item = DataItem::Status {
708 code: StatusCode::TIMED_OUT,
709 text: "deadline".into(),
710 };
711 let bytes = encode_one(item);
712 let DataItem::Status { code, text } = decode_one(&bytes) else {
713 panic!("wrong variant")
714 };
715 assert_eq!(code, StatusCode::TIMED_OUT);
716 assert_eq!(text, "deadline");
717 }
718
719 #[test]
720 fn status_with_empty_text_roundtrips() {
721 let item = DataItem::Status {
722 code: StatusCode::SUCCESS,
723 text: String::new(),
724 };
725 let bytes = encode_one(item);
726 assert_eq!(bytes, vec![0x00, 0x01, 0x00, 0x01, 0x00]);
728 let DataItem::Status { code, text } = decode_one(&bytes) else {
729 panic!()
730 };
731 assert_eq!(code, StatusCode::SUCCESS);
732 assert!(text.is_empty());
733 }
734
735 #[test]
736 fn status_with_zero_length_value_rejected() {
737 let raw = RawDataItem {
738 type_id: DataItemType::STATUS,
739 value: Bytes::new(),
740 };
741 let err = DataItem::decode(raw).unwrap_err();
742 assert!(matches!(
743 err,
744 CodecError::InvalidDataItemLength {
745 kind: DataItemType::STATUS,
746 ..
747 }
748 ));
749 }
750
751 #[test]
752 fn ipv4_connection_point_with_port_encodes() {
753 let item = DataItem::Ipv4ConnectionPoint {
754 flags: ConnectionPointFlags { use_tls: true },
755 addr: Ipv4Addr::new(10, 0, 0, 1),
756 port: Some(854),
757 };
758 assert_eq!(
760 encode_one(item),
761 vec![0x00, 0x02, 0x00, 0x07, 0x01, 10, 0, 0, 1, 0x03, 0x56]
762 );
763 }
764
765 #[test]
766 fn ipv4_connection_point_length_between_valid_forms_rejected() {
767 let raw = RawDataItem {
771 type_id: DataItemType::IPV4_CONNECTION_POINT,
772 value: Bytes::from_static(&[0, 1, 2, 3, 4, 5]),
773 };
774 match DataItem::decode(raw).unwrap_err() {
775 CodecError::InvalidDataItemLength {
776 kind: DataItemType::IPV4_CONNECTION_POINT,
777 expected: ExpectedLen::OneOf(&[5, 7]),
778 got: 6,
779 } => {}
780 other => panic!("unexpected error: {other:?}"),
781 }
782 }
783
784 #[test]
785 fn ipv6_connection_point_length_between_valid_forms_rejected() {
786 let raw = RawDataItem {
787 type_id: DataItemType::IPV6_CONNECTION_POINT,
788 value: Bytes::from_static(&[0u8; 18]),
789 };
790 match DataItem::decode(raw).unwrap_err() {
791 CodecError::InvalidDataItemLength {
792 kind: DataItemType::IPV6_CONNECTION_POINT,
793 expected: ExpectedLen::OneOf(&[17, 19]),
794 got: 18,
795 } => {}
796 other => panic!("unexpected error: {other:?}"),
797 }
798 }
799
800 #[test]
801 fn status_at_least_one_byte_error_carries_atleast() {
802 let raw = RawDataItem {
803 type_id: DataItemType::STATUS,
804 value: Bytes::new(),
805 };
806 match DataItem::decode(raw).unwrap_err() {
807 CodecError::InvalidDataItemLength {
808 kind: DataItemType::STATUS,
809 expected: ExpectedLen::AtLeast(1),
810 got: 0,
811 } => {}
812 other => panic!("unexpected error: {other:?}"),
813 }
814 }
815
816 #[test]
817 fn extensions_supported_odd_length_error_carries_multiple() {
818 let raw = RawDataItem {
819 type_id: DataItemType::EXTENSIONS_SUPPORTED,
820 value: Bytes::from_static(&[0x00, 0x01, 0x00]),
821 };
822 match DataItem::decode(raw).unwrap_err() {
823 CodecError::InvalidDataItemLength {
824 kind: DataItemType::EXTENSIONS_SUPPORTED,
825 expected: ExpectedLen::Multiple(2),
826 got: 3,
827 } => {}
828 other => panic!("unexpected error: {other:?}"),
829 }
830 }
831
832 #[test]
833 fn ipv4_connection_point_without_port_roundtrips() {
834 let item = DataItem::Ipv4ConnectionPoint {
835 flags: ConnectionPointFlags::default(),
836 addr: Ipv4Addr::new(192, 168, 1, 1),
837 port: None,
838 };
839 let bytes = encode_one(item);
840 assert_eq!(bytes.len(), 4 + 5);
841 let DataItem::Ipv4ConnectionPoint { flags, addr, port } = decode_one(&bytes) else {
842 panic!()
843 };
844 assert!(!flags.use_tls);
845 assert_eq!(addr, Ipv4Addr::new(192, 168, 1, 1));
846 assert_eq!(port, None);
847 }
848
849 #[test]
850 fn ipv6_connection_point_with_port_roundtrips() {
851 let item = DataItem::Ipv6ConnectionPoint {
852 flags: ConnectionPointFlags { use_tls: true },
853 addr: "fe80::1".parse().unwrap(),
854 port: Some(854),
855 };
856 let bytes = encode_one(item);
857 assert_eq!(bytes.len(), 4 + 19);
858 let DataItem::Ipv6ConnectionPoint { flags, addr, port } = decode_one(&bytes) else {
859 panic!()
860 };
861 assert!(flags.use_tls);
862 assert_eq!(addr, "fe80::1".parse::<Ipv6Addr>().unwrap());
863 assert_eq!(port, Some(854));
864 }
865
866 #[test]
867 fn ipv6_connection_point_without_port_roundtrips() {
868 let item = DataItem::Ipv6ConnectionPoint {
869 flags: ConnectionPointFlags::default(),
870 addr: Ipv6Addr::LOCALHOST,
871 port: None,
872 };
873 let bytes = encode_one(item);
874 assert_eq!(bytes.len(), 4 + 17);
875 let DataItem::Ipv6ConnectionPoint { port, .. } = decode_one(&bytes) else {
876 panic!()
877 };
878 assert_eq!(port, None);
879 }
880
881 #[test]
882 fn peer_type_encodes() {
883 let item = DataItem::PeerType {
884 flags: PeerFlags { smi: true },
885 description: "modem".into(),
886 };
887 assert_eq!(
889 encode_one(item),
890 vec![0x00, 0x04, 0x00, 0x06, 0x01, b'm', b'o', b'd', b'e', b'm']
891 );
892 }
893
894 #[test]
895 fn peer_type_with_empty_description_roundtrips() {
896 let item = DataItem::PeerType {
897 flags: PeerFlags::default(),
898 description: String::new(),
899 };
900 let bytes = encode_one(item);
901 let DataItem::PeerType { flags, description } = decode_one(&bytes) else {
902 panic!()
903 };
904 assert!(!flags.smi);
905 assert!(description.is_empty());
906 }
907
908 #[test]
909 fn heartbeat_interval_encodes() {
910 let item = DataItem::HeartbeatInterval(Duration::from_millis(60_000));
911 assert_eq!(
913 encode_one(item),
914 vec![0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0xEA, 0x60]
915 );
916 }
917
918 #[test]
919 fn heartbeat_interval_below_rfc_minimum_rejected_on_encode() {
920 for ms in [0, MIN_HEARTBEAT_INTERVAL_MS - 1] {
921 let item = DataItem::HeartbeatInterval(Duration::from_millis(ms.into()));
922 let mut buf = BytesMut::new();
923 let err = item.encode(&mut buf).unwrap_err();
924 assert!(matches!(
925 err,
926 CodecError::OutOfRange {
927 field: field::HEARTBEAT_INTERVAL_MS,
928 ..
929 }
930 ));
931 assert!(buf.is_empty());
932 }
933 }
934
935 #[test]
936 fn heartbeat_interval_below_rfc_minimum_rejected_on_decode() {
937 for ms in [0, MIN_HEARTBEAT_INTERVAL_MS - 1] {
938 let raw = RawDataItem {
939 type_id: DataItemType::HEARTBEAT_INTERVAL,
940 value: Bytes::copy_from_slice(&ms.to_be_bytes()),
941 };
942 let err = DataItem::decode(raw).unwrap_err();
943 assert!(matches!(
944 err,
945 CodecError::OutOfRange {
946 field: field::HEARTBEAT_INTERVAL_MS,
947 ..
948 }
949 ));
950 }
951 }
952
953 #[test]
954 fn heartbeat_interval_overflow_rejected_on_encode() {
955 let item = DataItem::HeartbeatInterval(Duration::from_secs(u64::MAX / 1000));
956 let mut buf = BytesMut::new();
957 let err = item.encode(&mut buf).unwrap_err();
958 assert!(matches!(err, CodecError::OutOfRange { .. }));
959 assert!(buf.is_empty());
961 }
962
963 #[test]
964 fn heartbeat_interval_at_u32_max_encodes() {
965 let item = DataItem::HeartbeatInterval(Duration::from_millis(u32::MAX.into()));
967 let bytes = encode_one(item);
968 let DataItem::HeartbeatInterval(d) = decode_one(&bytes) else {
969 panic!()
970 };
971 assert_eq!(d, Duration::from_millis(u32::MAX.into()));
972 }
973
974 #[test]
975 fn extensions_supported_encodes_three_ids() {
976 let item = DataItem::ExtensionsSupported(vec![
977 ExtensionId(1),
978 ExtensionId(2),
979 ExtensionId(0xFFFF),
980 ]);
981 assert_eq!(
983 encode_one(item),
984 vec![0x00, 0x06, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0xFF, 0xFF]
985 );
986 }
987
988 #[test]
989 fn extensions_supported_empty_roundtrips() {
990 let item = DataItem::ExtensionsSupported(Vec::new());
991 let bytes = encode_one(item);
992 assert_eq!(bytes, vec![0x00, 0x06, 0x00, 0x00]);
994 let DataItem::ExtensionsSupported(ids) = decode_one(&bytes) else {
995 panic!()
996 };
997 assert!(ids.is_empty());
998 }
999
1000 #[test]
1001 fn extensions_supported_odd_length_rejected() {
1002 let raw = RawDataItem {
1003 type_id: DataItemType::EXTENSIONS_SUPPORTED,
1004 value: Bytes::from_static(&[0x00, 0x01, 0x00]),
1005 };
1006 let err = DataItem::decode(raw).unwrap_err();
1007 assert!(matches!(
1008 err,
1009 CodecError::InvalidDataItemLength {
1010 kind: DataItemType::EXTENSIONS_SUPPORTED,
1011 ..
1012 }
1013 ));
1014 }
1015
1016 #[test]
1017 fn mac_address_eui48_encodes() {
1018 let item = DataItem::MacAddress(MacAddress::Eui48([0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01]));
1019 assert_eq!(
1021 encode_one(item),
1022 vec![0x00, 0x07, 0x00, 0x06, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01]
1023 );
1024 }
1025
1026 #[test]
1027 fn mac_address_eui64_encodes() {
1028 let item = DataItem::MacAddress(MacAddress::Eui64([
1029 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1030 ]));
1031 assert_eq!(
1033 encode_one(item),
1034 vec![
1035 0x00, 0x07, 0x00, 0x08, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1036 ]
1037 );
1038 }
1039
1040 #[test]
1041 fn mac_address_eui48_roundtrips() {
1042 let bytes = encode_one(DataItem::MacAddress(MacAddress::Eui48([
1043 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1044 ])));
1045 let DataItem::MacAddress(mac) = decode_one(&bytes) else {
1046 panic!("expected MacAddress variant")
1047 };
1048 assert!(mac.is_eui48());
1049 assert_eq!(mac.as_bytes(), &[0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
1050 }
1051
1052 #[test]
1053 fn mac_address_eui64_roundtrips() {
1054 let bytes = encode_one(DataItem::MacAddress(MacAddress::Eui64([
1055 0x02, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x01,
1056 ])));
1057 let DataItem::MacAddress(mac) = decode_one(&bytes) else {
1058 panic!("expected MacAddress variant")
1059 };
1060 assert!(mac.is_eui64());
1061 assert_eq!(
1062 mac.as_bytes(),
1063 &[0x02, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x01]
1064 );
1065 }
1066
1067 #[test]
1072 fn mac_address_wrong_length_rejected() {
1073 for bad_len in [0usize, 1, 5, 7, 9, 16] {
1074 let raw = RawDataItem {
1075 type_id: DataItemType::MAC_ADDRESS,
1076 value: Bytes::from(vec![0u8; bad_len]),
1077 };
1078 match DataItem::decode(raw).unwrap_err() {
1079 CodecError::InvalidDataItemLength {
1080 kind: DataItemType::MAC_ADDRESS,
1081 expected: ExpectedLen::OneOf(&[6, 8]),
1082 got,
1083 } => assert_eq!(got, bad_len),
1084 other => panic!("len={bad_len} got unexpected error {other:?}"),
1085 }
1086 }
1087 }
1088
1089 #[test]
1090 fn ipv4_address_add_drop_flag_roundtrips() {
1091 let add = DataItem::Ipv4Address {
1092 add: true,
1093 addr: Ipv4Addr::new(1, 2, 3, 4),
1094 };
1095 let bytes = encode_one(add);
1096 assert_eq!(bytes, vec![0x00, 0x08, 0x00, 0x05, 0x01, 1, 2, 3, 4]);
1097 let DataItem::Ipv4Address { add, addr } = decode_one(&bytes) else {
1098 panic!()
1099 };
1100 assert!(add);
1101 assert_eq!(addr, Ipv4Addr::new(1, 2, 3, 4));
1102
1103 let drop = DataItem::Ipv4Address {
1104 add: false,
1105 addr: Ipv4Addr::new(1, 2, 3, 4),
1106 };
1107 let bytes = encode_one(drop);
1108 assert_eq!(bytes[4], 0x00);
1109 let DataItem::Ipv4Address { add, .. } = decode_one(&bytes) else {
1110 panic!()
1111 };
1112 assert!(!add);
1113 }
1114
1115 #[test]
1116 fn ipv6_address_roundtrips() {
1117 let item = DataItem::Ipv6Address {
1118 add: true,
1119 addr: "2001:db8::1".parse().unwrap(),
1120 };
1121 let bytes = encode_one(item);
1122 assert_eq!(bytes.len(), 4 + 17);
1123 let DataItem::Ipv6Address { add, addr } = decode_one(&bytes) else {
1124 panic!()
1125 };
1126 assert!(add);
1127 assert_eq!(addr, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
1128 }
1129
1130 #[test]
1131 fn ipv4_attached_subnet_encodes() {
1132 let item = DataItem::Ipv4AttachedSubnet {
1133 add: true,
1134 subnet: "10.0.0.0/24".parse().unwrap(),
1135 };
1136 assert_eq!(
1138 encode_one(item),
1139 vec![0x00, 0x0A, 0x00, 0x06, 0x01, 10, 0, 0, 0, 24]
1140 );
1141 }
1142
1143 #[test]
1144 fn ipv4_attached_subnet_bad_prefix_rejected() {
1145 let raw = RawDataItem {
1146 type_id: DataItemType::IPV4_ATTACHED_SUBNET,
1147 value: Bytes::from_static(&[0x01, 10, 0, 0, 0, 33]),
1149 };
1150 let err = DataItem::decode(raw).unwrap_err();
1151 assert!(matches!(err, CodecError::OutOfRange { .. }));
1152 }
1153
1154 #[test]
1155 fn ipv6_attached_subnet_roundtrips() {
1156 let item = DataItem::Ipv6AttachedSubnet {
1157 add: true,
1158 subnet: "2001:db8::/32".parse().unwrap(),
1159 };
1160 let bytes = encode_one(item);
1161 assert_eq!(bytes.len(), 4 + 18);
1162 let DataItem::Ipv6AttachedSubnet { add, subnet } = decode_one(&bytes) else {
1163 panic!()
1164 };
1165 assert!(add);
1166 assert_eq!(subnet, "2001:db8::/32".parse::<Ipv6Net>().unwrap());
1167 }
1168
1169 #[test]
1170 fn data_rate_variants_encode_as_u64() {
1171 let pairs = [
1172 (
1173 DataItem::MaxDataRateReceive(1_000_000_000),
1174 DataItemType::MAXIMUM_DATA_RATE_RECEIVE,
1175 ),
1176 (
1177 DataItem::MaxDataRateTransmit(1_000_000_000),
1178 DataItemType::MAXIMUM_DATA_RATE_TRANSMIT,
1179 ),
1180 (
1181 DataItem::CurrentDataRateReceive(500_000_000),
1182 DataItemType::CURRENT_DATA_RATE_RECEIVE,
1183 ),
1184 (
1185 DataItem::CurrentDataRateTransmit(500_000_000),
1186 DataItemType::CURRENT_DATA_RATE_TRANSMIT,
1187 ),
1188 ];
1189 for (item, ty) in pairs {
1190 let bytes = encode_one(item);
1191 assert_eq!(bytes.len(), 4 + 8);
1192 assert_eq!(u16::from_be_bytes([bytes[0], bytes[1]]), ty.0);
1193 assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 8);
1194 }
1195 }
1196
1197 #[test]
1198 fn latency_encodes_microseconds() {
1199 let item = DataItem::Latency(Duration::from_micros(0xDEAD_BEEF));
1200 let bytes = encode_one(item);
1201 assert_eq!(&bytes[..4], &[0x00, 0x10, 0x00, 0x08]);
1203 let DataItem::Latency(d) = decode_one(&bytes) else {
1204 panic!()
1205 };
1206 assert_eq!(d, Duration::from_micros(0xDEAD_BEEF));
1207 }
1208
1209 #[test]
1210 fn resources_encodes_single_byte() {
1211 let item = DataItem::Resources(75);
1212 assert_eq!(encode_one(item), vec![0x00, 0x11, 0x00, 0x01, 0x4B]);
1214 }
1215
1216 #[test]
1217 fn resources_above_100_rejected_on_decode() {
1218 let raw = RawDataItem {
1219 type_id: DataItemType::RESOURCES,
1220 value: Bytes::from_static(&[150]),
1221 };
1222 let err = DataItem::decode(raw).unwrap_err();
1223 assert!(matches!(err, CodecError::OutOfRange { .. }));
1224 }
1225
1226 #[test]
1227 fn resources_above_100_rejected_on_encode() {
1228 let mut buf = BytesMut::new();
1229 let err = DataItem::Resources(150).encode(&mut buf).unwrap_err();
1230 assert!(matches!(err, CodecError::OutOfRange { .. }));
1231 assert!(buf.is_empty());
1233 }
1234
1235 #[test]
1236 fn rlq_above_100_rejected_on_encode() {
1237 for item in [
1238 DataItem::RelativeLinkQualityReceive(101),
1239 DataItem::RelativeLinkQualityTransmit(255),
1240 ] {
1241 let mut buf = BytesMut::new();
1242 let err = item.encode(&mut buf).unwrap_err();
1243 assert!(matches!(err, CodecError::OutOfRange { .. }));
1244 assert!(buf.is_empty());
1245 }
1246 }
1247
1248 #[test]
1249 fn latency_overflow_rejected_on_encode() {
1250 let mut buf = BytesMut::from(&b"prefix"[..]);
1254 let snapshot = buf.clone();
1255 let err = DataItem::Latency(Duration::MAX)
1256 .encode(&mut buf)
1257 .unwrap_err();
1258 assert!(matches!(err, CodecError::OutOfRange { .. }));
1259 assert_eq!(buf, snapshot);
1260 }
1261
1262 #[test]
1263 fn heartbeat_interval_overflow_restores_nonempty_buffer() {
1264 let mut buf = BytesMut::from(&b"existing"[..]);
1268 let snapshot = buf.clone();
1269 let item = DataItem::HeartbeatInterval(Duration::from_secs(u64::MAX / 1000));
1270 let err = item.encode(&mut buf).unwrap_err();
1271 assert!(matches!(err, CodecError::OutOfRange { .. }));
1272 assert_eq!(buf, snapshot);
1273 }
1274
1275 #[test]
1276 fn oversized_data_item_value_rejected() {
1277 let item = DataItem::Status {
1280 code: StatusCode::SUCCESS,
1281 text: "x".repeat(70_000),
1282 };
1283 let mut buf = BytesMut::new();
1284 let err = item.encode(&mut buf).unwrap_err();
1285 match err {
1286 CodecError::OutOfRange { field, value } => {
1287 assert_eq!(field, "data_item_value_length");
1288 assert!(value > u16::MAX as u64);
1289 }
1290 other => panic!("unexpected error: {other:?}"),
1291 }
1292 assert!(buf.is_empty());
1293 }
1294
1295 #[test]
1296 fn oversized_signal_body_rejected() {
1297 let s = Signal::new(SignalType::PEER_OFFER)
1301 .with_item(DataItem::Status {
1302 code: StatusCode::SUCCESS,
1303 text: "x".repeat(40_000),
1304 })
1305 .with_item(DataItem::Status {
1306 code: StatusCode::SUCCESS,
1307 text: "y".repeat(40_000),
1308 });
1309 match s.encode().unwrap_err() {
1310 CodecError::OutOfRange { field, .. } => {
1311 assert_eq!(field, "signal_body_length");
1312 }
1313 other => panic!("unexpected error: {other:?}"),
1314 }
1315 }
1316
1317 #[test]
1318 fn oversized_message_body_rejected() {
1319 let m = Message::new(MessageType::SESSION_UPDATE)
1320 .with_item(DataItem::Status {
1321 code: StatusCode::SUCCESS,
1322 text: "x".repeat(40_000),
1323 })
1324 .with_item(DataItem::Status {
1325 code: StatusCode::SUCCESS,
1326 text: "y".repeat(40_000),
1327 });
1328 match m.encode().unwrap_err() {
1329 CodecError::OutOfRange { field, .. } => {
1330 assert_eq!(field, "message_body_length");
1331 }
1332 other => panic!("unexpected error: {other:?}"),
1333 }
1334 }
1335
1336 #[test]
1337 fn mtu_wrong_length_carries_exact() {
1338 let raw = RawDataItem {
1341 type_id: DataItemType::MTU,
1342 value: Bytes::from_static(&[0x05]),
1343 };
1344 match DataItem::decode(raw).unwrap_err() {
1345 CodecError::InvalidDataItemLength {
1346 kind: DataItemType::MTU,
1347 expected: ExpectedLen::Exact(2),
1348 got: 1,
1349 } => {}
1350 other => panic!("unexpected error: {other:?}"),
1351 }
1352 }
1353
1354 #[test]
1355 fn ipv4_attached_subnet_decode_normalizes_host_bits() {
1356 let raw = RawDataItem {
1360 type_id: DataItemType::IPV4_ATTACHED_SUBNET,
1361 value: Bytes::from_static(&[0x01, 10, 0, 0, 5, 24]),
1362 };
1363 let DataItem::Ipv4AttachedSubnet { add, subnet } = DataItem::decode(raw).unwrap() else {
1364 panic!()
1365 };
1366 assert!(add);
1367 assert_eq!(subnet, "10.0.0.0/24".parse::<Ipv4Net>().unwrap());
1368 }
1369
1370 #[test]
1371 fn ipv6_attached_subnet_decode_normalizes_host_bits() {
1372 let mut wire = vec![0x01u8];
1374 wire.extend_from_slice(&"2001:db8::1234".parse::<Ipv6Addr>().unwrap().octets());
1375 wire.push(32);
1376 let raw = RawDataItem {
1377 type_id: DataItemType::IPV6_ATTACHED_SUBNET,
1378 value: Bytes::from(wire),
1379 };
1380 let DataItem::Ipv6AttachedSubnet { add, subnet } = DataItem::decode(raw).unwrap() else {
1381 panic!()
1382 };
1383 assert!(add);
1384 assert_eq!(subnet, "2001:db8::/32".parse::<Ipv6Net>().unwrap());
1385 }
1386
1387 #[test]
1388 fn non_canonical_subnet_wire_bytes_round_trip_to_canonical() {
1389 let non_canonical = [0x01u8, 10, 0, 0, 5, 24];
1394 let canonical = [0x01u8, 10, 0, 0, 0, 24];
1395
1396 let raw = RawDataItem {
1397 type_id: DataItemType::IPV4_ATTACHED_SUBNET,
1398 value: Bytes::copy_from_slice(&non_canonical),
1399 };
1400 let item = DataItem::decode(raw).unwrap();
1401
1402 let mut out = BytesMut::new();
1403 item.encode(&mut out).unwrap();
1404 assert_eq!(&out[4..], &canonical[..]);
1406 }
1407
1408 #[test]
1409 fn oversized_peer_type_value_rejected() {
1410 let item = DataItem::PeerType {
1411 flags: PeerFlags::default(),
1412 description: "x".repeat(70_000),
1413 };
1414 let mut buf = BytesMut::new();
1415 let err = item.encode(&mut buf).unwrap_err();
1416 match err {
1417 CodecError::OutOfRange { field, value } => {
1418 assert_eq!(field, "data_item_value_length");
1419 assert!(value > u16::MAX as u64);
1420 }
1421 other => panic!("unexpected error: {other:?}"),
1422 }
1423 assert!(buf.is_empty());
1425 }
1426
1427 #[test]
1428 fn oversized_unknown_data_item_value_rejected() {
1429 let item = DataItem::Unknown(RawDataItem {
1433 type_id: DataItemType(4242),
1434 value: Bytes::from(vec![0u8; 70_000]),
1435 });
1436 let mut buf = BytesMut::new();
1437 let err = item.encode(&mut buf).unwrap_err();
1438 match err {
1439 CodecError::OutOfRange { field, value } => {
1440 assert_eq!(field, "data_item_value_length");
1441 assert_eq!(value, 70_000);
1442 }
1443 other => panic!("unexpected error: {other:?}"),
1444 }
1445 assert!(buf.is_empty());
1447 }
1448
1449 #[test]
1450 fn oversized_extensions_supported_value_rejected() {
1451 let item = DataItem::ExtensionsSupported(vec![ExtensionId(0); 33_000]);
1453 let mut buf = BytesMut::new();
1454 let err = item.encode(&mut buf).unwrap_err();
1455 match err {
1456 CodecError::OutOfRange { field, value } => {
1457 assert_eq!(field, "data_item_value_length");
1458 assert!(value > u16::MAX as u64);
1459 }
1460 other => panic!("unexpected error: {other:?}"),
1461 }
1462 assert!(buf.is_empty());
1463 }
1464
1465 #[test]
1466 fn encode_failure_does_not_corrupt_existing_buffer() {
1467 let mut buf = BytesMut::from(&b"prefix"[..]);
1469 let snapshot = buf.clone();
1470 let err = DataItem::Resources(200).encode(&mut buf).unwrap_err();
1471 assert!(matches!(err, CodecError::OutOfRange { .. }));
1472 assert_eq!(buf, snapshot);
1473 }
1474
1475 #[test]
1476 fn signal_encode_propagates_data_item_error() {
1477 let s = Signal::new(SignalType::PEER_OFFER).with_item(DataItem::Resources(200));
1478 let err = s.encode().unwrap_err();
1479 assert!(matches!(err, CodecError::OutOfRange { .. }));
1480 }
1481
1482 #[test]
1483 fn message_encode_propagates_data_item_error() {
1484 let m = Message::new(MessageType::SESSION_UPDATE)
1485 .with_item(DataItem::HeartbeatInterval(Duration::from_millis(1000)))
1486 .with_item(DataItem::RelativeLinkQualityReceive(120));
1487 let err = m.encode().unwrap_err();
1488 assert!(matches!(err, CodecError::OutOfRange { .. }));
1489 }
1490
1491 #[test]
1492 fn relative_link_quality_variants_roundtrip() {
1493 for v in [0u8, 50, 100] {
1494 let rx = DataItem::RelativeLinkQualityReceive(v);
1495 let DataItem::RelativeLinkQualityReceive(out) = decode_one(&encode_one(rx)) else {
1496 panic!()
1497 };
1498 assert_eq!(out, v);
1499
1500 let tx = DataItem::RelativeLinkQualityTransmit(v);
1501 let DataItem::RelativeLinkQualityTransmit(out) = decode_one(&encode_one(tx)) else {
1502 panic!()
1503 };
1504 assert_eq!(out, v);
1505 }
1506 }
1507
1508 #[test]
1509 fn mtu_encodes() {
1510 let item = DataItem::Mtu(1500);
1511 assert_eq!(encode_one(item), vec![0x00, 0x14, 0x00, 0x02, 0x05, 0xDC]);
1513 }
1514
1515 #[test]
1516 fn unknown_data_item_passes_through() {
1517 let raw_in = RawDataItem {
1519 type_id: DataItemType(4242),
1520 value: Bytes::from_static(&[0xAA, 0xBB, 0xCC]),
1521 };
1522 let item = DataItem::decode(raw_in.clone()).unwrap();
1523 match &item {
1524 DataItem::Unknown(r) => {
1525 assert_eq!(r.type_id, DataItemType(4242));
1526 assert_eq!(&r.value[..], &[0xAA, 0xBB, 0xCC]);
1527 }
1528 _ => panic!("expected Unknown"),
1529 }
1530 let mut buf = BytesMut::new();
1532 item.encode(&mut buf).unwrap();
1533 assert_eq!(&buf[..], &[0x10, 0x92, 0x00, 0x03, 0xAA, 0xBB, 0xCC]);
1534 }
1535
1536 #[test]
1537 fn decoder_skips_unknown_data_items_inside_message() {
1538 let m = Message::new(MessageType::SESSION_INITIALIZATION)
1540 .with_item(DataItem::HeartbeatInterval(Duration::from_millis(1000)))
1541 .with_item(DataItem::Unknown(RawDataItem {
1542 type_id: DataItemType(9999),
1543 value: Bytes::from_static(&[0x42]),
1544 }));
1545 let decoded = Message::decode(m.encode().unwrap().freeze()).unwrap();
1546 assert_eq!(decoded.message_type, MessageType::SESSION_INITIALIZATION);
1547 assert_eq!(decoded.data_items.len(), 2);
1548 assert!(matches!(
1549 decoded.data_items[0],
1550 DataItem::HeartbeatInterval(_)
1551 ));
1552 assert!(matches!(decoded.data_items[1], DataItem::Unknown(_)));
1553 }
1554
1555 #[test]
1556 fn signal_with_multiple_unknown_items_round_trips() {
1557 let s = Signal::new(SignalType::PEER_OFFER)
1560 .with_item(DataItem::PeerType {
1561 flags: PeerFlags::default(),
1562 description: "router".into(),
1563 })
1564 .with_item(DataItem::Unknown(RawDataItem {
1565 type_id: DataItemType(7777),
1566 value: Bytes::from_static(&[1, 2, 3]),
1567 }))
1568 .with_item(DataItem::Unknown(RawDataItem {
1569 type_id: DataItemType(8888),
1570 value: Bytes::new(),
1571 }));
1572 let decoded = Signal::decode(s.encode().unwrap().freeze()).unwrap();
1573 assert_eq!(decoded.data_items.len(), 3);
1574 assert!(matches!(decoded.data_items[0], DataItem::PeerType { .. }));
1575 match &decoded.data_items[1] {
1576 DataItem::Unknown(r) => {
1577 assert_eq!(r.type_id, DataItemType(7777));
1578 assert_eq!(&r.value[..], &[1, 2, 3]);
1579 }
1580 _ => panic!(),
1581 }
1582 match &decoded.data_items[2] {
1583 DataItem::Unknown(r) => {
1584 assert_eq!(r.type_id, DataItemType(8888));
1585 assert!(r.value.is_empty());
1586 }
1587 _ => panic!(),
1588 }
1589 }
1590
1591 #[test]
1592 fn signal_with_multiple_items_preserves_order() {
1593 let s = Signal::new(SignalType::PEER_OFFER)
1594 .with_item(DataItem::PeerType {
1595 flags: PeerFlags::default(),
1596 description: "router".into(),
1597 })
1598 .with_item(DataItem::Ipv4ConnectionPoint {
1599 flags: ConnectionPointFlags { use_tls: false },
1600 addr: Ipv4Addr::new(127, 0, 0, 1),
1601 port: Some(854),
1602 });
1603 let decoded = Signal::decode(s.encode().unwrap().freeze()).unwrap();
1604 assert_eq!(decoded.signal_type, SignalType::PEER_OFFER);
1605 assert_eq!(decoded.data_items.len(), 2);
1606 assert!(matches!(decoded.data_items[0], DataItem::PeerType { .. }));
1607 assert!(matches!(
1608 decoded.data_items[1],
1609 DataItem::Ipv4ConnectionPoint { .. }
1610 ));
1611 }
1612
1613 #[test]
1614 fn truncated_message_buffer_rejected() {
1615 let mut buf = BytesMut::new();
1617 buf.put_u16(MessageType::HEARTBEAT.0);
1618 buf.put_u16(4);
1619 buf.put_u8(0xAB);
1620 buf.put_u8(0xCD);
1621 let err = Message::decode(buf.freeze()).unwrap_err();
1622 assert!(matches!(err, CodecError::LengthMismatch { .. }));
1623 }
1624
1625 #[test]
1626 fn truncated_signal_header_rejected() {
1627 let buf = Bytes::from_static(b"DLE");
1628 let err = Signal::decode(buf).unwrap_err();
1629 assert!(matches!(err, CodecError::Truncated { .. }));
1630 }
1631
1632 #[test]
1633 fn invalid_utf8_in_status_text_rejected() {
1634 let raw = RawDataItem {
1636 type_id: DataItemType::STATUS,
1637 value: Bytes::from_static(&[0x00, 0xFF, 0xFE]),
1638 };
1639 let err = DataItem::decode(raw).unwrap_err();
1640 assert!(matches!(err, CodecError::InvalidUtf8(_)));
1641 }
1642}