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