openigtlink_rust/protocol/types/
status.rs

1//! STATUS message type implementation
2//!
3//! The STATUS message type is used to notify the receiver about the current
4//! status of the sender. It can contain status code, subcode, error name,
5//! and a status string.
6
7use crate::error::{IgtlError, Result};
8use crate::protocol::message::Message;
9use bytes::{Buf, BufMut};
10
11/// STATUS message containing device status information
12///
13/// # OpenIGTLink Specification
14/// - Message type: "STATUS"
15/// - Body size: Variable (30 bytes minimum + status_string length + 1)
16/// - Encoding:
17///   - Code: u16 (2 bytes, big-endian)
18///   - Subcode: i64 (8 bytes, big-endian)
19///   - Error name: 20 bytes (null-padded)
20///   - Status string: variable length (null-terminated)
21#[derive(Debug, Clone, PartialEq)]
22pub struct StatusMessage {
23    /// Status code (0 = invalid, 1 = OK, others are device-specific)
24    pub code: u16,
25    /// Sub-code for additional status information
26    pub subcode: i64,
27    /// Error name (max 20 characters)
28    pub error_name: String,
29    /// Status message string
30    pub status_string: String,
31}
32
33impl StatusMessage {
34    /// Create a new STATUS message with OK status
35    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    /// Create a new STATUS message with error status
45    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        // Encode code (2 bytes, big-endian)
64        buf.put_u16(self.code);
65
66        // Encode subcode (8 bytes, big-endian)
67        buf.put_i64(self.subcode);
68
69        // Encode error_name (20 bytes, null-padded)
70        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        // Encode status_string (null-terminated)
78        buf.extend_from_slice(self.status_string.as_bytes());
79        buf.put_u8(0); // null terminator
80
81        Ok(buf)
82    }
83
84    fn decode_content(data: &[u8]) -> Result<Self> {
85        if data.len() < 31 {
86            // Minimum: 2 + 8 + 20 + 1 = 31 bytes
87            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        // Decode code (2 bytes, big-endian)
96        let code = cursor.get_u16();
97
98        // Decode subcode (8 bytes, big-endian)
99        let subcode = cursor.get_i64();
100
101        // Decode error_name (20 bytes, null-padded)
102        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        // Decode status_string (null-terminated)
109        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        // Should be truncated to 20 characters
193        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        // Check that error_name field is exactly 20 bytes
209        // Offset: 2 (code) + 8 (subcode) = 10
210        let name_field = &encoded[10..30];
211        assert_eq!(name_field.len(), 20);
212
213        // First 5 bytes should be "Short"
214        assert_eq!(&name_field[0..5], b"Short");
215
216        // Remaining should be null-padded
217        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        // Last byte should be null terminator
228        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        // Verify big-endian encoding of code
250        assert_eq!(encoded[0], 0x01);
251        assert_eq!(encoded[1], 0x02);
252
253        // Verify big-endian encoding of subcode
254        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}