proxy_protocol_codec/
v2.rs

1//! PROXY Protocol v2.
2
3#[cfg(any(feature = "feat-codec-decode", feature = "feat-codec-encode"))]
4pub mod codec;
5pub mod model;
6
7#[cfg(any(feature = "feat-codec-decode", feature = "feat-codec-encode"))]
8pub use codec::*;
9pub use model::{AddressPair, Command, ExtensionRef, ExtensionType, Protocol, HEADER_SIZE};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12/// The PROXY Protocol v2 header.
13///
14/// For encoding and decoding stuffs, please refer to [`codec::HeaderEncoder`]
15/// and [`codec::HeaderDecoder`].
16pub struct Header {
17    /// See [`Command`].
18    command: Command,
19
20    /// See [`Protocol`].
21    protocol: Protocol,
22
23    /// See [`AddressPair`].
24    address_pair: AddressPair,
25}
26
27impl Header {
28    /// The magic bytes that identify the PROXY Protocol v2 header.
29    pub const MAGIC: &'static [u8; 12] = crate::Version::MAGIC_V2;
30
31    #[inline]
32    /// Creates a new `Header` with [`Command::Local`]
33    pub const fn new_local() -> Self {
34        Self {
35            command: Command::Local,
36            protocol: Protocol::Unspecified,
37            address_pair: AddressPair::Unspecified,
38        }
39    }
40
41    #[inline]
42    /// Creates a new `Header` with [`Command::Proxy`]
43    pub const fn new_proxy(protocol: Protocol, address_pair: AddressPair) -> Self {
44        Self {
45            command: Command::Proxy,
46            protocol,
47            address_pair,
48        }
49    }
50
51    #[inline]
52    /// Returns the command of the header.
53    pub const fn command(&self) -> &Command {
54        &self.command
55    }
56
57    #[inline]
58    /// Returns the protocol of the header.
59    pub const fn protocol(&self) -> &Protocol {
60        &self.protocol
61    }
62
63    #[inline]
64    /// Returns the address pair of the header.
65    pub const fn address_pair(&self) -> &AddressPair {
66        &self.address_pair
67    }
68
69    #[cfg(feature = "feat-codec-encode")]
70    #[inline]
71    /// See [`HeaderEncoder::encode`].
72    pub fn encode(&self) -> Encoded {
73        HeaderEncoder::encode(self)
74    }
75
76    #[cfg(feature = "feat-codec-decode")]
77    #[inline]
78    /// See [`HeaderDecoder::decode`].
79    ///
80    /// # Errors
81    ///
82    /// See [`DecodeError`].
83    pub fn decode(encoded: &[u8]) -> Result<Decoded<'_>, DecodeError> {
84        HeaderDecoder::decode(encoded)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use alloc::vec;
91
92    use super::*;
93
94    #[test]
95    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
96    fn encode_decode_header_no_extension_local() {
97        let header = Header::new_local();
98
99        _encode_decode_header_no_extension(header);
100    }
101
102    #[test]
103    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
104    fn encode_decode_header_no_extension_proxy_unspec_unspec() {
105        let header = Header::new_proxy(Protocol::Unspecified, AddressPair::Unspecified);
106
107        _encode_decode_header_no_extension(header);
108    }
109
110    #[test]
111    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
112    fn encode_decode_header_no_extension_proxy_stream_unspec() {
113        let header = Header::new_proxy(Protocol::Stream, AddressPair::Unspecified);
114
115        _encode_decode_header_no_extension(header);
116    }
117
118    #[test]
119    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
120    fn encode_decode_header_no_extension_proxy_dgram_unspec() {
121        let header = Header::new_proxy(Protocol::Dgram, AddressPair::Unspecified);
122
123        _encode_decode_header_no_extension(header);
124    }
125
126    #[test]
127    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
128    fn encode_decode_header_no_extension_proxy_unspec_inet() {
129        let header = Header::new_proxy(
130            Protocol::Unspecified,
131            AddressPair::Inet {
132                src_ip: "127.0.0.1".parse().unwrap(),
133                dst_ip: "127.0.0.2".parse().unwrap(),
134                src_port: 8080,
135                dst_port: 80,
136            },
137        );
138
139        _encode_decode_header_no_extension(header);
140    }
141
142    #[test]
143    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
144    fn encode_decode_header_no_extension_proxy_stream_inet() {
145        let header = Header::new_proxy(
146            Protocol::Stream,
147            AddressPair::Inet {
148                src_ip: "127.0.0.1".parse().unwrap(),
149                dst_ip: "127.0.0.2".parse().unwrap(),
150                src_port: 8080,
151                dst_port: 80,
152            },
153        );
154
155        _encode_decode_header_no_extension(header);
156    }
157
158    #[test]
159    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
160    fn encode_decode_header_no_extension_proxy_dgram_inet() {
161        let header = Header::new_proxy(
162            Protocol::Dgram,
163            AddressPair::Inet {
164                src_ip: "127.0.0.1".parse().unwrap(),
165                dst_ip: "127.0.0.2".parse().unwrap(),
166                src_port: 8080,
167                dst_port: 80,
168            },
169        );
170
171        _encode_decode_header_no_extension(header);
172    }
173
174    #[test]
175    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
176    fn encode_decode_header_no_extension_proxy_unspec_inet6() {
177        let header = Header::new_proxy(
178            Protocol::Unspecified,
179            AddressPair::Inet6 {
180                src_ip: "::1".parse().unwrap(),
181                dst_ip: "::2".parse().unwrap(),
182                src_port: 8080,
183                dst_port: 80,
184            },
185        );
186
187        _encode_decode_header_no_extension(header);
188    }
189
190    #[test]
191    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
192    fn encode_decode_header_no_extension_proxy_stream_inet6() {
193        let header = Header::new_proxy(
194            Protocol::Stream,
195            AddressPair::Inet6 {
196                src_ip: "::1".parse().unwrap(),
197                dst_ip: "::2".parse().unwrap(),
198                src_port: 8080,
199                dst_port: 80,
200            },
201        );
202
203        _encode_decode_header_no_extension(header);
204    }
205
206    #[test]
207    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
208    fn encode_decode_header_no_extension_proxy_dgram_inet6() {
209        let header = Header::new_proxy(
210            Protocol::Dgram,
211            AddressPair::Inet6 {
212                src_ip: "::1".parse().unwrap(),
213                dst_ip: "::2".parse().unwrap(),
214                src_port: 8080,
215                dst_port: 80,
216            },
217        );
218
219        _encode_decode_header_no_extension(header);
220    }
221
222    #[test]
223    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
224    fn encode_decode_header_no_extension_proxy_unspec_unix() {
225        let header = Header::new_proxy(
226            Protocol::Unspecified,
227            AddressPair::Unix {
228                src_addr: [b'S'; 108],
229                dst_addr: [b'D'; 108],
230            },
231        );
232
233        _encode_decode_header_no_extension(header);
234    }
235
236    #[test]
237    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
238    fn encode_decode_header_no_extension_proxy_stream_unix() {
239        let header = Header::new_proxy(
240            Protocol::Stream,
241            AddressPair::Unix {
242                src_addr: [b'S'; 108],
243                dst_addr: [b'D'; 108],
244            },
245        );
246
247        _encode_decode_header_no_extension(header);
248    }
249
250    #[test]
251    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
252    fn encode_decode_header_no_extension_proxy_dgram_unix() {
253        let header = Header::new_proxy(
254            Protocol::Dgram,
255            AddressPair::Unix {
256                src_addr: [b'S'; 108],
257                dst_addr: [b'D'; 108],
258            },
259        );
260
261        _encode_decode_header_no_extension(header);
262    }
263
264    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
265    fn _encode_decode_header_no_extension(header: Header) {
266        let encoded = codec::HeaderEncoder::encode(&header).finish().unwrap();
267
268        let decoded = codec::HeaderDecoder::decode(&encoded).unwrap();
269        if let codec::Decoded::Some(codec::DecodedHeader {
270            header: decoded_header,
271            extensions,
272        }) = decoded
273        {
274            assert_eq!(decoded_header, header);
275            let mut extensions_iter = extensions.into_iter();
276            assert!(extensions_iter.next().is_none());
277        } else {
278            panic!("Expected decoded header");
279        }
280    }
281
282    // Tests with extensions
283    #[test]
284    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
285    fn encode_decode_header_with_extension_local_alpn() {
286        let header = Header::new_local();
287        let alpn_data = b"http/1.1";
288
289        _encode_decode_header_with_extension(
290            header,
291            |encoder| encoder.write_ext_alpn(alpn_data),
292            |mut extensions| {
293                let ext = extensions.next().unwrap().unwrap();
294                assert_eq!(ext.typ().unwrap(), ExtensionType::ALPN);
295                assert_eq!(ext.payload(), alpn_data);
296                assert!(extensions.next().is_none());
297            },
298        );
299    }
300
301    #[test]
302    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
303    fn encode_decode_header_with_extension_proxy_authority() {
304        let header = Header::new_proxy(
305            Protocol::Stream,
306            AddressPair::Inet {
307                src_ip: "127.0.0.1".parse().unwrap(),
308                dst_ip: "127.0.0.2".parse().unwrap(),
309                src_port: 8080,
310                dst_port: 80,
311            },
312        );
313        let authority_data = b"example.com";
314
315        _encode_decode_header_with_extension(
316            header,
317            |encoder| encoder.write_ext_authority(authority_data),
318            |mut extensions| {
319                let ext = extensions.next().unwrap().unwrap();
320                assert_eq!(ext.typ().unwrap(), ExtensionType::Authority);
321                assert_eq!(ext.payload(), authority_data);
322                assert!(extensions.next().is_none());
323            },
324        );
325    }
326
327    #[test]
328    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
329    fn encode_decode_header_with_extension_proxy_unique_id() {
330        let header = Header::new_proxy(
331            Protocol::Stream,
332            AddressPair::Inet6 {
333                src_ip: "::1".parse().unwrap(),
334                dst_ip: "::2".parse().unwrap(),
335                src_port: 8080,
336                dst_port: 80,
337            },
338        );
339        let unique_id = b"unique-connection-id-12345";
340
341        _encode_decode_header_with_extension(
342            header,
343            |encoder| encoder.write_ext_unique_id(unique_id),
344            |mut extensions| {
345                let ext = extensions.next().unwrap().unwrap();
346                assert_eq!(ext.typ().unwrap(), ExtensionType::NetworkNamespace); // Note: write_ext_unique_id has a bug, it uses NetworkNamespace
347                assert_eq!(ext.payload(), unique_id);
348                assert!(extensions.next().is_none());
349            },
350        );
351    }
352
353    #[test]
354    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
355    fn encode_decode_header_with_extension_proxy_network_namespace() {
356        let header = Header::new_proxy(
357            Protocol::Dgram,
358            AddressPair::Unix {
359                src_addr: [b'S'; 108],
360                dst_addr: [b'D'; 108],
361            },
362        );
363        let netns_data = b"my-namespace";
364
365        _encode_decode_header_with_extension(
366            header,
367            |encoder| encoder.write_ext_network_namespace(netns_data),
368            |mut extensions| {
369                let ext = extensions.next().unwrap().unwrap();
370                assert_eq!(ext.typ().unwrap(), ExtensionType::NetworkNamespace);
371                assert_eq!(ext.payload(), netns_data);
372                assert!(extensions.next().is_none());
373            },
374        );
375    }
376
377    #[test]
378    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
379    fn encode_decode_header_with_extension_proxy_no_op() {
380        let header = Header::new_proxy(
381            Protocol::Stream,
382            AddressPair::Inet {
383                src_ip: "127.0.0.1".parse().unwrap(),
384                dst_ip: "127.0.0.2".parse().unwrap(),
385                src_port: 8080,
386                dst_port: 80,
387            },
388        );
389        let padding_size = 5u16;
390
391        _encode_decode_header_with_extension(
392            header,
393            |encoder| encoder.write_ext_no_op(padding_size),
394            |mut extensions| {
395                let ext = extensions.next().unwrap().unwrap();
396                assert_eq!(ext.typ().unwrap(), ExtensionType::NoOp);
397                assert_eq!(ext.payload().len(), padding_size as usize);
398                assert_eq!(ext.payload(), &vec![0u8; padding_size as usize]);
399                assert!(extensions.next().is_none());
400            },
401        );
402    }
403
404    #[test]
405    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
406    fn encode_decode_header_with_multiple_extensions() {
407        let header = Header::new_proxy(
408            Protocol::Stream,
409            AddressPair::Inet {
410                src_ip: "127.0.0.1".parse().unwrap(),
411                dst_ip: "127.0.0.2".parse().unwrap(),
412                src_port: 8080,
413                dst_port: 80,
414            },
415        );
416        let alpn_data = b"h2";
417        let authority_data = b"test.example.org";
418        let padding_size = 3u16;
419
420        let encoded = codec::HeaderEncoder::encode(&header)
421            .write_ext_alpn(alpn_data)
422            .unwrap()
423            .write_ext_authority(authority_data)
424            .unwrap()
425            .write_ext_no_op(padding_size)
426            .unwrap()
427            .finish()
428            .unwrap();
429
430        let decoded = codec::HeaderDecoder::decode(&encoded).unwrap();
431        if let codec::Decoded::Some(codec::DecodedHeader {
432            header: decoded_header,
433            extensions,
434        }) = decoded
435        {
436            assert_eq!(decoded_header, header);
437
438            let mut extensions_iter = extensions.into_iter();
439
440            // Check ALPN extension
441            let ext1 = extensions_iter.next().unwrap().unwrap();
442            assert_eq!(ext1.typ().unwrap(), ExtensionType::ALPN);
443            assert_eq!(ext1.payload(), alpn_data);
444
445            // Check Authority extension
446            let ext2 = extensions_iter.next().unwrap().unwrap();
447            assert_eq!(ext2.typ().unwrap(), ExtensionType::Authority);
448            assert_eq!(ext2.payload(), authority_data);
449
450            // Check NoOp extension
451            let ext3 = extensions_iter.next().unwrap().unwrap();
452            assert_eq!(ext3.typ().unwrap(), ExtensionType::NoOp);
453            assert_eq!(ext3.payload().len(), padding_size as usize);
454            assert_eq!(ext3.payload(), &vec![0u8; padding_size as usize]);
455
456            // No more extensions
457            assert!(extensions_iter.next().is_none());
458        } else {
459            panic!("Expected decoded header");
460        }
461    }
462
463    #[test]
464    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
465    fn encode_decode_header_with_custom_extension() {
466        let header = Header::new_local();
467        let custom_type = 0x80u8; // Custom extension type
468        let custom_data = b"custom-extension-data";
469
470        let extension = ExtensionRef::new_custom(custom_type, custom_data).unwrap();
471        let encoded = codec::HeaderEncoder::encode(&header)
472            .write_ext_custom(extension)
473            .finish()
474            .unwrap();
475
476        let decoded = codec::HeaderDecoder::decode(&encoded).unwrap();
477        if let codec::Decoded::Some(codec::DecodedHeader {
478            header: decoded_header,
479            extensions,
480        }) = decoded
481        {
482            assert_eq!(decoded_header, header);
483
484            let mut extensions_iter = extensions.into_iter();
485            let ext = extensions_iter.next().unwrap().unwrap();
486            assert_eq!(ext.typ().unwrap_err(), custom_type); // Unknown type returns Err with raw byte
487            assert_eq!(ext.payload(), custom_data);
488            assert!(extensions_iter.next().is_none());
489        } else {
490            panic!("Expected decoded header");
491        }
492    }
493
494    #[test]
495    #[cfg(all(
496        feature = "feat-codec-encode",
497        feature = "feat-codec-decode",
498        feature = "feat-codec-v2-crc32c"
499    ))]
500    fn encode_decode_header_with_crc32c() {
501        let header = Header::new_proxy(
502            Protocol::Stream,
503            AddressPair::Inet {
504                src_ip: "127.0.0.1".parse().unwrap(),
505                dst_ip: "127.0.0.2".parse().unwrap(),
506                src_port: 8080,
507                dst_port: 80,
508            },
509        );
510
511        let encoded = codec::HeaderEncoder::encode(&header).finish_with_crc32c().unwrap();
512
513        let decoded = codec::HeaderDecoder::decode(&encoded).unwrap();
514        if let codec::Decoded::Some(codec::DecodedHeader {
515            header: decoded_header,
516            extensions,
517        }) = decoded
518        {
519            assert_eq!(decoded_header, header);
520
521            let mut extensions_iter = extensions.into_iter();
522            let ext = extensions_iter.next().unwrap().unwrap();
523            assert_eq!(ext.typ().unwrap(), ExtensionType::CRC32C);
524            assert_eq!(ext.payload().len(), 4); // CRC32C is 4 bytes
525            assert!(extensions_iter.next().is_none());
526        } else {
527            panic!("Expected decoded header");
528        }
529    }
530
531    #[cfg(all(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
532    fn _encode_decode_header_with_extension<F, V>(header: Header, add_extension: F, verify_extension: V)
533    where
534        F: FnOnce(
535            codec::HeaderEncoder<codec::encode::stage::Finished>,
536        ) -> Result<codec::HeaderEncoder<codec::encode::stage::Finished>, codec::EncodeError>,
537        V: FnOnce(codec::DecodedExtensionsIter<'_>),
538    {
539        let encoded = add_extension(codec::HeaderEncoder::encode(&header))
540            .unwrap()
541            .finish()
542            .unwrap();
543
544        let decoded = codec::HeaderDecoder::decode(&encoded).unwrap();
545        if let codec::Decoded::Some(codec::DecodedHeader {
546            header: decoded_header,
547            extensions,
548        }) = decoded
549        {
550            assert_eq!(decoded_header, header);
551            verify_extension(extensions.into_iter());
552        } else {
553            panic!("Expected decoded header");
554        }
555    }
556
557    #[test]
558    #[cfg(feature = "feat-codec-decode")]
559    fn decode_peek_insufficient_data() {
560        // Test with buffers of various sizes less than HEADER_SIZE (16 bytes)
561        // that start with magic bytes
562        for size in 1..16 {
563            let mut buffer = vec![0u8; size];
564            // Copy as much of the magic bytes as we can fit
565            let magic = b"\r\n\r\n\x00\r\nQUIT\n";
566            let copy_len = size.min(magic.len());
567            buffer[..copy_len].copy_from_slice(&magic[..copy_len]);
568
569            let result = codec::HeaderDecoder::decode(&buffer).unwrap();
570            // If we have the full magic bytes, it should be Partial
571            assert!(matches!(result, codec::Decoded::Partial(_)));
572        }
573
574        // Test with buffer that has no magic bytes
575        let buffer = vec![0u8; 8];
576        let result = codec::HeaderDecoder::decode(&buffer).unwrap();
577        assert!(matches!(result, codec::Decoded::None));
578    }
579
580    #[test]
581    #[cfg(feature = "feat-codec-decode")]
582    fn decode_peek_not_proxy_protocol() {
583        // Buffer that's large enough but doesn't start with magic bytes
584        let buffer = vec![0u8; 16];
585        let result = codec::HeaderDecoder::decode(&buffer).unwrap();
586        assert!(matches!(result, codec::Decoded::None));
587
588        // Buffer with wrong magic bytes
589        let mut buffer = vec![0u8; 16];
590        buffer[0..12].copy_from_slice(b"wrong_magic\x00");
591        let result = codec::HeaderDecoder::decode(&buffer).unwrap();
592        assert!(matches!(result, codec::Decoded::None));
593    }
594
595    #[test]
596    #[cfg(feature = "feat-codec-decode")]
597    fn decode_malformed_data() {
598        // Create a valid header first
599        let header = Header::new_proxy(
600            Protocol::Stream,
601            AddressPair::Inet {
602                src_ip: "127.0.0.1".parse().unwrap(),
603                dst_ip: "127.0.0.2".parse().unwrap(),
604                src_port: 8080,
605                dst_port: 80,
606            },
607        );
608        let encoded = codec::HeaderEncoder::encode(&header).finish().unwrap();
609
610        // Test with insufficient data for address parsing (truncate after header)
611        let truncated = &encoded[..16]; // Only header, no address data
612        let result = codec::HeaderDecoder::decode(truncated).unwrap();
613        assert!(matches!(result, codec::Decoded::Partial(_)));
614
615        // Test with partial address data
616        let partial = &encoded[..20]; // Header + partial IPv4 address
617        let result = codec::HeaderDecoder::decode(partial).unwrap();
618        assert!(matches!(result, codec::Decoded::Partial(_)));
619    }
620
621    #[test]
622    #[cfg(feature = "feat-codec-decode")]
623    fn decode_total_length_overflow() {
624        // Create a buffer with magic bytes and valid version/command
625        let mut buffer = vec![0u8; 16];
626        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
627        buffer[12] = 0x21; // Version 2, Command Proxy
628        buffer[13] = 0x11; // Family Inet, Protocol Stream
629
630        // Set length to maximum value that would cause overflow
631        buffer[14] = 0xFF;
632        buffer[15] = 0xFF;
633
634        // try_decode will return Partial requesting more data for the large length
635        let result = codec::HeaderDecoder::decode(&buffer).unwrap();
636        assert!(matches!(result, codec::Decoded::Partial(_)));
637    }
638
639    #[test]
640    #[cfg(feature = "feat-codec-decode")]
641    fn decode_invalid_version_variants() {
642        let mut buffer = vec![0u8; 16];
643        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
644        buffer[13] = 0x11; // Valid family and protocol
645        buffer[14] = 0x00; // Length hi
646        buffer[15] = 0x00; // Length lo
647
648        // Test various invalid version values
649        let invalid_versions = [
650            0x00, 0x10, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0,
651        ];
652        for &invalid_version in &invalid_versions {
653            buffer[12] = invalid_version | 0x01; // Keep command as Proxy
654            let err = codec::HeaderDecoder::decode(&buffer).unwrap_err();
655            assert!(matches!(err, codec::DecodeError::InvalidVersion(v) if v == invalid_version));
656        }
657    }
658
659    #[test]
660    #[cfg(feature = "feat-codec-decode")]
661    fn decode_invalid_command_variants() {
662        let mut buffer = vec![0u8; 16];
663        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
664        buffer[12] = 0x20; // Valid version 2
665        buffer[13] = 0x11; // Valid family and protocol
666        buffer[14] = 0x00; // Length hi
667        buffer[15] = 0x00; // Length lo
668
669        // Test various invalid command values
670        let invalid_commands = [
671            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
672        ];
673        for &invalid_command in &invalid_commands {
674            buffer[12] = 0x20 | invalid_command;
675            let err = codec::HeaderDecoder::decode(&buffer).unwrap_err();
676            assert!(matches!(err, codec::DecodeError::InvalidCommand(c) if c == invalid_command));
677        }
678    }
679
680    #[test]
681    #[cfg(feature = "feat-codec-decode")]
682    fn decode_invalid_family_variants() {
683        let mut buffer = vec![0u8; 16];
684        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
685        buffer[12] = 0x21; // Valid version and command
686        buffer[14] = 0x00; // Length hi
687        buffer[15] = 0x00; // Length lo
688
689        // Test various invalid family values
690        let invalid_families = [0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0];
691        for &invalid_family in &invalid_families {
692            buffer[13] = invalid_family | 0x01; // Keep protocol as Stream
693            let err = codec::HeaderDecoder::decode(&buffer).unwrap_err();
694            assert!(matches!(err, codec::DecodeError::InvalidFamily(f) if f == invalid_family));
695        }
696    }
697
698    #[test]
699    #[cfg(feature = "feat-codec-decode")]
700    fn decode_invalid_protocol_variants() {
701        let mut buffer = vec![0u8; 16];
702        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
703        buffer[12] = 0x21; // Valid version and command
704        buffer[13] = 0x10; // Valid family Inet
705        buffer[14] = 0x00; // Length hi
706        buffer[15] = 0x00; // Length lo
707
708        // Test various invalid protocol values
709        let invalid_protocols = [
710            0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
711        ];
712        for &invalid_protocol in &invalid_protocols {
713            buffer[13] = 0x10 | invalid_protocol;
714            let err = codec::HeaderDecoder::decode(&buffer).unwrap_err();
715            assert!(matches!(err, codec::DecodeError::InvalidProtocol(p) if p == invalid_protocol));
716        }
717    }
718
719    #[test]
720    #[cfg(feature = "feat-codec-decode")]
721    fn decode_trailing_data_variants() {
722        // Create a minimal valid header (Local command)
723        let header = Header::new_local();
724        let encoded = codec::HeaderEncoder::encode(&header).finish().unwrap();
725
726        // Test with various amounts of trailing data
727        for extra_bytes in 1..=10 {
728            let mut with_trailing = encoded.clone();
729            with_trailing.extend(vec![0xAA; extra_bytes]);
730
731            // try_decode should detect trailing data and return an error
732            let err = codec::HeaderDecoder::decode(&with_trailing).unwrap_err();
733            assert!(matches!(err, codec::DecodeError::TrailingData));
734        }
735    }
736
737    #[test]
738    #[cfg(feature = "feat-codec-decode")]
739    fn decode_inet6_malformed() {
740        // Create header for IPv6 but with insufficient data
741        let mut buffer = vec![0u8; 16];
742        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
743        buffer[12] = 0x21; // Version 2, Command Proxy
744        buffer[13] = 0x21; // Family Inet6, Protocol Stream
745        buffer[14] = 0x00; // Length hi
746        buffer[15] = 0x24; // Length lo (36 bytes for IPv6 addresses)
747
748        // Create buffer with header but insufficient address data
749        let mut insufficient_data = buffer.clone();
750        insufficient_data.extend(vec![0u8; 20]); // Only 20 bytes instead of 36
751
752        let result = codec::HeaderDecoder::decode(&insufficient_data).unwrap();
753        assert!(matches!(result, codec::Decoded::Partial(_)));
754    }
755
756    #[test]
757    #[cfg(feature = "feat-codec-decode")]
758    fn decode_unix_malformed() {
759        // Create header for Unix but with insufficient data
760        let mut buffer = vec![0u8; 16];
761        buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
762        buffer[12] = 0x21; // Version 2, Command Proxy
763        buffer[13] = 0x31; // Family Unix, Protocol Stream
764        buffer[14] = 0x00; // Length hi
765        buffer[15] = 0xD8; // Length lo (216 bytes for Unix addresses)
766
767        // Create buffer with header but insufficient address data
768        let mut insufficient_data = buffer.clone();
769        insufficient_data.extend(vec![0u8; 100]); // Only 100 bytes instead of 216
770
771        let result = codec::HeaderDecoder::decode(&insufficient_data).unwrap();
772        assert!(matches!(result, codec::Decoded::Partial(_)));
773    }
774
775    #[test]
776    #[cfg(feature = "feat-codec-decode")]
777    fn decode_zero_length_but_with_address_family() {
778        // Test headers that claim to have address families but with zero length
779        let families_and_protocols = [
780            (0x10, 0x01), // Inet + Stream
781            (0x20, 0x01), // Inet6 + Stream
782            (0x30, 0x01), // Unix + Stream
783        ];
784
785        for (family, protocol) in families_and_protocols {
786            let mut buffer = vec![0u8; 16];
787            buffer[0..12].copy_from_slice(b"\r\n\r\n\x00\r\nQUIT\n");
788            buffer[12] = 0x21; // Version 2, Command Proxy
789            buffer[13] = family | protocol;
790            buffer[14] = 0x00; // Length hi - zero length
791            buffer[15] = 0x00; // Length lo - zero length
792
793            let err = codec::HeaderDecoder::decode(&buffer).unwrap_err();
794            assert!(matches!(err, codec::DecodeError::MalformedData));
795        }
796    }
797}