Skip to main content

mtp_rs/ptp/
container.rs

1//! MTP/PTP USB container format.
2//!
3//! This module implements the USB container format used for MTP/PTP communication.
4//! All containers share a common 12-byte header followed by optional parameters or payload.
5//!
6//! ## Container format (little-endian)
7//!
8//! Header (12 bytes):
9//! - Offset 0: Length (u32) - Total container size including header
10//! - Offset 4: Type (u16) - Container type
11//! - Offset 6: Code (u16) - Operation/Response/Event code
12//! - Offset 8: TransactionID (u32)
13//!
14//! After header: parameters (each u32) or payload bytes.
15
16use super::{pack_u16, pack_u32, unpack_u16, unpack_u32, EventCode, OperationCode, ResponseCode};
17
18/// Minimum container header size in bytes.
19const HEADER_SIZE: usize = 12;
20
21/// Container type identifier.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u16)]
24pub enum ContainerType {
25    /// Command container (sent to device).
26    Command = 1,
27    /// Data container (bidirectional).
28    Data = 2,
29    /// Response container (from device).
30    Response = 3,
31    /// Event container (from device).
32    Event = 4,
33}
34
35impl ContainerType {
36    /// Convert a raw u16 value to a ContainerType.
37    #[must_use]
38    pub fn from_code(code: u16) -> Option<Self> {
39        match code {
40            1 => Some(ContainerType::Command),
41            2 => Some(ContainerType::Data),
42            3 => Some(ContainerType::Response),
43            4 => Some(ContainerType::Event),
44            _ => None,
45        }
46    }
47
48    /// Convert a ContainerType to its raw u16 value.
49    #[must_use]
50    pub fn to_code(self) -> u16 {
51        self as u16
52    }
53}
54
55/// Determine the container type from a raw buffer.
56///
57/// Returns an error if the buffer is too small or contains an invalid container type.
58pub fn container_type(buf: &[u8]) -> Result<ContainerType, crate::Error> {
59    if buf.len() < HEADER_SIZE {
60        return Err(crate::Error::invalid_data(format!(
61            "container too small: need at least {} bytes, have {}",
62            HEADER_SIZE,
63            buf.len()
64        )));
65    }
66
67    let type_code = unpack_u16(&buf[4..6])?;
68    ContainerType::from_code(type_code)
69        .ok_or_else(|| crate::Error::invalid_data(format!("invalid container type: {}", type_code)))
70}
71
72/// Command container sent to the device.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct CommandContainer {
75    /// Operation code for the command.
76    pub code: OperationCode,
77    /// Transaction ID for this operation.
78    pub transaction_id: u32,
79    /// Parameters (0-5 u32 values).
80    pub params: Vec<u32>,
81}
82
83impl CommandContainer {
84    /// Serialize the command container to bytes.
85    pub fn to_bytes(&self) -> Vec<u8> {
86        let param_bytes = self.params.len() * 4;
87        let total_len = HEADER_SIZE + param_bytes;
88
89        let mut buf = Vec::with_capacity(total_len);
90
91        // Header
92        buf.extend_from_slice(&pack_u32(total_len as u32));
93        buf.extend_from_slice(&pack_u16(ContainerType::Command.to_code()));
94        buf.extend_from_slice(&pack_u16(self.code.into()));
95        buf.extend_from_slice(&pack_u32(self.transaction_id));
96
97        // Parameters
98        for &param in &self.params {
99            buf.extend_from_slice(&pack_u32(param));
100        }
101
102        buf
103    }
104}
105
106/// Data container for transferring payload data.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct DataContainer {
109    /// Operation code this data belongs to.
110    pub code: OperationCode,
111    /// Transaction ID for this operation.
112    pub transaction_id: u32,
113    /// Payload bytes.
114    pub payload: Vec<u8>,
115}
116
117impl DataContainer {
118    /// Serialize the data container to bytes.
119    pub fn to_bytes(&self) -> Vec<u8> {
120        let total_len = HEADER_SIZE + self.payload.len();
121
122        let mut buf = Vec::with_capacity(total_len);
123
124        // Header
125        buf.extend_from_slice(&pack_u32(total_len as u32));
126        buf.extend_from_slice(&pack_u16(ContainerType::Data.to_code()));
127        buf.extend_from_slice(&pack_u16(self.code.into()));
128        buf.extend_from_slice(&pack_u32(self.transaction_id));
129
130        // Payload
131        buf.extend_from_slice(&self.payload);
132
133        buf
134    }
135
136    /// Parse a data container from bytes.
137    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
138        if buf.len() < HEADER_SIZE {
139            return Err(crate::Error::invalid_data(format!(
140                "data container too small: need at least {} bytes, have {}",
141                HEADER_SIZE,
142                buf.len()
143            )));
144        }
145
146        let length = unpack_u32(&buf[0..4])? as usize;
147        let type_code = unpack_u16(&buf[4..6])?;
148        let code = unpack_u16(&buf[6..8])?;
149        let transaction_id = unpack_u32(&buf[8..12])?;
150
151        // Validate container type
152        if type_code != ContainerType::Data.to_code() {
153            return Err(crate::Error::invalid_data(format!(
154                "expected Data container type ({}), got {}",
155                ContainerType::Data.to_code(),
156                type_code
157            )));
158        }
159
160        // Validate length - must be at least header size and not exceed buffer
161        if length < HEADER_SIZE {
162            return Err(crate::Error::invalid_data(format!(
163                "data container length too small: {} < header size {}",
164                length, HEADER_SIZE
165            )));
166        }
167        if buf.len() < length {
168            return Err(crate::Error::invalid_data(format!(
169                "data container length mismatch: header says {}, have {}",
170                length,
171                buf.len()
172            )));
173        }
174
175        // Extract payload
176        let payload = buf[HEADER_SIZE..length].to_vec();
177
178        Ok(DataContainer {
179            code: code.into(),
180            transaction_id,
181            payload,
182        })
183    }
184}
185
186/// Response container from the device.
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct ResponseContainer {
189    /// Response code indicating success or failure.
190    pub code: ResponseCode,
191    /// Transaction ID this response corresponds to.
192    pub transaction_id: u32,
193    /// Response parameters (0-5 u32 values).
194    pub params: Vec<u32>,
195}
196
197impl ResponseContainer {
198    /// Parse a response container from bytes.
199    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
200        if buf.len() < HEADER_SIZE {
201            return Err(crate::Error::invalid_data(format!(
202                "response container too small: need at least {} bytes, have {}",
203                HEADER_SIZE,
204                buf.len()
205            )));
206        }
207
208        let length = unpack_u32(&buf[0..4])? as usize;
209        let type_code = unpack_u16(&buf[4..6])?;
210        let code = unpack_u16(&buf[6..8])?;
211        let transaction_id = unpack_u32(&buf[8..12])?;
212
213        // Validate container type
214        if type_code != ContainerType::Response.to_code() {
215            return Err(crate::Error::invalid_data(format!(
216                "expected Response container type ({}), got {}",
217                ContainerType::Response.to_code(),
218                type_code
219            )));
220        }
221
222        // Validate length
223        if buf.len() < length {
224            return Err(crate::Error::invalid_data(format!(
225                "response container length mismatch: header says {}, have {}",
226                length,
227                buf.len()
228            )));
229        }
230
231        // Parse parameters
232        let param_bytes = length - HEADER_SIZE;
233        if param_bytes % 4 != 0 {
234            return Err(crate::Error::invalid_data(format!(
235                "response parameter bytes not aligned: {} bytes",
236                param_bytes
237            )));
238        }
239
240        let param_count = param_bytes / 4;
241        let mut params = Vec::with_capacity(param_count);
242        for i in 0..param_count {
243            let offset = HEADER_SIZE + i * 4;
244            params.push(unpack_u32(&buf[offset..])?);
245        }
246
247        Ok(ResponseContainer {
248            code: code.into(),
249            transaction_id,
250            params,
251        })
252    }
253
254    /// Check if the response indicates success (Ok).
255    #[must_use]
256    pub fn is_ok(&self) -> bool {
257        self.code == ResponseCode::Ok
258    }
259}
260
261/// Event container from the device.
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct EventContainer {
264    /// Event code identifying the event type.
265    pub code: EventCode,
266    /// Transaction ID (may be 0 for unsolicited events).
267    pub transaction_id: u32,
268    /// Event parameters (always exactly 3).
269    pub params: [u32; 3],
270}
271
272impl EventContainer {
273    /// Parse an event container from bytes.
274    ///
275    /// Events can have 0-3 parameters, so valid sizes are 12-24 bytes
276    /// (header + 0-3 u32 params). Missing parameters default to 0.
277    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
278        const MAX_EVENT_SIZE: usize = HEADER_SIZE + 12; // 24 bytes max (3 params)
279
280        if buf.len() < HEADER_SIZE {
281            return Err(crate::Error::invalid_data(format!(
282                "event container too small: need at least {} bytes, have {}",
283                HEADER_SIZE,
284                buf.len()
285            )));
286        }
287
288        let length = unpack_u32(&buf[0..4])? as usize;
289        let type_code = unpack_u16(&buf[4..6])?;
290        let code = unpack_u16(&buf[6..8])?;
291        let transaction_id = unpack_u32(&buf[8..12])?;
292
293        // Validate container type
294        if type_code != ContainerType::Event.to_code() {
295            return Err(crate::Error::invalid_data(format!(
296                "expected Event container type ({}), got {}",
297                ContainerType::Event.to_code(),
298                type_code
299            )));
300        }
301
302        // Validate length: must be between 12 (header only) and 24 (header + 3 params)
303        if !(HEADER_SIZE..=MAX_EVENT_SIZE).contains(&length) {
304            return Err(crate::Error::invalid_data(format!(
305                "event container invalid size: expected 12-24, got {}",
306                length
307            )));
308        }
309
310        // Validate parameter alignment (must be multiple of 4 bytes after header)
311        let param_bytes = length - HEADER_SIZE;
312        if param_bytes % 4 != 0 {
313            return Err(crate::Error::invalid_data(format!(
314                "event parameter bytes not aligned: {} bytes",
315                param_bytes
316            )));
317        }
318
319        // Validate buffer has enough data
320        if buf.len() < length {
321            return Err(crate::Error::invalid_data(format!(
322                "event container buffer too small: need {}, have {}",
323                length,
324                buf.len()
325            )));
326        }
327
328        // Parse parameters (0-3), defaulting missing ones to 0
329        let param_count = param_bytes / 4;
330        let param1 = if param_count >= 1 {
331            unpack_u32(&buf[12..16])?
332        } else {
333            0
334        };
335        let param2 = if param_count >= 2 {
336            unpack_u32(&buf[16..20])?
337        } else {
338            0
339        };
340        let param3 = if param_count >= 3 {
341            unpack_u32(&buf[20..24])?
342        } else {
343            0
344        };
345
346        Ok(EventContainer {
347            code: code.into(),
348            transaction_id,
349            params: [param1, param2, param3],
350        })
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use proptest::prelude::*;
358
359    // --- ContainerType tests ---
360
361    #[test]
362    fn container_type_conversions() {
363        for (code, ct) in [
364            (1, ContainerType::Command),
365            (2, ContainerType::Data),
366            (3, ContainerType::Response),
367            (4, ContainerType::Event),
368        ] {
369            assert_eq!(ContainerType::from_code(code), Some(ct));
370            assert_eq!(ct.to_code(), code);
371        }
372        for invalid in [0, 5, 0xFFFF] {
373            assert_eq!(ContainerType::from_code(invalid), None);
374        }
375    }
376
377    #[test]
378    fn container_type_detection() {
379        // Build minimal containers and verify type detection
380        let containers: [(u16, ContainerType); 4] = [
381            (1, ContainerType::Command),
382            (2, ContainerType::Data),
383            (3, ContainerType::Response),
384            (4, ContainerType::Event),
385        ];
386        for (type_code, expected) in containers {
387            let mut bytes = vec![0x0C, 0x00, 0x00, 0x00]; // length = 12
388            bytes.extend_from_slice(&type_code.to_le_bytes());
389            bytes.extend_from_slice(&[0x00; 6]); // code + tx_id
390            assert_eq!(container_type(&bytes).unwrap(), expected);
391        }
392
393        // Invalid type codes
394        for invalid in [0u16, 5] {
395            let mut bytes = vec![0x0C, 0x00, 0x00, 0x00];
396            bytes.extend_from_slice(&invalid.to_le_bytes());
397            bytes.extend_from_slice(&[0x00; 6]);
398            assert!(container_type(&bytes).is_err());
399        }
400
401        // Insufficient bytes
402        assert!(container_type(&[]).is_err());
403        assert!(container_type(&[0x00; 11]).is_err());
404    }
405
406    // --- CommandContainer tests ---
407
408    #[test]
409    fn command_container_serialization() {
410        let cmd = CommandContainer {
411            code: OperationCode::GetObjectHandles,
412            transaction_id: 10,
413            params: vec![0x00010001, 0x00000000, 0xFFFFFFFF],
414        };
415        let bytes = cmd.to_bytes();
416        assert_eq!(bytes.len(), 24);
417        assert_eq!(&bytes[0..4], &[0x18, 0x00, 0x00, 0x00]); // length = 24
418        assert_eq!(&bytes[4..6], &[0x01, 0x00]); // type = Command
419        assert_eq!(&bytes[6..8], &[0x07, 0x10]); // code = 0x1007
420        assert_eq!(&bytes[8..12], &[0x0A, 0x00, 0x00, 0x00]); // tx_id = 10
421        assert_eq!(&bytes[12..16], &[0x01, 0x00, 0x01, 0x00]); // param1
422    }
423
424    // --- DataContainer tests ---
425
426    #[test]
427    fn data_container_roundtrip() {
428        let original = DataContainer {
429            code: OperationCode::GetObject,
430            transaction_id: 100,
431            payload: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
432        };
433        let parsed = DataContainer::from_bytes(&original.to_bytes()).unwrap();
434        assert_eq!(parsed, original);
435
436        // Empty payload
437        let empty = DataContainer {
438            code: OperationCode::SendObject,
439            transaction_id: 5,
440            payload: vec![],
441        };
442        assert_eq!(DataContainer::from_bytes(&empty.to_bytes()).unwrap(), empty);
443    }
444
445    #[test]
446    fn data_container_errors() {
447        assert!(DataContainer::from_bytes(&[0x00; 11]).is_err()); // Too small
448
449        // Wrong type
450        let mut bad_type = vec![0x0C, 0x00, 0x00, 0x00, 0x03, 0x00]; // Response type
451        bad_type.extend_from_slice(&[0x00; 6]);
452        assert!(DataContainer::from_bytes(&bad_type).is_err());
453
454        // Length > buffer
455        let mut truncated = vec![0x20, 0x00, 0x00, 0x00, 0x02, 0x00]; // claims 32 bytes
456        truncated.extend_from_slice(&[0x00; 6]);
457        assert!(DataContainer::from_bytes(&truncated).is_err());
458    }
459
460    // --- ResponseContainer tests ---
461
462    #[test]
463    fn response_container_parsing() {
464        // OK response with params
465        let bytes = [
466            0x18, 0x00, 0x00, 0x00, // length = 24
467            0x03, 0x00, // type = Response
468            0x01, 0x20, // code = OK
469            0x02, 0x00, 0x00, 0x00, // tx_id = 2
470            0x01, 0x00, 0x01, 0x00, // param1
471            0x00, 0x00, 0x00, 0x00, // param2
472            0x05, 0x00, 0x00, 0x00, // param3
473        ];
474        let resp = ResponseContainer::from_bytes(&bytes).unwrap();
475        assert_eq!(resp.code, ResponseCode::Ok);
476        assert!(resp.is_ok());
477        assert_eq!(resp.params, vec![0x00010001, 0, 5]);
478
479        // Error response
480        let err_bytes = [
481            0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x20, // GeneralError
482            0x03, 0x00, 0x00, 0x00,
483        ];
484        let err_resp = ResponseContainer::from_bytes(&err_bytes).unwrap();
485        assert_eq!(err_resp.code, ResponseCode::GeneralError);
486        assert!(!err_resp.is_ok());
487    }
488
489    #[test]
490    fn response_container_errors() {
491        assert!(ResponseContainer::from_bytes(&[0x00; 11]).is_err());
492
493        // Unaligned params (13 bytes = 12 header + 1)
494        let unaligned = [
495            0x0D, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x01, 0x00, 0x00, 0x00, 0xFF,
496        ];
497        assert!(ResponseContainer::from_bytes(&unaligned).is_err());
498    }
499
500    // --- EventContainer tests ---
501
502    #[test]
503    fn event_container_variable_params() {
504        // 0 params (12 bytes)
505        let zero = [
506            0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00,
507        ];
508        let e0 = EventContainer::from_bytes(&zero).unwrap();
509        assert_eq!(e0.code, EventCode::DeviceInfoChanged);
510        assert_eq!(e0.params, [0, 0, 0]);
511
512        // 1 param (16 bytes) - common on Android
513        let one = [
514            0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00,
515            0x00, 0x00,
516        ];
517        let e1 = EventContainer::from_bytes(&one).unwrap();
518        assert_eq!(e1.params, [42, 0, 0]);
519
520        // 3 params (24 bytes)
521        let three = [
522            0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x40, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00,
523            0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
524        ];
525        let e3 = EventContainer::from_bytes(&three).unwrap();
526        assert_eq!(e3.transaction_id, 10);
527        assert_eq!(e3.params, [1, 2, 3]);
528    }
529
530    #[test]
531    fn event_container_errors() {
532        assert!(EventContainer::from_bytes(&[0x00; 11]).is_err());
533
534        // Length > 24 (too many params)
535        let too_long = [
536            0x1C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
537            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
538        ];
539        assert!(EventContainer::from_bytes(&too_long).is_err());
540
541        // Unaligned (14 bytes)
542        let unaligned = [
543            0x0E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
544        ];
545        assert!(EventContainer::from_bytes(&unaligned).is_err());
546    }
547
548    // --- Property-based tests ---
549
550    fn valid_response_bytes(param_count: usize) -> impl Strategy<Value = Vec<u8>> {
551        (
552            any::<u16>(),
553            any::<u32>(),
554            prop::collection::vec(any::<u32>(), param_count..=param_count),
555        )
556            .prop_map(move |(code, tx_id, params)| {
557                let len = HEADER_SIZE + params.len() * 4;
558                let mut bytes = Vec::with_capacity(len);
559                bytes.extend_from_slice(&pack_u32(len as u32));
560                bytes.extend_from_slice(&pack_u16(ContainerType::Response.to_code()));
561                bytes.extend_from_slice(&pack_u16(code));
562                bytes.extend_from_slice(&pack_u32(tx_id));
563                for p in &params {
564                    bytes.extend_from_slice(&pack_u32(*p));
565                }
566                bytes
567            })
568    }
569
570    proptest! {
571        #[test]
572        fn prop_container_type_roundtrip(code in 1u16..=4u16) {
573            let ct = ContainerType::from_code(code).unwrap();
574            prop_assert_eq!(ct.to_code(), code);
575        }
576
577        #[test]
578        fn prop_data_container_roundtrip(
579            code in any::<u16>(),
580            tx_id in any::<u32>(),
581            payload in prop::collection::vec(any::<u8>(), 0..500)
582        ) {
583            let original = DataContainer {
584                code: code.into(),
585                transaction_id: tx_id,
586                payload: payload.clone(),
587            };
588            let parsed = DataContainer::from_bytes(&original.to_bytes()).unwrap();
589            prop_assert_eq!(parsed, original);
590        }
591
592        #[test]
593        fn prop_command_container_length(
594            code in any::<u16>(),
595            tx_id in any::<u32>(),
596            params in prop::collection::vec(any::<u32>(), 0..5)
597        ) {
598            let cmd = CommandContainer {
599                code: code.into(),
600                transaction_id: tx_id,
601                params: params.clone(),
602            };
603            let bytes = cmd.to_bytes();
604            let length = unpack_u32(&bytes[0..4]).unwrap() as usize;
605            prop_assert_eq!(length, HEADER_SIZE + params.len() * 4);
606            prop_assert_eq!(length, bytes.len());
607        }
608
609        #[test]
610        fn prop_response_container_parse(param_count in 0usize..=5usize) {
611            let strategy = valid_response_bytes(param_count);
612            proptest!(|(bytes in strategy)| {
613                let resp = ResponseContainer::from_bytes(&bytes).unwrap();
614                prop_assert_eq!(resp.params.len(), param_count);
615            });
616        }
617
618        #[test]
619        fn prop_container_type_identification(
620            code in any::<u16>(),
621            tx_id in any::<u32>(),
622            payload in prop::collection::vec(any::<u8>(), 0..50)
623        ) {
624            let data = DataContainer {
625                code: code.into(),
626                transaction_id: tx_id,
627                payload,
628            };
629            prop_assert_eq!(container_type(&data.to_bytes()).unwrap(), ContainerType::Data);
630
631            let cmd = CommandContainer {
632                code: code.into(),
633                transaction_id: tx_id,
634                params: vec![],
635            };
636            prop_assert_eq!(container_type(&cmd.to_bytes()).unwrap(), ContainerType::Command);
637        }
638
639        // Adversarial tests
640
641        #[test]
642        fn fuzz_data_container_length_underflow(fake_length in 0u32..12u32, tx_id: u32) {
643            let mut buf = fake_length.to_le_bytes().to_vec();
644            buf.extend_from_slice(&2u16.to_le_bytes());
645            buf.extend_from_slice(&0x1001u16.to_le_bytes());
646            buf.extend_from_slice(&tx_id.to_le_bytes());
647            prop_assert!(DataContainer::from_bytes(&buf).is_err());
648        }
649
650        #[test]
651        fn fuzz_event_container_invalid_length(
652            fake_length in prop::sample::select(vec![
653                0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // Too small
654                13, 14, 15, 17, 18, 19, 21, 22, 23,      // Unaligned
655                25, 26, 28, 32, 100,                      // Too large
656            ]),
657            tx_id: u32,
658        ) {
659            let mut buf = fake_length.to_le_bytes().to_vec();
660            buf.extend_from_slice(&4u16.to_le_bytes());
661            buf.extend_from_slice(&0x4002u16.to_le_bytes());
662            buf.extend_from_slice(&tx_id.to_le_bytes());
663            buf.extend_from_slice(&[0u8; 12]); // 3 params
664            prop_assert!(EventContainer::from_bytes(&buf).is_err());
665        }
666
667        #[test]
668        fn fuzz_wrong_container_type(
669            tx_id: u32,
670            payload in prop::collection::vec(any::<u8>(), 0..20),
671        ) {
672            let len = 12 + payload.len();
673            for (parser_type, wrong_type) in [(2u16, 1u16), (2, 3), (2, 4), (3, 1), (3, 2), (4, 1)] {
674                let mut buf = (len as u32).to_le_bytes().to_vec();
675                buf.extend_from_slice(&wrong_type.to_le_bytes());
676                buf.extend_from_slice(&0x1001u16.to_le_bytes());
677                buf.extend_from_slice(&tx_id.to_le_bytes());
678                buf.extend_from_slice(&payload);
679
680                match parser_type {
681                    2 => prop_assert!(DataContainer::from_bytes(&buf).is_err()),
682                    3 => prop_assert!(ResponseContainer::from_bytes(&buf).is_err()),
683                    4 => prop_assert!(EventContainer::from_bytes(&buf).is_err()),
684                    _ => {}
685                }
686            }
687        }
688    }
689
690    // Fuzz tests - verify parsers don't panic on arbitrary input
691    crate::fuzz_bytes_fn!(fuzz_container_type, container_type, 100);
692    crate::fuzz_bytes!(fuzz_data_container, DataContainer, 100);
693    crate::fuzz_bytes!(fuzz_response_container, ResponseContainer, 100);
694    crate::fuzz_bytes!(fuzz_event_container, EventContainer, 100);
695}