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