openigtlink_rust/protocol/types/
command.rs1use crate::error::{IgtlError, Result};
7use crate::protocol::message::Message;
8use bytes::{Buf, BufMut};
9
10const COMMAND_NAME_SIZE: usize = 20;
12
13#[derive(Debug, Clone, PartialEq)]
20pub struct CommandMessage {
21 pub command_id: u32,
23
24 pub command_name: String,
26
27 pub encoding: u16,
32
33 pub command: String,
35}
36
37impl CommandMessage {
38 pub fn new(
40 command_id: u32,
41 command_name: impl Into<String>,
42 command: impl Into<String>,
43 ) -> Self {
44 CommandMessage {
45 command_id,
46 command_name: command_name.into(),
47 encoding: 3, command: command.into(),
49 }
50 }
51
52 pub fn utf8(
54 command_id: u32,
55 command_name: impl Into<String>,
56 command: impl Into<String>,
57 ) -> Self {
58 CommandMessage {
59 command_id,
60 command_name: command_name.into(),
61 encoding: 106, command: command.into(),
63 }
64 }
65
66 pub fn with_encoding(
68 command_id: u32,
69 command_name: impl Into<String>,
70 encoding: u16,
71 command: impl Into<String>,
72 ) -> Self {
73 CommandMessage {
74 command_id,
75 command_name: command_name.into(),
76 encoding,
77 command: command.into(),
78 }
79 }
80
81 pub fn as_str(&self) -> &str {
83 &self.command
84 }
85}
86
87impl Message for CommandMessage {
88 fn message_type() -> &'static str {
89 "COMMAND"
90 }
91
92 fn encode_content(&self) -> Result<Vec<u8>> {
93 let command_bytes = self.command.as_bytes();
94 let command_len = command_bytes.len();
95
96 let mut buf = Vec::with_capacity(4 + COMMAND_NAME_SIZE + 2 + 4 + command_len);
97
98 buf.put_u32(self.command_id);
100
101 let mut name_bytes = [0u8; COMMAND_NAME_SIZE];
103 let name_str = self.command_name.as_bytes();
104 let copy_len = name_str.len().min(COMMAND_NAME_SIZE - 1);
105 name_bytes[..copy_len].copy_from_slice(&name_str[..copy_len]);
106 buf.extend_from_slice(&name_bytes);
107
108 buf.put_u16(self.encoding);
110
111 buf.put_u32(command_len as u32);
113
114 buf.extend_from_slice(command_bytes);
116
117 Ok(buf)
118 }
119
120 fn decode_content(mut data: &[u8]) -> Result<Self> {
121 if data.len() < 4 + COMMAND_NAME_SIZE + 2 + 4 {
122 return Err(IgtlError::InvalidSize {
123 expected: 4 + COMMAND_NAME_SIZE + 2 + 4,
124 actual: data.len(),
125 });
126 }
127
128 let command_id = data.get_u32();
130
131 let name_bytes = &data[..COMMAND_NAME_SIZE];
133 data.advance(COMMAND_NAME_SIZE);
134
135 let name_len = name_bytes
136 .iter()
137 .position(|&b| b == 0)
138 .unwrap_or(COMMAND_NAME_SIZE);
139 let command_name = String::from_utf8(name_bytes[..name_len].to_vec())?;
140
141 let encoding = data.get_u16();
143
144 let length = data.get_u32() as usize;
146
147 if data.len() < length {
149 return Err(IgtlError::InvalidSize {
150 expected: length,
151 actual: data.len(),
152 });
153 }
154
155 let command_bytes = &data[..length];
157 let command = String::from_utf8(command_bytes.to_vec())?;
158
159 Ok(CommandMessage {
160 command_id,
161 command_name,
162 encoding,
163 command,
164 })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_message_type() {
174 assert_eq!(CommandMessage::message_type(), "COMMAND");
175 }
176
177 #[test]
178 fn test_new() {
179 let msg = CommandMessage::new(1, "START", "<cmd>start</cmd>");
180 assert_eq!(msg.command_id, 1);
181 assert_eq!(msg.command_name, "START");
182 assert_eq!(msg.encoding, 3);
183 assert_eq!(msg.command, "<cmd>start</cmd>");
184 }
185
186 #[test]
187 fn test_utf8() {
188 let msg = CommandMessage::utf8(2, "STOP", "<cmd>停止</cmd>");
189 assert_eq!(msg.encoding, 106);
190 }
191
192 #[test]
193 fn test_with_encoding() {
194 let msg = CommandMessage::with_encoding(3, "TEST", 42, "<test/>");
195 assert_eq!(msg.encoding, 42);
196 }
197
198 #[test]
199 fn test_as_str() {
200 let msg = CommandMessage::new(1, "CMD", "test");
201 assert_eq!(msg.as_str(), "test");
202 }
203
204 #[test]
205 fn test_encode_simple() {
206 let msg = CommandMessage::new(100, "START", "GO");
207 let encoded = msg.encode_content().unwrap();
208
209 assert_eq!(
211 u32::from_be_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]),
212 100
213 );
214
215 assert_eq!(u16::from_be_bytes([encoded[24], encoded[25]]), 3);
217
218 assert_eq!(
220 u32::from_be_bytes([encoded[26], encoded[27], encoded[28], encoded[29]]),
221 2
222 );
223 }
224
225 #[test]
226 fn test_roundtrip_simple() {
227 let original = CommandMessage::new(42, "TEST", "Hello");
228 let encoded = original.encode_content().unwrap();
229 let decoded = CommandMessage::decode_content(&encoded).unwrap();
230
231 assert_eq!(decoded.command_id, original.command_id);
232 assert_eq!(decoded.command_name, original.command_name);
233 assert_eq!(decoded.encoding, original.encoding);
234 assert_eq!(decoded.command, original.command);
235 }
236
237 #[test]
238 fn test_roundtrip_xml() {
239 let xml =
240 r#"<?xml version="1.0" encoding="UTF-8"?><command><action>start</action></command>"#;
241 let original = CommandMessage::new(1, "XML_CMD", xml);
242 let encoded = original.encode_content().unwrap();
243 let decoded = CommandMessage::decode_content(&encoded).unwrap();
244
245 assert_eq!(decoded.command, xml);
246 }
247
248 #[test]
249 fn test_roundtrip_utf8() {
250 let original = CommandMessage::utf8(5, "日本語", "こんにちは世界");
251 let encoded = original.encode_content().unwrap();
252 let decoded = CommandMessage::decode_content(&encoded).unwrap();
253
254 assert_eq!(decoded.command_name, original.command_name);
255 assert_eq!(decoded.command, original.command);
256 }
257
258 #[test]
259 fn test_name_truncation() {
260 let long_name = "ThisIsAVeryLongCommandNameThatExceedsTwentyCharacters";
261 let msg = CommandMessage::new(1, long_name, "test");
262 let encoded = msg.encode_content().unwrap();
263 let decoded = CommandMessage::decode_content(&encoded).unwrap();
264
265 assert!(decoded.command_name.len() < 20);
266 }
267
268 #[test]
269 fn test_empty_command() {
270 let msg = CommandMessage::new(0, "EMPTY", "");
271 let encoded = msg.encode_content().unwrap();
272 let decoded = CommandMessage::decode_content(&encoded).unwrap();
273
274 assert_eq!(decoded.command, "");
275 }
276
277 #[test]
278 fn test_decode_invalid_size() {
279 let data = vec![0u8; 10]; let result = CommandMessage::decode_content(&data);
281 assert!(result.is_err());
282 }
283
284 #[test]
285 fn test_decode_truncated_command() {
286 let mut data = vec![0u8; 30]; data[26..30].copy_from_slice(&10u32.to_be_bytes());
289 let result = CommandMessage::decode_content(&data);
292 assert!(result.is_err());
293 }
294}