openigtlink_rust/io/
udp.rs

1//! UDP-based OpenIGTLink communication
2//!
3//! Provides connectionless UDP transport for low-latency applications where
4//! occasional packet loss is acceptable (e.g., real-time tracking).
5//!
6//! # Important Notes
7//!
8//! - **No delivery guarantee**: UDP does not guarantee message delivery or ordering
9//! - **MTU limitation**: Single UDP datagram limited to ~65507 bytes
10//! - **Use cases**: High-frequency tracking data (>60Hz), non-critical status updates
11//! - **Not recommended for**: Large images, critical commands, file transfers
12//!
13//! # Example: High-Speed Tracking
14//!
15//! ```no_run
16//! use openigtlink_rust::io::UdpClient;
17//! use openigtlink_rust::protocol::types::TransformMessage;
18//! use openigtlink_rust::protocol::message::IgtlMessage;
19//!
20//! // Client sends tracking data at 120Hz
21//! let client = UdpClient::bind("0.0.0.0:0")?;
22//!
23//! loop {
24//!     let transform = TransformMessage::identity();
25//!     let msg = IgtlMessage::new(transform, "Tracker")?;
26//!     client.send_to(&msg, "127.0.0.1:18944")?;
27//!     std::thread::sleep(std::time::Duration::from_millis(8)); // 120Hz
28//! }
29//! # Ok::<(), openigtlink_rust::error::IgtlError>(())
30//! ```
31
32use std::net::{SocketAddr, UdpSocket};
33use std::time::Duration;
34
35use crate::error::{IgtlError, Result};
36use crate::protocol::message::{IgtlMessage, Message};
37
38/// Maximum UDP datagram size (IPv4 max - IP header - UDP header)
39/// 65535 (max IP packet) - 20 (IP header) - 8 (UDP header) = 65507 bytes
40pub const MAX_UDP_DATAGRAM_SIZE: usize = 65507;
41
42/// UDP client for sending/receiving OpenIGTLink messages
43///
44/// Provides connectionless communication with low overhead. Suitable for
45/// high-frequency updates where occasional packet loss is acceptable.
46///
47/// # Performance Characteristics
48///
49/// - **Latency**: Lower than TCP (no connection setup, no retransmission)
50/// - **Throughput**: Limited by network MTU (~1500 bytes typical Ethernet)
51/// - **Reliability**: None (packets may be lost, duplicated, or reordered)
52///
53/// # Examples
54///
55/// ```no_run
56/// use openigtlink_rust::io::UdpClient;
57/// use openigtlink_rust::protocol::types::TransformMessage;
58/// use openigtlink_rust::protocol::message::IgtlMessage;
59///
60/// let client = UdpClient::bind("0.0.0.0:0")?;
61/// let transform = TransformMessage::identity();
62/// let msg = IgtlMessage::new(transform, "Tool")?;
63/// client.send_to(&msg, "192.168.1.100:18944")?;
64/// # Ok::<(), openigtlink_rust::error::IgtlError>(())
65/// ```
66pub struct UdpClient {
67    socket: UdpSocket,
68}
69
70impl UdpClient {
71    /// Bind to a local address
72    ///
73    /// # Arguments
74    ///
75    /// * `local_addr` - Local address to bind (use "0.0.0.0:0" for any available port)
76    ///
77    /// # Errors
78    ///
79    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to bind socket (address in use, permission denied, etc.)
80    ///
81    /// # Examples
82    ///
83    /// ```no_run
84    /// use openigtlink_rust::io::UdpClient;
85    ///
86    /// // Bind to any available port
87    /// let client = UdpClient::bind("0.0.0.0:0")?;
88    ///
89    /// // Bind to specific port
90    /// let client = UdpClient::bind("0.0.0.0:18945")?;
91    /// # Ok::<(), openigtlink_rust::error::IgtlError>(())
92    /// ```
93    pub fn bind(local_addr: &str) -> Result<Self> {
94        let socket = UdpSocket::bind(local_addr)?;
95        Ok(UdpClient { socket })
96    }
97
98    /// Send a message to a remote address
99    ///
100    /// # Arguments
101    ///
102    /// * `msg` - Message to send
103    /// * `target` - Target address (e.g., "127.0.0.1:18944")
104    ///
105    /// # Errors
106    ///
107    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Network transmission failed
108    /// - [`IgtlError::BodyTooLarge`](crate::error::IgtlError::BodyTooLarge) - Message exceeds UDP MTU (65507 bytes)
109    ///
110    /// # Examples
111    ///
112    /// ```no_run
113    /// use openigtlink_rust::io::UdpClient;
114    /// use openigtlink_rust::protocol::types::TransformMessage;
115    /// use openigtlink_rust::protocol::message::IgtlMessage;
116    ///
117    /// let client = UdpClient::bind("0.0.0.0:0")?;
118    /// let transform = TransformMessage::identity();
119    /// let msg = IgtlMessage::new(transform, "Device")?;
120    /// client.send_to(&msg, "127.0.0.1:18944")?;
121    /// # Ok::<(), openigtlink_rust::error::IgtlError>(())
122    /// ```
123    pub fn send_to<T: Message>(&self, msg: &IgtlMessage<T>, target: &str) -> Result<()> {
124        let data = msg.encode()?;
125
126        if data.len() > MAX_UDP_DATAGRAM_SIZE {
127            return Err(IgtlError::BodyTooLarge {
128                size: data.len(),
129                max: MAX_UDP_DATAGRAM_SIZE,
130            });
131        }
132
133        self.socket.send_to(&data, target)?;
134        Ok(())
135    }
136
137    /// Receive a message (blocking)
138    ///
139    /// Blocks until a datagram is received. Returns the message and sender address.
140    ///
141    /// # Returns
142    ///
143    /// Tuple of (message, sender_address)
144    ///
145    /// # Errors
146    ///
147    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Network read failed or timeout
148    /// - [`IgtlError::InvalidHeader`](crate::error::IgtlError::InvalidHeader) - Malformed header
149    /// - [`IgtlError::CrcMismatch`](crate::error::IgtlError::CrcMismatch) - Data corruption detected
150    ///
151    /// # Examples
152    ///
153    /// ```no_run
154    /// use openigtlink_rust::io::UdpClient;
155    /// use openigtlink_rust::protocol::types::TransformMessage;
156    ///
157    /// let client = UdpClient::bind("0.0.0.0:18944")?;
158    /// let (msg, sender) = client.receive_from::<TransformMessage>()?;
159    /// println!("Received from {}", sender);
160    /// # Ok::<(), openigtlink_rust::error::IgtlError>(())
161    /// ```
162    pub fn receive_from<T: Message>(&self) -> Result<(IgtlMessage<T>, SocketAddr)> {
163        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
164        let (size, src) = self.socket.recv_from(&mut buf)?;
165
166        let msg = IgtlMessage::decode(&buf[..size])?;
167        Ok((msg, src))
168    }
169
170    /// Set read timeout
171    ///
172    /// # Arguments
173    ///
174    /// * `timeout` - Timeout duration (None for blocking forever)
175    ///
176    /// # Errors
177    ///
178    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to set socket option
179    ///
180    /// # Examples
181    ///
182    /// ```no_run
183    /// use openigtlink_rust::io::UdpClient;
184    /// use std::time::Duration;
185    ///
186    /// let client = UdpClient::bind("0.0.0.0:0")?;
187    /// client.set_read_timeout(Some(Duration::from_secs(5)))?;
188    /// # Ok::<(), openigtlink_rust::error::IgtlError>(())
189    /// ```
190    pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()> {
191        self.socket.set_read_timeout(timeout)?;
192        Ok(())
193    }
194
195    /// Set write timeout
196    ///
197    /// # Arguments
198    ///
199    /// * `timeout` - Timeout duration (None for blocking forever)
200    ///
201    /// # Errors
202    ///
203    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to set socket option
204    pub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<()> {
205        self.socket.set_write_timeout(timeout)?;
206        Ok(())
207    }
208
209    /// Get local socket address
210    ///
211    /// # Errors
212    ///
213    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to get socket address
214    pub fn local_addr(&self) -> Result<SocketAddr> {
215        Ok(self.socket.local_addr()?)
216    }
217}
218
219/// UDP server for receiving OpenIGTLink messages
220///
221/// Listens for incoming datagrams on a specific port.
222///
223/// # Examples
224///
225/// ```no_run
226/// use openigtlink_rust::io::UdpServer;
227/// use openigtlink_rust::protocol::types::TransformMessage;
228/// use openigtlink_rust::protocol::message::IgtlMessage;
229///
230/// # fn main() -> Result<(), openigtlink_rust::error::IgtlError> {
231/// let server = UdpServer::bind("0.0.0.0:18944")?;
232///
233/// # let mut count = 0;
234/// loop {
235///     let (msg, sender) = server.receive::<TransformMessage>()?;
236///     println!("Received from {}", sender);
237///
238///     // Echo back
239///     let response = IgtlMessage::new(msg.content, "Server")?;
240///     server.send_to(&response, sender)?;
241///
242///     # count += 1;
243///     # if count >= 1 { break; }
244/// }
245/// # Ok(())
246/// # }
247/// ```
248pub struct UdpServer {
249    socket: UdpSocket,
250}
251
252impl UdpServer {
253    /// Bind server to an address
254    ///
255    /// # Arguments
256    ///
257    /// * `addr` - Address to bind (e.g., "0.0.0.0:18944")
258    ///
259    /// # Errors
260    ///
261    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to bind (port in use, permission denied, etc.)
262    ///
263    /// # Examples
264    ///
265    /// ```no_run
266    /// use openigtlink_rust::io::UdpServer;
267    ///
268    /// let server = UdpServer::bind("0.0.0.0:18944")?;
269    /// # Ok::<(), openigtlink_rust::error::IgtlError>(())
270    /// ```
271    pub fn bind(addr: &str) -> Result<Self> {
272        let socket = UdpSocket::bind(addr)?;
273        Ok(UdpServer { socket })
274    }
275
276    /// Receive a message (blocking)
277    ///
278    /// # Returns
279    ///
280    /// Tuple of (message, sender_address)
281    ///
282    /// # Errors
283    ///
284    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Network read failed or timeout
285    /// - [`IgtlError::InvalidHeader`](crate::error::IgtlError::InvalidHeader) - Malformed header
286    /// - [`IgtlError::CrcMismatch`](crate::error::IgtlError::CrcMismatch) - Data corruption
287    pub fn receive<T: Message>(&self) -> Result<(IgtlMessage<T>, SocketAddr)> {
288        let mut buf = vec![0u8; MAX_UDP_DATAGRAM_SIZE];
289        let (size, src) = self.socket.recv_from(&mut buf)?;
290
291        let msg = IgtlMessage::decode(&buf[..size])?;
292        Ok((msg, src))
293    }
294
295    /// Send a response to a specific address
296    ///
297    /// # Arguments
298    ///
299    /// * `msg` - Message to send
300    /// * `target` - Target socket address
301    ///
302    /// # Errors
303    ///
304    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Network transmission failed
305    /// - [`IgtlError::BodyTooLarge`](crate::error::IgtlError::BodyTooLarge) - Message exceeds UDP MTU
306    pub fn send_to<T: Message>(&self, msg: &IgtlMessage<T>, target: SocketAddr) -> Result<()> {
307        let data = msg.encode()?;
308
309        if data.len() > MAX_UDP_DATAGRAM_SIZE {
310            return Err(IgtlError::BodyTooLarge {
311                size: data.len(),
312                max: MAX_UDP_DATAGRAM_SIZE,
313            });
314        }
315
316        self.socket.send_to(&data, target)?;
317        Ok(())
318    }
319
320    /// Set read timeout
321    ///
322    /// # Arguments
323    ///
324    /// * `timeout` - Timeout duration (None for blocking forever)
325    ///
326    /// # Errors
327    ///
328    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to set socket option
329    pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()> {
330        self.socket.set_read_timeout(timeout)?;
331        Ok(())
332    }
333
334    /// Get local socket address
335    ///
336    /// # Errors
337    ///
338    /// - [`IgtlError::Io`](crate::error::IgtlError::Io) - Failed to get socket address
339    pub fn local_addr(&self) -> Result<SocketAddr> {
340        Ok(self.socket.local_addr()?)
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use crate::protocol::types::TransformMessage;
348
349    #[test]
350    fn test_max_datagram_size() {
351        assert_eq!(MAX_UDP_DATAGRAM_SIZE, 65507);
352    }
353
354    #[test]
355    fn test_client_bind() {
356        let client = UdpClient::bind("127.0.0.1:0");
357        assert!(client.is_ok());
358    }
359
360    #[test]
361    fn test_server_bind() {
362        let server = UdpServer::bind("127.0.0.1:0");
363        assert!(server.is_ok());
364    }
365
366    #[test]
367    fn test_local_addr() {
368        let client = UdpClient::bind("127.0.0.1:0").unwrap();
369        let addr = client.local_addr().unwrap();
370        assert_eq!(addr.ip().to_string(), "127.0.0.1");
371        assert!(addr.port() > 0);
372    }
373
374    #[test]
375    fn test_send_receive() {
376        // Bind server first to get a known port
377        let server = UdpServer::bind("127.0.0.1:0").unwrap();
378        let server_addr = server.local_addr().unwrap();
379
380        // Create client
381        let client = UdpClient::bind("127.0.0.1:0").unwrap();
382
383        // Send message
384        let transform = TransformMessage::identity();
385        let msg = IgtlMessage::new(transform, "TestDevice").unwrap();
386        client.send_to(&msg, &server_addr.to_string()).unwrap();
387
388        // Receive message
389        let (received_msg, sender) = server.receive::<TransformMessage>().unwrap();
390        assert_eq!(
391            received_msg.header.device_name.as_str().unwrap(),
392            "TestDevice"
393        );
394        assert_eq!(sender, client.local_addr().unwrap());
395    }
396
397    #[test]
398    fn test_timeout() {
399        let client = UdpClient::bind("127.0.0.1:0").unwrap();
400        client
401            .set_read_timeout(Some(Duration::from_millis(100)))
402            .unwrap();
403
404        // Should timeout since no data is available
405        let result = client.receive_from::<TransformMessage>();
406        assert!(result.is_err());
407    }
408
409    #[test]
410    fn test_message_too_large() {
411        let _client = UdpClient::bind("127.0.0.1:0").unwrap();
412
413        // This would fail during encoding if we tried to create a message > 65507 bytes
414        // Verify the constant is within valid UDP datagram size
415        const _: () = assert!(MAX_UDP_DATAGRAM_SIZE < 65536);
416    }
417}