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