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