openigtlink_rust/protocol/types/
bind.rs

1//! BIND message type implementation
2//!
3//! The BIND message is used to bind multiple OpenIGTLink messages into a single message.
4//! This allows grouping related messages together for synchronized transmission.
5
6use crate::error::{IgtlError, Result};
7use crate::protocol::message::Message;
8use bytes::Buf;
9
10/// Child message entry in BIND message
11#[derive(Debug, Clone, PartialEq)]
12pub struct BindEntry {
13    /// Message type (max 12 chars)
14    pub message_type: String,
15    /// Device name (max 20 chars)
16    pub device_name: String,
17}
18
19impl BindEntry {
20    /// Create a new bind entry
21    pub fn new(message_type: impl Into<String>, device_name: impl Into<String>) -> Self {
22        BindEntry {
23            message_type: message_type.into(),
24            device_name: device_name.into(),
25        }
26    }
27}
28
29/// BIND message for grouping multiple messages
30///
31/// # OpenIGTLink Specification
32/// - Message type: "BIND"
33/// - Format: (TYPE (`char[12]`) + NAME (`char[20]`)) * n
34/// - Each entry: 32 bytes
35/// - Number of child messages determined by body size / 32
36#[derive(Debug, Clone, PartialEq)]
37pub struct BindMessage {
38    /// List of child message entries
39    pub entries: Vec<BindEntry>,
40}
41
42impl BindMessage {
43    /// Create a new BIND message
44    pub fn new(entries: Vec<BindEntry>) -> Self {
45        BindMessage { entries }
46    }
47
48    /// Create an empty BIND message
49    pub fn empty() -> Self {
50        BindMessage {
51            entries: Vec::new(),
52        }
53    }
54
55    /// Add a child message entry
56    pub fn add_entry(&mut self, entry: BindEntry) {
57        self.entries.push(entry);
58    }
59
60    /// Add a child message by type and name
61    pub fn add(&mut self, message_type: impl Into<String>, device_name: impl Into<String>) {
62        self.entries.push(BindEntry::new(message_type, device_name));
63    }
64
65    /// Get number of child messages
66    pub fn len(&self) -> usize {
67        self.entries.len()
68    }
69
70    /// Check if message has no children
71    pub fn is_empty(&self) -> bool {
72        self.entries.is_empty()
73    }
74}
75
76impl Message for BindMessage {
77    fn message_type() -> &'static str {
78        "BIND"
79    }
80
81    fn encode_content(&self) -> Result<Vec<u8>> {
82        let mut buf = Vec::with_capacity(self.entries.len() * 32);
83
84        for entry in &self.entries {
85            // Encode TYPE (char[12])
86            let mut type_bytes = [0u8; 12];
87            let type_str = entry.message_type.as_bytes();
88            let copy_len = type_str.len().min(12);
89            type_bytes[..copy_len].copy_from_slice(&type_str[..copy_len]);
90            buf.extend_from_slice(&type_bytes);
91
92            // Encode NAME (`char[20]`)
93            let mut name_bytes = [0u8; 20];
94            let name_str = entry.device_name.as_bytes();
95            let copy_len = name_str.len().min(20);
96            name_bytes[..copy_len].copy_from_slice(&name_str[..copy_len]);
97            buf.extend_from_slice(&name_bytes);
98        }
99
100        Ok(buf)
101    }
102
103    fn decode_content(mut data: &[u8]) -> Result<Self> {
104        let mut entries = Vec::new();
105
106        while data.len() >= 32 {
107            // Decode TYPE (char[12])
108            let type_bytes = &data[..12];
109            data.advance(12);
110            let type_len = type_bytes.iter().position(|&b| b == 0).unwrap_or(12);
111            let message_type = String::from_utf8(type_bytes[..type_len].to_vec())?;
112
113            // Decode NAME (`char[20]`)
114            let name_bytes = &data[..20];
115            data.advance(20);
116            let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(20);
117            let device_name = String::from_utf8(name_bytes[..name_len].to_vec())?;
118
119            entries.push(BindEntry {
120                message_type,
121                device_name,
122            });
123        }
124
125        if !data.is_empty() {
126            return Err(IgtlError::InvalidSize {
127                expected: 0,
128                actual: data.len(),
129            });
130        }
131
132        Ok(BindMessage { entries })
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_message_type() {
142        assert_eq!(BindMessage::message_type(), "BIND");
143    }
144
145    #[test]
146    fn test_empty() {
147        let msg = BindMessage::empty();
148        assert!(msg.is_empty());
149        assert_eq!(msg.len(), 0);
150    }
151
152    #[test]
153    fn test_new_entry() {
154        let entry = BindEntry::new("TRANSFORM", "Device1");
155        assert_eq!(entry.message_type, "TRANSFORM");
156        assert_eq!(entry.device_name, "Device1");
157    }
158
159    #[test]
160    fn test_add_entry() {
161        let mut msg = BindMessage::empty();
162        msg.add_entry(BindEntry::new("STATUS", "Device2"));
163        assert_eq!(msg.len(), 1);
164    }
165
166    #[test]
167    fn test_add() {
168        let mut msg = BindMessage::empty();
169        msg.add("TRANSFORM", "Device1");
170        msg.add("STATUS", "Device2");
171        assert_eq!(msg.len(), 2);
172    }
173
174    #[test]
175    fn test_encode_single() {
176        let msg = BindMessage::new(vec![BindEntry::new("TRANSFORM", "Device1")]);
177        let encoded = msg.encode_content().unwrap();
178
179        // Each entry is 32 bytes
180        assert_eq!(encoded.len(), 32);
181    }
182
183    #[test]
184    fn test_encode_multiple() {
185        let msg = BindMessage::new(vec![
186            BindEntry::new("TRANSFORM", "Device1"),
187            BindEntry::new("STATUS", "Device2"),
188            BindEntry::new("POSITION", "Device3"),
189        ]);
190        let encoded = msg.encode_content().unwrap();
191
192        assert_eq!(encoded.len(), 96); // 3 * 32
193    }
194
195    #[test]
196    fn test_roundtrip_single() {
197        let original = BindMessage::new(vec![BindEntry::new("TRANSFORM", "SurgicalTool")]);
198
199        let encoded = original.encode_content().unwrap();
200        let decoded = BindMessage::decode_content(&encoded).unwrap();
201
202        assert_eq!(decoded.entries.len(), 1);
203        assert_eq!(decoded.entries[0].message_type, "TRANSFORM");
204        assert_eq!(decoded.entries[0].device_name, "SurgicalTool");
205    }
206
207    #[test]
208    fn test_roundtrip_multiple() {
209        let original = BindMessage::new(vec![
210            BindEntry::new("TRANSFORM", "Device1"),
211            BindEntry::new("STATUS", "Device2"),
212            BindEntry::new("POSITION", "Device3"),
213            BindEntry::new("SENSOR", "Device4"),
214        ]);
215
216        let encoded = original.encode_content().unwrap();
217        let decoded = BindMessage::decode_content(&encoded).unwrap();
218
219        assert_eq!(decoded.entries.len(), 4);
220        assert_eq!(decoded.entries[0].message_type, "TRANSFORM");
221        assert_eq!(decoded.entries[1].message_type, "STATUS");
222        assert_eq!(decoded.entries[2].message_type, "POSITION");
223        assert_eq!(decoded.entries[3].message_type, "SENSOR");
224    }
225
226    #[test]
227    fn test_empty_message() {
228        let msg = BindMessage::empty();
229        let encoded = msg.encode_content().unwrap();
230        let decoded = BindMessage::decode_content(&encoded).unwrap();
231
232        assert!(decoded.is_empty());
233    }
234
235    #[test]
236    fn test_decode_invalid_size() {
237        let data = vec![0u8; 31]; // One byte short
238        let result = BindMessage::decode_content(&data);
239        assert!(result.is_err());
240    }
241
242    #[test]
243    fn test_long_names_truncated() {
244        let msg = BindMessage::new(vec![BindEntry::new(
245            "VERYLONGMESSAGETYPE",
246            "VERYLONGDEVICENAMEOVER20CHARS",
247        )]);
248        let encoded = msg.encode_content().unwrap();
249        let decoded = BindMessage::decode_content(&encoded).unwrap();
250
251        // Should be truncated to 12 and 20 chars respectively
252        assert!(decoded.entries[0].message_type.len() <= 12);
253        assert!(decoded.entries[0].device_name.len() <= 20);
254    }
255}