openigtlink_rust/protocol/types/
string.rs

1//! STRING message type implementation
2//!
3//! The STRING message type is used for transferring character strings.
4//! It supports strings up to 65535 bytes with configurable character encoding.
5
6use crate::protocol::message::Message;
7use crate::error::{IgtlError, Result};
8use bytes::{Buf, BufMut};
9
10/// STRING message containing a text string with encoding information
11///
12/// # OpenIGTLink Specification
13/// - Message type: "STRING"
14/// - Body format: ENCODING (uint16) + LENGTH (uint16) + STRING (uint8[LENGTH])
15/// - Encoding: MIBenum value (default: 3 = US-ASCII)
16/// - Max length: 65535 bytes
17#[derive(Debug, Clone, PartialEq)]
18pub struct StringMessage {
19    /// Character encoding as MIBenum value
20    ///
21    /// Common values:
22    /// - 3: US-ASCII (ANSI-X3.4-1968) - recommended
23    /// - 106: UTF-8
24    /// See: <http://www.iana.org/assignments/character-sets>
25    pub encoding: u16,
26
27    /// The text content
28    pub string: String,
29}
30
31impl StringMessage {
32    /// Create a new STRING message with US-ASCII encoding (default)
33    pub fn new(string: impl Into<String>) -> Self {
34        StringMessage {
35            encoding: 3, // US-ASCII
36            string: string.into(),
37        }
38    }
39
40    /// Create a STRING message with UTF-8 encoding
41    pub fn utf8(string: impl Into<String>) -> Self {
42        StringMessage {
43            encoding: 106, // UTF-8
44            string: string.into(),
45        }
46    }
47
48    /// Create a STRING message with custom encoding
49    pub fn with_encoding(encoding: u16, string: impl Into<String>) -> Self {
50        StringMessage {
51            encoding,
52            string: string.into(),
53        }
54    }
55
56    /// Get the string content as a reference
57    pub fn as_str(&self) -> &str {
58        &self.string
59    }
60
61    /// Get the length of the string in bytes
62    pub fn len(&self) -> usize {
63        self.string.len()
64    }
65
66    /// Check if the string is empty
67    pub fn is_empty(&self) -> bool {
68        self.string.is_empty()
69    }
70}
71
72impl Message for StringMessage {
73    fn message_type() -> &'static str {
74        "STRING"
75    }
76
77    fn encode_content(&self) -> Result<Vec<u8>> {
78        let string_bytes = self.string.as_bytes();
79        let length = string_bytes.len();
80
81        if length > 65535 {
82            return Err(IgtlError::BodyTooLarge {
83                size: length,
84                max: 65535,
85            });
86        }
87
88        let mut buf = Vec::with_capacity(4 + length);
89
90        // Encode ENCODING (uint16)
91        buf.put_u16(self.encoding);
92
93        // Encode LENGTH (uint16)
94        buf.put_u16(length as u16);
95
96        // Encode STRING bytes
97        buf.extend_from_slice(string_bytes);
98
99        Ok(buf)
100    }
101
102    fn decode_content(mut data: &[u8]) -> Result<Self> {
103        if data.len() < 4 {
104            return Err(IgtlError::InvalidSize {
105                expected: 4,
106                actual: data.len(),
107            });
108        }
109
110        // Decode ENCODING
111        let encoding = data.get_u16();
112
113        // Decode LENGTH
114        let length = data.get_u16() as usize;
115
116        // Check remaining data size
117        if data.len() < length {
118            return Err(IgtlError::InvalidSize {
119                expected: length,
120                actual: data.len(),
121            });
122        }
123
124        // Decode STRING
125        let string_bytes = &data[..length];
126        let string = String::from_utf8(string_bytes.to_vec())?;
127
128        Ok(StringMessage { encoding, string })
129    }
130}
131
132impl From<&str> for StringMessage {
133    fn from(s: &str) -> Self {
134        StringMessage::new(s)
135    }
136}
137
138impl From<String> for StringMessage {
139    fn from(s: String) -> Self {
140        StringMessage::new(s)
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_message_type() {
150        assert_eq!(StringMessage::message_type(), "STRING");
151    }
152
153    #[test]
154    fn test_new() {
155        let msg = StringMessage::new("Hello");
156        assert_eq!(msg.encoding, 3); // US-ASCII
157        assert_eq!(msg.string, "Hello");
158    }
159
160    #[test]
161    fn test_utf8() {
162        let msg = StringMessage::utf8("こんにちは");
163        assert_eq!(msg.encoding, 106); // UTF-8
164        assert_eq!(msg.string, "こんにちは");
165    }
166
167    #[test]
168    fn test_with_encoding() {
169        let msg = StringMessage::with_encoding(42, "Test");
170        assert_eq!(msg.encoding, 42);
171        assert_eq!(msg.string, "Test");
172    }
173
174    #[test]
175    fn test_as_str() {
176        let msg = StringMessage::new("Test");
177        assert_eq!(msg.as_str(), "Test");
178    }
179
180    #[test]
181    fn test_len() {
182        let msg = StringMessage::new("Hello");
183        assert_eq!(msg.len(), 5);
184    }
185
186    #[test]
187    fn test_is_empty() {
188        let msg1 = StringMessage::new("");
189        assert!(msg1.is_empty());
190
191        let msg2 = StringMessage::new("test");
192        assert!(!msg2.is_empty());
193    }
194
195    #[test]
196    fn test_encode_simple() {
197        let msg = StringMessage::new("Test");
198        let encoded = msg.encode_content().unwrap();
199
200        // Check header: encoding (2 bytes) + length (2 bytes)
201        assert_eq!(encoded[0..2], [0, 3]); // Encoding = 3 (US-ASCII)
202        assert_eq!(encoded[2..4], [0, 4]); // Length = 4
203        assert_eq!(&encoded[4..], b"Test");
204    }
205
206    #[test]
207    fn test_roundtrip_ascii() {
208        let original = StringMessage::new("Hello, World!");
209        let encoded = original.encode_content().unwrap();
210        let decoded = StringMessage::decode_content(&encoded).unwrap();
211
212        assert_eq!(decoded.encoding, original.encoding);
213        assert_eq!(decoded.string, original.string);
214    }
215
216    #[test]
217    fn test_roundtrip_utf8() {
218        let original = StringMessage::utf8("Hello 世界 🌍");
219        let encoded = original.encode_content().unwrap();
220        let decoded = StringMessage::decode_content(&encoded).unwrap();
221
222        assert_eq!(decoded.encoding, original.encoding);
223        assert_eq!(decoded.string, original.string);
224    }
225
226    #[test]
227    fn test_empty_string() {
228        let msg = StringMessage::new("");
229        let encoded = msg.encode_content().unwrap();
230        let decoded = StringMessage::decode_content(&encoded).unwrap();
231
232        assert_eq!(decoded.string, "");
233        assert_eq!(encoded.len(), 4); // Only header, no content
234    }
235
236    #[test]
237    fn test_max_length() {
238        let long_string = "A".repeat(65535);
239        let msg = StringMessage::new(long_string.clone());
240        let encoded = msg.encode_content().unwrap();
241        let decoded = StringMessage::decode_content(&encoded).unwrap();
242
243        assert_eq!(decoded.string, long_string);
244    }
245
246    #[test]
247    fn test_too_long() {
248        let too_long = "A".repeat(65536);
249        let msg = StringMessage::new(too_long);
250        let result = msg.encode_content();
251
252        assert!(result.is_err());
253    }
254
255    #[test]
256    fn test_decode_invalid_size() {
257        let data = vec![0, 3]; // Only 2 bytes, need at least 4
258        let result = StringMessage::decode_content(&data);
259        assert!(result.is_err());
260    }
261
262    #[test]
263    fn test_decode_truncated() {
264        let mut data = vec![0, 3, 0, 10]; // Encoding=3, Length=10
265        data.extend_from_slice(b"Short"); // Only 5 bytes instead of 10
266
267        let result = StringMessage::decode_content(&data);
268        assert!(result.is_err());
269    }
270
271    #[test]
272    fn test_from_str() {
273        let msg: StringMessage = "Test".into();
274        assert_eq!(msg.string, "Test");
275        assert_eq!(msg.encoding, 3);
276    }
277
278    #[test]
279    fn test_from_string() {
280        let s = String::from("Test");
281        let msg: StringMessage = s.into();
282        assert_eq!(msg.string, "Test");
283        assert_eq!(msg.encoding, 3);
284    }
285
286    #[test]
287    fn test_big_endian_encoding() {
288        let msg = StringMessage::new("X");
289        let encoded = msg.encode_content().unwrap();
290
291        // Encoding = 3: should be [0x00, 0x03] in big-endian
292        assert_eq!(encoded[0], 0x00);
293        assert_eq!(encoded[1], 0x03);
294
295        // Length = 1: should be [0x00, 0x01] in big-endian
296        assert_eq!(encoded[2], 0x00);
297        assert_eq!(encoded[3], 0x01);
298    }
299}