1pub mod model;
4
5#[cfg(feature = "feat-codec-encode")]
6use alloc::string::{String, ToString};
7use core::{fmt, str};
8
9pub use model::AddressPair;
10#[cfg(feature = "feat-codec-decode")]
11pub use model::Decoded;
12
13pub const MAXIMUM_LENGTH: usize = 107;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Header {
20 protocol: FamProto,
22
23 address_pair: AddressPair,
25}
26
27#[cfg(feature = "feat-codec-encode")]
28impl fmt::Display for Header {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self.address_pair {
31 AddressPair::Unspecified => write!(f, "{} {FAM_PROTO_UNKNOWN}\r\n", Self::MAGIC),
32 AddressPair::Inet {
33 src_ip,
34 dst_ip,
35 src_port,
36 dst_port,
37 } => write!(
38 f,
39 "{} {FAM_PROTO_TCP4} {src_ip} {dst_ip} {src_port} {dst_port}\r\n",
40 Self::MAGIC
41 ),
42 AddressPair::Inet6 {
43 src_ip,
44 dst_ip,
45 src_port,
46 dst_port,
47 } => write!(
48 f,
49 "{} {FAM_PROTO_TCP6} {src_ip} {dst_ip} {src_port} {dst_port}\r\n",
50 Self::MAGIC
51 ),
52 }
53 }
54}
55
56impl Header {
57 pub const MAGIC: &'static str = "PROXY";
59
60 pub const fn new(address_pair: AddressPair) -> Self {
62 Header {
63 protocol: match &address_pair {
64 AddressPair::Inet { .. } => FamProto::TCP4,
65 AddressPair::Inet6 { .. } => FamProto::TCP6,
66 AddressPair::Unspecified => FamProto::Unknown,
67 },
68 address_pair,
69 }
70 }
71
72 #[inline]
73 pub const fn address_pair(self) -> AddressPair {
75 self.address_pair
76 }
77
78 #[inline]
79 #[cfg(feature = "feat-codec-encode")]
80 pub fn encode(&self) -> String {
82 self.to_string()
83 }
84
85 #[cfg(feature = "feat-codec-decode")]
86 pub fn decode(header_bytes: &[u8]) -> Result<Decoded, DecodeError> {
109 {
111 use core::cmp::min;
112
113 let magic_length = min(Header::MAGIC.len(), header_bytes.len());
114
115 if header_bytes[..magic_length] != Header::MAGIC.as_bytes()[..magic_length] {
116 return Ok(Decoded::None);
117 }
118 }
119
120 if header_bytes.len() > MAXIMUM_LENGTH {
121 return Err(DecodeError::MalformedData("bytes too long"));
123 }
124
125 let header_str = str::from_utf8(header_bytes).map_err(|_| DecodeError::MalformedData("not UTF-8"))?;
126
127 if !header_str.ends_with("\r\n") {
129 return Err(DecodeError::MalformedData("missing CRLF or trailing data"));
130 }
131
132 let mut header_parts_iter = header_str.split_whitespace();
133
134 let magic = header_parts_iter
136 .next()
137 .ok_or(DecodeError::MalformedData("missing MAGIC"))?;
138
139 if magic != Header::MAGIC {
140 return Ok(Decoded::None);
141 }
142
143 let Some(family_protocol) = header_parts_iter.next() else {
145 return Err(DecodeError::InvalidFamProto);
146 };
147
148 let (protocol, address_pair) = match family_protocol {
150 FAM_PROTO_TCP4 => {
151 let src_ip = header_parts_iter
152 .next()
153 .ok_or(DecodeError::MissingData("SRC_IP"))
154 .and_then(|s| s.parse().map_err(|_| DecodeError::MalformedData("SRC_IP")))?;
155
156 let dst_ip = header_parts_iter
157 .next()
158 .ok_or(DecodeError::MissingData("DST_IP"))
159 .and_then(|s| s.parse().map_err(|_| DecodeError::MalformedData("DST_IP")))?;
160
161 let src_port = header_parts_iter
162 .next()
163 .ok_or(DecodeError::MissingData("SRC_PORT"))
164 .and_then(|s| s.parse::<u16>().map_err(|_| DecodeError::MalformedData("SRC_PORT")))?;
165
166 let dst_port = header_parts_iter
167 .next()
168 .ok_or(DecodeError::MissingData("DST_PORT"))
169 .and_then(|s| s.parse::<u16>().map_err(|_| DecodeError::MalformedData("DST_PORT")))?;
170
171 (
172 FamProto::TCP4,
173 AddressPair::Inet {
174 src_ip,
175 dst_ip,
176 src_port,
177 dst_port,
178 },
179 )
180 }
181 FAM_PROTO_TCP6 => {
182 let src_ip = header_parts_iter
183 .next()
184 .ok_or(DecodeError::MissingData("SRC_IP"))
185 .and_then(|s| s.parse().map_err(|_| DecodeError::MalformedData("SRC_IP")))?;
186
187 let dst_ip = header_parts_iter
188 .next()
189 .ok_or(DecodeError::MissingData("DST_IP"))
190 .and_then(|s| s.parse().map_err(|_| DecodeError::MalformedData("DST_IP")))?;
191
192 let src_port = header_parts_iter
193 .next()
194 .ok_or(DecodeError::MissingData("SRC_PORT"))
195 .and_then(|s| s.parse::<u16>().map_err(|_| DecodeError::MalformedData("SRC_PORT")))?;
196
197 let dst_port = header_parts_iter
198 .next()
199 .ok_or(DecodeError::MissingData("DST_PORT"))
200 .and_then(|s| s.parse::<u16>().map_err(|_| DecodeError::MalformedData("DST_PORT")))?;
201
202 (
203 FamProto::TCP6,
204 AddressPair::Inet6 {
205 src_ip,
206 dst_ip,
207 src_port,
208 dst_port,
209 },
210 )
211 }
212 FAM_PROTO_UNKNOWN => {
213 (FamProto::Unknown, AddressPair::Unspecified)
215 }
216 _ => {
217 return Err(DecodeError::InvalidFamProto);
218 }
219 };
220
221 Ok(Decoded::Some(Self { protocol, address_pair }))
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226enum FamProto {
229 Unknown,
231
232 TCP4,
234
235 TCP6,
237}
238
239#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
240const FAM_PROTO_UNKNOWN: &str = "UNKNOWN";
241
242#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
243const FAM_PROTO_TCP4: &str = "TCP4";
244
245#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
246const FAM_PROTO_TCP6: &str = "TCP6";
247
248#[cfg(feature = "feat-codec-decode")]
249#[derive(Debug)]
250#[derive(thiserror::Error)]
251pub enum DecodeError {
253 #[error("Invalid PROXY addr family & protocol")]
254 InvalidFamProto,
256
257 #[error("Missing data: {0}")]
258 MissingData(&'static str),
260
261 #[error("Malformed data: {0}")]
262 MalformedData(&'static str),
265
266 #[error("Trailing data after the PROXY Protocol v1 header")]
267 TrailingData,
269}
270
271#[cfg(test)]
272mod tests {
273 use core::net::{Ipv4Addr, Ipv6Addr};
274
275 use super::*;
276
277 #[test]
278 fn test_header_new_tcp4() {
279 let src_ip = Ipv4Addr::new(192, 168, 1, 1);
280 let dst_ip = Ipv4Addr::new(10, 0, 0, 1);
281 let src_port = 8080;
282 let dst_port = 80;
283
284 let address_pair = AddressPair::Inet {
285 src_ip,
286 dst_ip,
287 src_port,
288 dst_port,
289 };
290 let header = Header::new(address_pair);
291
292 assert_eq!(header.protocol, FamProto::TCP4);
293 match header.address_pair {
294 AddressPair::Inet {
295 src_ip: s_ip,
296 dst_ip: d_ip,
297 src_port: s_port,
298 dst_port: d_port,
299 } => {
300 assert_eq!(s_ip, src_ip);
301 assert_eq!(d_ip, dst_ip);
302 assert_eq!(s_port, src_port);
303 assert_eq!(d_port, dst_port);
304 }
305 _ => panic!("Expected Inet address pair"),
306 }
307 }
308
309 #[test]
310 fn test_header_new_tcp6() {
311 let src_ip = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
312 let dst_ip = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
313 let src_port = 8080;
314 let dst_port = 80;
315
316 let address_pair = AddressPair::Inet6 {
317 src_ip,
318 dst_ip,
319 src_port,
320 dst_port,
321 };
322 let header = Header::new(address_pair);
323
324 assert_eq!(header.protocol, FamProto::TCP6);
325 match header.address_pair {
326 AddressPair::Inet6 {
327 src_ip: s_ip,
328 dst_ip: d_ip,
329 src_port: s_port,
330 dst_port: d_port,
331 } => {
332 assert_eq!(s_ip, src_ip);
333 assert_eq!(d_ip, dst_ip);
334 assert_eq!(s_port, src_port);
335 assert_eq!(d_port, dst_port);
336 }
337 _ => panic!("Expected Inet6 address pair"),
338 }
339 }
340
341 #[test]
342 fn test_header_new_unknown() {
343 let header = Header::new(AddressPair::Unspecified);
344 assert_eq!(header.protocol, FamProto::Unknown);
345 assert_eq!(header.address_pair, AddressPair::Unspecified);
346 }
347
348 #[test]
349 #[cfg(feature = "feat-codec-encode")]
350 fn test_encode_tcp4() {
351 let src_ip = Ipv4Addr::new(192, 168, 1, 1);
352 let dst_ip = Ipv4Addr::new(10, 0, 0, 1);
353 let src_port = 8080;
354 let dst_port = 80;
355
356 let address_pair = AddressPair::Inet {
357 src_ip,
358 dst_ip,
359 src_port,
360 dst_port,
361 };
362 let header = Header::new(address_pair);
363
364 let encoded = header.encode();
365 assert_eq!(encoded, "PROXY TCP4 192.168.1.1 10.0.0.1 8080 80\r\n");
366 }
367
368 #[test]
369 #[cfg(feature = "feat-codec-encode")]
370 fn test_encode_tcp6() {
371 let src_ip = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
372 let dst_ip = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
373 let src_port = 8080;
374 let dst_port = 80;
375
376 let address_pair = AddressPair::Inet6 {
377 src_ip,
378 dst_ip,
379 src_port,
380 dst_port,
381 };
382 let header = Header::new(address_pair);
383
384 let encoded = header.encode();
385 assert_eq!(encoded, "PROXY TCP6 2001:db8::1 fe80::1 8080 80\r\n");
386 }
387
388 #[test]
389 #[cfg(feature = "feat-codec-encode")]
390 fn test_encode_unknown() {
391 let header = Header::new(AddressPair::Unspecified);
392 let encoded = header.encode();
393 assert_eq!(encoded, "PROXY UNKNOWN\r\n");
394 }
395
396 #[test]
397 #[cfg(feature = "feat-codec-decode")]
398 fn test_decode_tcp4_valid() {
399 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 80\r\n";
400 let Decoded::Some(header) = Header::decode(input).unwrap() else {
401 unreachable!()
402 };
403
404 assert_eq!(header.protocol, FamProto::TCP4);
405 match header.address_pair {
406 AddressPair::Inet {
407 src_ip,
408 dst_ip,
409 src_port,
410 dst_port,
411 } => {
412 assert_eq!(src_ip, Ipv4Addr::new(192, 168, 1, 1));
413 assert_eq!(src_port, 8080);
414 assert_eq!(dst_ip, Ipv4Addr::new(10, 0, 0, 1));
415 assert_eq!(dst_port, 80);
416 }
417 _ => panic!("Expected Inet address pair"),
418 }
419 }
420
421 #[test]
422 #[cfg(feature = "feat-codec-decode")]
423 fn test_decode_tcp6_valid() {
424 let input = b"PROXY TCP6 2001:db8::1 fe80::1 8080 80\r\n";
425 let Decoded::Some(header) = Header::decode(input).unwrap() else {
426 unreachable!()
427 };
428
429 assert_eq!(header.protocol, FamProto::TCP6);
430 match header.address_pair {
431 AddressPair::Inet6 {
432 src_ip,
433 dst_ip,
434 src_port,
435 dst_port,
436 } => {
437 assert_eq!(src_ip, Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
438 assert_eq!(src_port, 8080);
439 assert_eq!(dst_ip, Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
440 assert_eq!(dst_port, 80);
441 }
442 _ => panic!("Expected Inet6 address pair"),
443 }
444 }
445
446 #[test]
447 #[cfg(feature = "feat-codec-decode")]
448 fn test_decode_unknown_valid() {
449 let input = b"PROXY UNKNOWN\r\n";
450 let Decoded::Some(header) = Header::decode(input).unwrap() else {
451 unreachable!()
452 };
453
454 assert_eq!(header.protocol, FamProto::Unknown);
455 assert_eq!(header.address_pair, AddressPair::Unspecified);
456 }
457
458 #[test]
459 #[cfg(feature = "feat-codec-decode")]
460 fn test_decode_unknown_with_extra_data() {
461 let input = b"PROXY UNKNOWN some extra data here\r\n";
462 let Decoded::Some(header) = Header::decode(input).unwrap() else {
463 unreachable!()
464 };
465
466 assert_eq!(header.protocol, FamProto::Unknown);
467 assert_eq!(header.address_pair, AddressPair::Unspecified);
468 }
469
470 #[test]
471 #[cfg(feature = "feat-codec-decode")]
472 fn test_decode_too_long() {
473 let mut input = [b'A'; MAXIMUM_LENGTH + 1];
475 input[0] = b'P';
476 input[1] = b'R';
477 input[2] = b'O';
478 input[3] = b'X';
479 input[4] = b'Y';
480
481 let result = Header::decode(&input);
482
483 assert!(matches!(result, Err(DecodeError::MalformedData("bytes too long"))));
484 }
485
486 #[test]
487 #[cfg(feature = "feat-codec-decode")]
488 fn test_decode_not_utf8() {
489 let input = b"PROXY TCP4 \xff\xff\xff\xff 10.0.0.1 8080 80\r\n";
490 let result = Header::decode(input);
491
492 assert!(matches!(result, Err(DecodeError::MalformedData("not UTF-8"))));
493 }
494
495 #[test]
496 #[cfg(feature = "feat-codec-decode")]
497 fn test_decode_missing_crlf() {
498 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 80";
499 let result = Header::decode(input);
500
501 assert!(matches!(
502 result,
503 Err(DecodeError::MalformedData("missing CRLF or trailing data"))
504 ));
505 }
506
507 #[test]
508 #[cfg(feature = "feat-codec-decode")]
509 fn test_decode_trailing_data() {
510 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 80\r\ntrailing data";
511 let result = Header::decode(input);
512
513 assert!(matches!(
514 result,
515 Err(DecodeError::MalformedData("missing CRLF or trailing data"))
516 ));
517 }
518
519 #[test]
520 #[cfg(feature = "feat-codec-decode")]
521 fn test_decode_no_magic() {
522 let input = b"NOTPROXY TCP4 192.168.1.1 10.0.0.1 8080 80\r\n";
523 let result = Header::decode(input);
524
525 assert!(matches!(result, Ok(Decoded::None)));
526 }
527
528 #[test]
529 #[cfg(feature = "feat-codec-decode")]
530 fn test_decode_empty_input() {
531 let input = b"";
532 let result = Header::decode(input);
533
534 assert!(matches!(
537 result,
538 Err(DecodeError::MalformedData("missing CRLF or trailing data"))
539 ));
540 }
541
542 #[test]
543 #[cfg(feature = "feat-codec-decode")]
544 fn test_decode_only_magic() {
545 let input = b"PROXY\r\n";
546 let result = Header::decode(input);
547
548 assert!(matches!(result, Err(DecodeError::InvalidFamProto)));
549 }
550
551 #[test]
552 #[cfg(feature = "feat-codec-decode")]
553 fn test_decode_invalid_protocol() {
554 let input = b"PROXY INVALID 192.168.1.1 10.0.0.1 8080 80\r\n";
555 let result = Header::decode(input);
556
557 assert!(matches!(result, Err(DecodeError::InvalidFamProto)));
558 }
559
560 #[test]
561 #[cfg(feature = "feat-codec-decode")]
562 fn test_decode_tcp4_missing_src_ip() {
563 let input = b"PROXY TCP4\r\n";
564 let result = Header::decode(input);
565
566 assert!(matches!(result, Err(DecodeError::MissingData("SRC_IP"))));
567 }
568
569 #[test]
570 #[cfg(feature = "feat-codec-decode")]
571 fn test_decode_tcp4_missing_dst_ip() {
572 let input = b"PROXY TCP4 192.168.1.1\r\n";
573 let result = Header::decode(input);
574
575 assert!(matches!(result, Err(DecodeError::MissingData("DST_IP"))));
576 }
577
578 #[test]
579 #[cfg(feature = "feat-codec-decode")]
580 fn test_decode_tcp4_missing_src_port() {
581 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1\r\n";
582 let result = Header::decode(input);
583
584 assert!(matches!(result, Err(DecodeError::MissingData("SRC_PORT"))));
585 }
586
587 #[test]
588 #[cfg(feature = "feat-codec-decode")]
589 fn test_decode_tcp4_missing_dst_port() {
590 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080\r\n";
591 let result = Header::decode(input);
592
593 assert!(matches!(result, Err(DecodeError::MissingData("DST_PORT"))));
594 }
595
596 #[test]
597 #[cfg(feature = "feat-codec-decode")]
598 fn test_decode_tcp4_invalid_src_ip() {
599 let input = b"PROXY TCP4 999.999.999.999 10.0.0.1 8080 80\r\n";
600 let result = Header::decode(input);
601
602 assert!(matches!(result, Err(DecodeError::MalformedData("SRC_IP"))));
603 }
604
605 #[test]
606 #[cfg(feature = "feat-codec-decode")]
607 fn test_decode_tcp4_invalid_dst_ip() {
608 let input = b"PROXY TCP4 192.168.1.1 invalid_ip 8080 80\r\n";
609 let result = Header::decode(input);
610
611 assert!(matches!(result, Err(DecodeError::MalformedData("DST_IP"))));
612 }
613
614 #[test]
615 #[cfg(feature = "feat-codec-decode")]
616 fn test_decode_tcp4_invalid_src_port() {
617 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 65536 80\r\n";
618 let result = Header::decode(input);
619
620 assert!(matches!(result, Err(DecodeError::MalformedData("SRC_PORT"))));
621 }
622
623 #[test]
624 #[cfg(feature = "feat-codec-decode")]
625 fn test_decode_tcp4_invalid_dst_port() {
626 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 invalid_port\r\n";
627 let result = Header::decode(input);
628
629 assert!(matches!(result, Err(DecodeError::MalformedData("DST_PORT"))));
630 }
631
632 #[test]
633 #[cfg(feature = "feat-codec-decode")]
634 fn test_decode_tcp6_missing_fields() {
635 let input = b"PROXY TCP6 2001:db8::1\r\n";
636 let result = Header::decode(input);
637
638 assert!(matches!(result, Err(DecodeError::MissingData("DST_IP"))));
639 }
640
641 #[test]
642 #[cfg(feature = "feat-codec-decode")]
643 fn test_decode_tcp6_invalid_ip() {
644 let input = b"PROXY TCP6 invalid::ip fe80::1 8080 80\r\n";
645 let result = Header::decode(input);
646
647 assert!(matches!(result, Err(DecodeError::MalformedData("SRC_IP"))));
648 }
649
650 #[test]
651 #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
652 fn test_roundtrip_tcp4() {
653 let src_ip = Ipv4Addr::new(192, 168, 1, 100);
654 let dst_ip = Ipv4Addr::new(10, 0, 0, 50);
655 let src_port = 12345;
656 let dst_port = 443;
657
658 let address_pair = AddressPair::Inet {
659 src_ip,
660 dst_ip,
661 src_port,
662 dst_port,
663 };
664 let original = Header::new(address_pair);
665
666 let encoded = original.encode();
667 let Decoded::Some(decoded) = Header::decode(encoded.as_bytes()).unwrap() else {
668 unreachable!()
669 };
670
671 assert_eq!(original, decoded);
672 }
673
674 #[test]
675 #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
676 fn test_roundtrip_tcp6() {
677 let src_ip = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x100);
678 let dst_ip = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x50);
679 let src_port = 12345;
680 let dst_port = 443;
681
682 let address_pair = AddressPair::Inet6 {
683 src_ip,
684 dst_ip,
685 src_port,
686 dst_port,
687 };
688 let original = Header::new(address_pair);
689
690 let encoded = original.encode();
691 let Decoded::Some(decoded) = Header::decode(encoded.as_bytes()).unwrap() else {
692 unreachable!()
693 };
694
695 assert_eq!(original, decoded);
696 }
697
698 #[test]
699 #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
700 fn test_roundtrip_unknown() {
701 let original = Header::new(AddressPair::Unspecified);
702
703 let encoded = original.encode();
704 let Decoded::Some(decoded) = Header::decode(encoded.as_bytes()).unwrap() else {
705 unreachable!()
706 };
707
708 assert_eq!(original, decoded);
709 }
710
711 #[test]
712 fn test_maximum_length_constant() {
713 let longest_possible = "PROXY UNKNOWN ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff \
718 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n";
719 assert!(longest_possible.len() == MAXIMUM_LENGTH);
720 }
721
722 #[test]
723 #[cfg(feature = "feat-codec-decode")]
724 fn test_decode_edge_case_whitespace() {
725 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 80\r\n";
727 let Decoded::Some(header) = Header::decode(input).unwrap() else {
728 unreachable!()
729 };
730
731 assert_eq!(header.protocol, FamProto::TCP4);
732 match header.address_pair {
733 AddressPair::Inet {
734 src_ip,
735 dst_ip,
736 src_port,
737 dst_port,
738 } => {
739 assert_eq!(src_ip, Ipv4Addr::new(192, 168, 1, 1));
740 assert_eq!(src_port, 8080);
741 assert_eq!(dst_ip, Ipv4Addr::new(10, 0, 0, 1));
742 assert_eq!(dst_port, 80);
743 }
744 _ => panic!("Expected Inet address pair"),
745 }
746 }
747
748 #[test]
750 #[cfg(feature = "feat-codec-decode")]
751 fn test_decode_exact_maximum_length() {
752 let input = b"PROXY TCP6 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n";
754 assert!(input.len() <= MAXIMUM_LENGTH);
755 let Decoded::Some(header) = Header::decode(input).unwrap() else {
756 unreachable!()
757 };
758
759 assert_eq!(header.protocol, FamProto::TCP6);
760 }
761
762 #[test]
763 #[cfg(feature = "feat-codec-decode")]
764 fn test_decode_minimal_tcp4() {
765 let input = b"PROXY TCP4 0.0.0.0 0.0.0.0 0 0\r\n";
766 let Decoded::Some(header) = Header::decode(input).unwrap() else {
767 unreachable!()
768 };
769
770 assert_eq!(header.protocol, FamProto::TCP4);
771 match header.address_pair {
772 AddressPair::Inet {
773 src_ip,
774 dst_ip,
775 src_port,
776 dst_port,
777 } => {
778 assert_eq!(src_ip, Ipv4Addr::new(0, 0, 0, 0));
779 assert_eq!(src_port, 0);
780 assert_eq!(dst_ip, Ipv4Addr::new(0, 0, 0, 0));
781 assert_eq!(dst_port, 0);
782 }
783 _ => panic!("Expected Inet address pair"),
784 }
785 }
786
787 #[test]
788 #[cfg(feature = "feat-codec-decode")]
789 fn test_decode_minimal_tcp6() {
790 let input = b"PROXY TCP6 :: :: 0 0\r\n";
791 let Decoded::Some(header) = Header::decode(input).unwrap() else {
792 unreachable!()
793 };
794
795 assert_eq!(header.protocol, FamProto::TCP6);
796 match header.address_pair {
797 AddressPair::Inet6 {
798 src_ip,
799 dst_ip,
800 src_port,
801 dst_port,
802 } => {
803 assert_eq!(src_ip, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
804 assert_eq!(src_port, 0);
805 assert_eq!(dst_ip, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
806 assert_eq!(dst_port, 0);
807 }
808 _ => panic!("Expected Inet6 address pair"),
809 }
810 }
811
812 #[test]
813 #[cfg(feature = "feat-codec-decode")]
814 fn test_decode_mixed_case_protocol() {
815 let input = b"PROXY tcp4 192.168.1.1 10.0.0.1 8080 80\r\n";
817 let result = Header::decode(input);
818
819 assert!(matches!(result, Err(DecodeError::InvalidFamProto)));
820 }
821
822 #[test]
823 #[cfg(feature = "feat-codec-decode")]
824 fn test_decode_different_line_ending() {
825 let input = b"PROXY TCP4 192.168.1.1 10.0.0.1 8080 80\n";
827 let result = Header::decode(input);
828
829 assert!(matches!(
830 result,
831 Err(DecodeError::MalformedData("missing CRLF or trailing data"))
832 ));
833 }
834}