1use crate::CodecError;
4use serde::{Deserialize, Serialize};
5use serde_bytes::ByteBuf;
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct ControlMessage {
10 #[serde(rename = "op")]
12 pub opcode: u16,
13 #[serde(rename = "rid")]
15 pub request_id: u32,
16 #[serde(rename = "sid")]
18 pub sender_id: u32,
19 #[serde(rename = "tid")]
21 pub transition_id: u32,
22 #[serde(rename = "alen")]
24 pub args_length: u32,
25 #[serde(rename = "args")]
27 pub args: ByteBuf,
28}
29
30impl ControlMessage {
31 pub fn bare(opcode: u16, request_id: u32, sender_id: u32, transition_id: u32) -> Self {
33 Self {
34 opcode,
35 request_id,
36 sender_id,
37 transition_id,
38 args_length: 0,
39 args: ByteBuf::new(),
40 }
41 }
42
43 pub fn with_args(
45 opcode: u16,
46 request_id: u32,
47 sender_id: u32,
48 transition_id: u32,
49 args: Vec<u8>,
50 ) -> Self {
51 Self {
52 opcode,
53 request_id,
54 sender_id,
55 transition_id,
56 args_length: args.len() as u32,
57 args: ByteBuf::from(args),
58 }
59 }
60
61 pub fn to_cbor(&self) -> Vec<u8> {
63 let mut buf = Vec::new();
64 ciborium::into_writer(self, &mut buf).expect("cbor encode");
65 buf
66 }
67
68 pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
70 let m: Self = ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))?;
71 if m.args_length as usize != m.args.len() {
72 return Err(CodecError::PayloadSizeMismatch);
73 }
74 Ok(m)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn bare_message_round_trip() {
84 let msg = ControlMessage::bare(3, 99, 1, 2);
85 let bytes = msg.to_cbor();
86 let decoded = ControlMessage::from_cbor(&bytes).unwrap();
87 assert_eq!(decoded.opcode, 3);
88 assert_eq!(decoded.request_id, 99);
89 assert_eq!(decoded.sender_id, 1);
90 assert_eq!(decoded.transition_id, 2);
91 assert_eq!(decoded.args_length, 0);
92 assert!(decoded.args.is_empty());
93 }
94
95 #[test]
96 fn with_args_round_trip() {
97 let args = vec![0xA1u8, 0x00, 0xF5];
98 let msg = ControlMessage::with_args(7, 1, 2, 3, args.clone());
99 assert_eq!(msg.args_length, 3);
100 let decoded = ControlMessage::from_cbor(&msg.to_cbor()).unwrap();
101 assert_eq!(decoded.args.as_ref(), args.as_slice());
102 assert_eq!(decoded.args_length, 3);
103 }
104
105 #[test]
106 fn args_length_mismatch_rejected() {
107 let mut msg = ControlMessage::bare(1, 0, 0, 0);
108 msg.args = serde_bytes::ByteBuf::from(vec![0xFFu8; 5]);
109 let bytes = {
111 let mut buf = Vec::new();
112 ciborium::into_writer(&msg, &mut buf).unwrap();
113 buf
114 };
115 assert!(matches!(
116 ControlMessage::from_cbor(&bytes),
117 Err(CodecError::PayloadSizeMismatch)
118 ));
119 }
120
121 #[test]
122 fn invalid_cbor_returns_decode_error() {
123 assert!(matches!(
124 ControlMessage::from_cbor(b"\xFF\xFF"),
125 Err(CodecError::Decode(_))
126 ));
127 }
128}