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::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#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct CommandContainer {
75 pub code: OperationCode,
77 pub transaction_id: u32,
79 pub params: Vec<u32>,
81}
82
83impl CommandContainer {
84 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 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 for ¶m in &self.params {
99 buf.extend_from_slice(&pack_u32(param));
100 }
101
102 buf
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct DataContainer {
109 pub code: OperationCode,
111 pub transaction_id: u32,
113 pub payload: Vec<u8>,
115}
116
117impl DataContainer {
118 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 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 buf.extend_from_slice(&self.payload);
132
133 buf
134 }
135
136 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct ResponseContainer {
189 pub code: ResponseCode,
191 pub transaction_id: u32,
193 pub params: Vec<u32>,
195}
196
197impl ResponseContainer {
198 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 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 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 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 #[must_use]
256 pub fn is_ok(&self) -> bool {
257 self.code == ResponseCode::Ok
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct EventContainer {
264 pub code: EventCode,
266 pub transaction_id: u32,
268 pub params: [u32; 3],
270}
271
272impl EventContainer {
273 pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
278 const MAX_EVENT_SIZE: usize = HEADER_SIZE + 12; 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 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 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 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 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 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 #[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 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]; bytes.extend_from_slice(&type_code.to_le_bytes());
389 bytes.extend_from_slice(&[0x00; 6]); assert_eq!(container_type(&bytes).unwrap(), expected);
391 }
392
393 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 assert!(container_type(&[]).is_err());
403 assert!(container_type(&[0x00; 11]).is_err());
404 }
405
406 #[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]); 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]); }
423
424 #[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 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()); let mut bad_type = vec![0x0C, 0x00, 0x00, 0x00, 0x03, 0x00]; bad_type.extend_from_slice(&[0x00; 6]);
452 assert!(DataContainer::from_bytes(&bad_type).is_err());
453
454 let mut truncated = vec![0x20, 0x00, 0x00, 0x00, 0x02, 0x00]; truncated.extend_from_slice(&[0x00; 6]);
457 assert!(DataContainer::from_bytes(&truncated).is_err());
458 }
459
460 #[test]
463 fn response_container_parsing() {
464 let bytes = [
466 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, ];
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 let err_bytes = [
481 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x20, 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 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 #[test]
503 fn event_container_variable_params() {
504 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 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 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 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 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 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 ¶ms {
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 #[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, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 32, 100, ]),
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]); 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 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}