openigtlink_rust/protocol/types/
status.rs1use crate::protocol::message::Message;
8use crate::error::{IgtlError, Result};
9use bytes::{Buf, BufMut};
10
11#[derive(Debug, Clone, PartialEq)]
22pub struct StatusMessage {
23 pub code: u16,
25 pub subcode: i64,
27 pub error_name: String,
29 pub status_string: String,
31}
32
33impl StatusMessage {
34 pub fn ok(status_string: &str) -> Self {
36 StatusMessage {
37 code: 1,
38 subcode: 0,
39 error_name: String::new(),
40 status_string: status_string.to_string(),
41 }
42 }
43
44 pub fn error(error_name: &str, status_string: &str) -> Self {
46 StatusMessage {
47 code: 0,
48 subcode: 0,
49 error_name: error_name.to_string(),
50 status_string: status_string.to_string(),
51 }
52 }
53}
54
55impl Message for StatusMessage {
56 fn message_type() -> &'static str {
57 "STATUS"
58 }
59
60 fn encode_content(&self) -> Result<Vec<u8>> {
61 let mut buf = Vec::new();
62
63 buf.put_u16(self.code);
65
66 buf.put_i64(self.subcode);
68
69 let mut name_bytes = [0u8; 20];
71 let name_len = self.error_name.len().min(20);
72 if name_len > 0 {
73 name_bytes[..name_len].copy_from_slice(&self.error_name.as_bytes()[..name_len]);
74 }
75 buf.extend_from_slice(&name_bytes);
76
77 buf.extend_from_slice(self.status_string.as_bytes());
79 buf.put_u8(0); Ok(buf)
82 }
83
84 fn decode_content(data: &[u8]) -> Result<Self> {
85 if data.len() < 31 {
86 return Err(IgtlError::InvalidSize {
88 expected: 31,
89 actual: data.len(),
90 });
91 }
92
93 let mut cursor = std::io::Cursor::new(data);
94
95 let code = cursor.get_u16();
97
98 let subcode = cursor.get_i64();
100
101 let mut name_bytes = [0u8; 20];
103 cursor.copy_to_slice(&mut name_bytes);
104 let error_name = String::from_utf8_lossy(&name_bytes)
105 .trim_end_matches('\0')
106 .to_string();
107
108 let remaining = &data[cursor.position() as usize..];
110 let status_bytes: Vec<u8> = remaining
111 .iter()
112 .take_while(|&&b| b != 0)
113 .copied()
114 .collect();
115
116 let status_string = String::from_utf8(status_bytes)?;
117
118 Ok(StatusMessage {
119 code,
120 subcode,
121 error_name,
122 status_string,
123 })
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_message_type() {
133 assert_eq!(StatusMessage::message_type(), "STATUS");
134 }
135
136 #[test]
137 fn test_ok_status() {
138 let status = StatusMessage::ok("Operation successful");
139 assert_eq!(status.code, 1);
140 assert_eq!(status.subcode, 0);
141 assert_eq!(status.error_name, "");
142 assert_eq!(status.status_string, "Operation successful");
143 }
144
145 #[test]
146 fn test_error_status() {
147 let status = StatusMessage::error("ERR_TIMEOUT", "Connection timeout");
148 assert_eq!(status.code, 0);
149 assert_eq!(status.error_name, "ERR_TIMEOUT");
150 assert_eq!(status.status_string, "Connection timeout");
151 }
152
153 #[test]
154 fn test_status_roundtrip() {
155 let original = StatusMessage {
156 code: 1,
157 subcode: 42,
158 error_name: "TestError".to_string(),
159 status_string: "Test status message".to_string(),
160 };
161
162 let encoded = original.encode_content().unwrap();
163 let decoded = StatusMessage::decode_content(&encoded).unwrap();
164
165 assert_eq!(original, decoded);
166 }
167
168 #[test]
169 fn test_empty_strings() {
170 let status = StatusMessage {
171 code: 1,
172 subcode: 0,
173 error_name: String::new(),
174 status_string: String::new(),
175 };
176
177 let encoded = status.encode_content().unwrap();
178 let decoded = StatusMessage::decode_content(&encoded).unwrap();
179
180 assert_eq!(status, decoded);
181 }
182
183 #[test]
184 fn test_long_error_name_truncation() {
185 let long_name = "ThisIsAVeryLongErrorNameThatExceeds20Characters";
186 let status = StatusMessage {
187 code: 0,
188 subcode: 0,
189 error_name: long_name.to_string(),
190 status_string: "Error".to_string(),
191 };
192
193 let encoded = status.encode_content().unwrap();
194 let decoded = StatusMessage::decode_content(&encoded).unwrap();
195
196 assert_eq!(decoded.error_name.len(), 20);
198 assert_eq!(&decoded.error_name, &long_name[..20]);
199 }
200
201 #[test]
202 fn test_null_padding() {
203 let status = StatusMessage {
204 code: 1,
205 subcode: 0,
206 error_name: "Short".to_string(),
207 status_string: "OK".to_string(),
208 };
209
210 let encoded = status.encode_content().unwrap();
211
212 let name_field = &encoded[10..30];
215 assert_eq!(name_field.len(), 20);
216
217 assert_eq!(&name_field[0..5], b"Short");
219
220 for &byte in &name_field[5..] {
222 assert_eq!(byte, 0);
223 }
224 }
225
226 #[test]
227 fn test_null_termination() {
228 let status = StatusMessage::ok("Test");
229 let encoded = status.encode_content().unwrap();
230
231 assert_eq!(encoded.last(), Some(&0));
233 }
234
235 #[test]
236 fn test_decode_invalid_size() {
237 let short_data = vec![0u8; 20];
238 let result = StatusMessage::decode_content(&short_data);
239 assert!(matches!(result, Err(IgtlError::InvalidSize { .. })));
240 }
241
242 #[test]
243 fn test_big_endian_encoding() {
244 let status = StatusMessage {
245 code: 0x0102,
246 subcode: 0x0102030405060708,
247 error_name: String::new(),
248 status_string: String::new(),
249 };
250
251 let encoded = status.encode_content().unwrap();
252
253 assert_eq!(encoded[0], 0x01);
255 assert_eq!(encoded[1], 0x02);
256
257 assert_eq!(encoded[2], 0x01);
259 assert_eq!(encoded[3], 0x02);
260 assert_eq!(encoded[4], 0x03);
261 assert_eq!(encoded[5], 0x04);
262 }
263}