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}