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::protocol::message::Message;
8use crate::error::{IgtlError, Result};
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
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        // Should be truncated to 20 characters
197        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        // Check that error_name field is exactly 20 bytes
213        // Offset: 2 (code) + 8 (subcode) = 10
214        let name_field = &encoded[10..30];
215        assert_eq!(name_field.len(), 20);
216
217        // First 5 bytes should be "Short"
218        assert_eq!(&name_field[0..5], b"Short");
219
220        // Remaining should be null-padded
221        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        // Last byte should be null terminator
232        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        // Verify big-endian encoding of code
254        assert_eq!(encoded[0], 0x01);
255        assert_eq!(encoded[1], 0x02);
256
257        // Verify big-endian encoding of subcode
258        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}