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