openigtlink_rust/protocol/types/
status.rs1use crate::error::{IgtlError, Result};
8use crate::protocol::message::Message;
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.iter().take_while(|&&b| b != 0).copied().collect();
111
112 let status_string = String::from_utf8(status_bytes)?;
113
114 Ok(StatusMessage {
115 code,
116 subcode,
117 error_name,
118 status_string,
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_message_type() {
129 assert_eq!(StatusMessage::message_type(), "STATUS");
130 }
131
132 #[test]
133 fn test_ok_status() {
134 let status = StatusMessage::ok("Operation successful");
135 assert_eq!(status.code, 1);
136 assert_eq!(status.subcode, 0);
137 assert_eq!(status.error_name, "");
138 assert_eq!(status.status_string, "Operation successful");
139 }
140
141 #[test]
142 fn test_error_status() {
143 let status = StatusMessage::error("ERR_TIMEOUT", "Connection timeout");
144 assert_eq!(status.code, 0);
145 assert_eq!(status.error_name, "ERR_TIMEOUT");
146 assert_eq!(status.status_string, "Connection timeout");
147 }
148
149 #[test]
150 fn test_status_roundtrip() {
151 let original = StatusMessage {
152 code: 1,
153 subcode: 42,
154 error_name: "TestError".to_string(),
155 status_string: "Test status message".to_string(),
156 };
157
158 let encoded = original.encode_content().unwrap();
159 let decoded = StatusMessage::decode_content(&encoded).unwrap();
160
161 assert_eq!(original, decoded);
162 }
163
164 #[test]
165 fn test_empty_strings() {
166 let status = StatusMessage {
167 code: 1,
168 subcode: 0,
169 error_name: String::new(),
170 status_string: String::new(),
171 };
172
173 let encoded = status.encode_content().unwrap();
174 let decoded = StatusMessage::decode_content(&encoded).unwrap();
175
176 assert_eq!(status, decoded);
177 }
178
179 #[test]
180 fn test_long_error_name_truncation() {
181 let long_name = "ThisIsAVeryLongErrorNameThatExceeds20Characters";
182 let status = StatusMessage {
183 code: 0,
184 subcode: 0,
185 error_name: long_name.to_string(),
186 status_string: "Error".to_string(),
187 };
188
189 let encoded = status.encode_content().unwrap();
190 let decoded = StatusMessage::decode_content(&encoded).unwrap();
191
192 assert_eq!(decoded.error_name.len(), 20);
194 assert_eq!(&decoded.error_name, &long_name[..20]);
195 }
196
197 #[test]
198 fn test_null_padding() {
199 let status = StatusMessage {
200 code: 1,
201 subcode: 0,
202 error_name: "Short".to_string(),
203 status_string: "OK".to_string(),
204 };
205
206 let encoded = status.encode_content().unwrap();
207
208 let name_field = &encoded[10..30];
211 assert_eq!(name_field.len(), 20);
212
213 assert_eq!(&name_field[0..5], b"Short");
215
216 for &byte in &name_field[5..] {
218 assert_eq!(byte, 0);
219 }
220 }
221
222 #[test]
223 fn test_null_termination() {
224 let status = StatusMessage::ok("Test");
225 let encoded = status.encode_content().unwrap();
226
227 assert_eq!(encoded.last(), Some(&0));
229 }
230
231 #[test]
232 fn test_decode_invalid_size() {
233 let short_data = vec![0u8; 20];
234 let result = StatusMessage::decode_content(&short_data);
235 assert!(matches!(result, Err(IgtlError::InvalidSize { .. })));
236 }
237
238 #[test]
239 fn test_big_endian_encoding() {
240 let status = StatusMessage {
241 code: 0x0102,
242 subcode: 0x0102030405060708,
243 error_name: String::new(),
244 status_string: String::new(),
245 };
246
247 let encoded = status.encode_content().unwrap();
248
249 assert_eq!(encoded[0], 0x01);
251 assert_eq!(encoded[1], 0x02);
252
253 assert_eq!(encoded[2], 0x01);
255 assert_eq!(encoded[3], 0x02);
256 assert_eq!(encoded[4], 0x03);
257 assert_eq!(encoded[5], 0x04);
258 }
259}