1use super::{pack_u16, pack_u32, unpack_u16, unpack_u32, EventCode, OperationCode, ResponseCode};
17
18const HEADER_SIZE: usize = 12;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u16)]
24pub enum ContainerType {
25 Command = 1,
27 Data = 2,
29 Response = 3,
31 Event = 4,
33}
34
35impl ContainerType {
36 #[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 #[must_use]
50 pub fn to_code(self) -> u16 {
51 self as u16
52 }
53}
54
55pub 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#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct CommandContainer {
76 pub code: OperationCode,
78 pub transaction_id: u32,
80 pub params: Vec<u32>,
82}
83
84impl CommandContainer {
85 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 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 for ¶m in &self.params {
100 buf.extend_from_slice(&pack_u32(param));
101 }
102
103 buf
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct DataContainer {
110 pub code: OperationCode,
112 pub transaction_id: u32,
114 pub payload: Vec<u8>,
116}
117
118impl DataContainer {
119 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 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 buf.extend_from_slice(&self.payload);
133
134 buf
135 }
136
137 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct ResponseContainer {
190 pub code: ResponseCode,
192 pub transaction_id: u32,
194 pub params: Vec<u32>,
196}
197
198impl ResponseContainer {
199 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 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 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 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 #[must_use]
257 pub fn is_ok(&self) -> bool {
258 self.code == ResponseCode::Ok
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct EventContainer {
265 pub code: EventCode,
267 pub transaction_id: u32,
269 pub params: [u32; 3],
271}
272
273impl EventContainer {
274 pub fn to_bytes(&self) -> Vec<u8> {
278 const EVENT_SIZE: usize = HEADER_SIZE + 12; 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 ¶m in &self.params {
285 buf.extend_from_slice(&pack_u32(param));
286 }
287 buf
288 }
289
290 pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::PtpError> {
295 const MAX_EVENT_SIZE: usize = HEADER_SIZE + 12; 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 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 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 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 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 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 #[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 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]; bytes.extend_from_slice(&type_code.to_le_bytes());
406 bytes.extend_from_slice(&[0x00; 6]); assert_eq!(container_type(&bytes).unwrap(), expected);
408 }
409
410 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 assert!(container_type(&[]).is_err());
420 assert!(container_type(&[0x00; 11]).is_err());
421 }
422
423 #[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]); assert_eq!(&bytes[4..6], &[0x01, 0x00]); assert_eq!(&bytes[6..8], &[0x07, 0x10]); assert_eq!(&bytes[8..12], &[0x0A, 0x00, 0x00, 0x00]); assert_eq!(&bytes[12..16], &[0x01, 0x00, 0x01, 0x00]); }
440
441 #[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 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()); let mut bad_type = vec![0x0C, 0x00, 0x00, 0x00, 0x03, 0x00]; bad_type.extend_from_slice(&[0x00; 6]);
469 assert!(DataContainer::from_bytes(&bad_type).is_err());
470
471 let mut truncated = vec![0x20, 0x00, 0x00, 0x00, 0x02, 0x00]; truncated.extend_from_slice(&[0x00; 6]);
474 assert!(DataContainer::from_bytes(&truncated).is_err());
475 }
476
477 #[test]
480 fn response_container_parsing() {
481 let bytes = [
483 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, ];
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 let err_bytes = [
498 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x20, 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 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 #[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 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 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 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 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 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 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 ¶ms {
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 #[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, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 32, 100, ]),
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]); 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 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}