1use std::mem::MaybeUninit;
12use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket};
13use std::os::fd::{AsRawFd, RawFd};
14use std::sync::Mutex;
15use std::time::Duration;
16
17use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
18
19use crate::error::{FrankaError, FrankaResult};
20
21pub const DEFAULT_UDP_TIMEOUT: Duration = Duration::from_secs(1);
23
24#[derive(Debug)]
29pub struct UdpChannel {
30 socket: UdpSocket,
31 port: u16,
32 peer: Mutex<Option<SocketAddr>>,
35}
36
37impl UdpChannel {
38 pub fn bind(timeout: Duration) -> FrankaResult<UdpChannel> {
40 let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))
41 .map_err(|e| FrankaError::Network(format!("libfranka: {e}")))?;
42 socket
43 .set_read_timeout(Some(timeout))
44 .map_err(|e| FrankaError::Network(format!("libfranka: {e}")))?;
45 let port = socket
46 .local_addr()
47 .map_err(|e| FrankaError::Network(format!("libfranka: {e}")))?
48 .port();
49 Ok(UdpChannel {
50 socket,
51 port,
52 peer: Mutex::new(None),
53 })
54 }
55
56 pub fn port(&self) -> u16 {
58 self.port
59 }
60
61 pub fn peer(&self) -> Option<SocketAddr> {
63 *self.peer.lock().unwrap_or_else(|e| e.into_inner())
64 }
65
66 pub fn try_receive(&self, buf: &mut [u8]) -> FrankaResult<Option<usize>> {
80 match self.recvfrom(buf, true) {
81 Ok((n, from)) => {
82 self.remember(from);
83 Ok(Some(n))
84 }
85 Err(e)
86 if e.kind() == std::io::ErrorKind::WouldBlock
87 || e.kind() == std::io::ErrorKind::TimedOut =>
88 {
89 Ok(None)
90 }
91 Err(e) => Err(FrankaError::Network(format!("libfranka: UDP receive: {e}"))),
92 }
93 }
94
95 pub fn blocking_receive(&self, buf: &mut [u8]) -> FrankaResult<usize> {
105 match self.recvfrom(buf, false) {
106 Ok((n, from)) => {
107 self.remember(from);
108 Ok(n)
109 }
110 Err(e)
111 if e.kind() == std::io::ErrorKind::WouldBlock
112 || e.kind() == std::io::ErrorKind::TimedOut =>
113 {
114 Err(FrankaError::Network(
115 "libfranka: UDP receive: Timeout".to_string(),
116 ))
117 }
118 Err(e) => Err(FrankaError::Network(format!("libfranka: UDP receive: {e}"))),
119 }
120 }
121
122 pub fn try_receive_struct<T>(&self) -> FrankaResult<Option<T>>
133 where
134 T: FromBytes + IntoBytes + Immutable + KnownLayout,
135 {
136 let mut value = T::new_zeroed();
137 match self.try_receive(value.as_mut_bytes())? {
138 None => Ok(None),
139 Some(n) if n == std::mem::size_of::<T>() => Ok(Some(value)),
140 Some(_) => Err(FrankaError::Protocol(
141 "libfranka: incorrect object size".to_string(),
142 )),
143 }
144 }
145
146 pub fn blocking_receive_struct<T>(&self) -> FrankaResult<T>
152 where
153 T: FromBytes + IntoBytes + Immutable + KnownLayout,
154 {
155 let mut value = T::new_zeroed();
156 let received = self.blocking_receive(value.as_mut_bytes())?;
157 if received != std::mem::size_of::<T>() {
158 return Err(FrankaError::Protocol(
159 "libfranka: incorrect object size".to_string(),
160 ));
161 }
162 Ok(value)
163 }
164
165 pub fn send(&self, addr: SocketAddr, data: &[u8]) -> FrankaResult<()> {
170 let sent = self
171 .socket
172 .send_to(data, addr)
173 .map_err(|e| FrankaError::Network(format!("libfranka: UDP send: {e}")))?;
174 if sent != data.len() {
175 return Err(FrankaError::Network(
176 "libfranka: could not send UDP data".to_string(),
177 ));
178 }
179 Ok(())
180 }
181
182 fn remember(&self, from: SocketAddr) {
183 *self.peer.lock().unwrap_or_else(|e| e.into_inner()) = Some(from);
184 }
185
186 fn recvfrom(&self, buf: &mut [u8], non_blocking: bool) -> std::io::Result<(usize, SocketAddr)> {
188 let fd: RawFd = self.socket.as_raw_fd();
189 let flags = if non_blocking { libc::MSG_DONTWAIT } else { 0 };
190 let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
191 loop {
192 let mut len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
193 let n = unsafe {
194 libc::recvfrom(
195 fd,
196 buf.as_mut_ptr().cast(),
197 buf.len(),
198 flags,
199 storage.as_mut_ptr().cast(),
200 &mut len,
201 )
202 };
203 if n < 0 {
204 let e = std::io::Error::last_os_error();
205 if e.kind() == std::io::ErrorKind::Interrupted {
206 continue;
207 }
208 return Err(e);
209 }
210 let addr = unsafe { socket_addr_from_storage(storage.assume_init_ref()) }.ok_or_else(
211 || {
212 std::io::Error::new(
213 std::io::ErrorKind::InvalidData,
214 "unsupported address family",
215 )
216 },
217 )?;
218 return Ok((n as usize, addr));
219 }
220 }
221}
222
223unsafe fn socket_addr_from_storage(storage: &libc::sockaddr_storage) -> Option<SocketAddr> {
228 match storage.ss_family as libc::c_int {
229 libc::AF_INET => {
230 let addr = &*(storage as *const libc::sockaddr_storage as *const libc::sockaddr_in);
231 Some(SocketAddr::V4(SocketAddrV4::new(
232 Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)),
233 u16::from_be(addr.sin_port),
234 )))
235 }
236 libc::AF_INET6 => {
237 let addr = &*(storage as *const libc::sockaddr_storage as *const libc::sockaddr_in6);
238 Some(SocketAddr::V6(SocketAddrV6::new(
239 Ipv6Addr::from(addr.sin6_addr.s6_addr),
240 u16::from_be(addr.sin6_port),
241 u32::from_be(addr.sin6_flowinfo),
242 addr.sin6_scope_id,
243 )))
244 }
245 _ => None,
246 }
247}
248
249pub fn loopback(port: u16) -> SocketAddr {
251 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn learns_the_peer_and_sends_back() {
260 let channel = UdpChannel::bind(Duration::from_millis(200)).unwrap();
261 let server = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
262 server
263 .set_read_timeout(Some(Duration::from_secs(2)))
264 .unwrap();
265
266 let mut buf = [0u8; 64];
267 assert_eq!(channel.try_receive(&mut buf).unwrap(), None);
268 assert_eq!(channel.peer(), None);
269
270 server
271 .send_to(b"state", (Ipv4Addr::LOCALHOST, channel.port()))
272 .unwrap();
273
274 let deadline = std::time::Instant::now() + Duration::from_secs(2);
276 let received = loop {
277 if let Some(n) = channel.try_receive(&mut buf).unwrap() {
278 break n;
279 }
280 assert!(
281 std::time::Instant::now() < deadline,
282 "datagram never arrived"
283 );
284 };
285 assert_eq!(&buf[..received], b"state");
286 assert_eq!(channel.peer(), Some(server.local_addr().unwrap()));
287
288 channel.send(channel.peer().unwrap(), b"command").unwrap();
289 let mut back = [0u8; 64];
290 let (n, from) = server.recv_from(&mut back).unwrap();
291 assert_eq!(&back[..n], b"command");
292 assert_eq!(from.port(), channel.port());
293 }
294
295 #[test]
296 fn typed_receive_checks_the_datagram_size() {
297 use crate::wire::gripper::GripperState;
298 use zerocopy::little_endian::{F64, U16, U32};
299 use zerocopy::IntoBytes;
300
301 let channel = UdpChannel::bind(Duration::from_secs(2)).unwrap();
302 let server = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
303 let target = (Ipv4Addr::LOCALHOST, channel.port());
304
305 assert!(channel
306 .try_receive_struct::<GripperState>()
307 .unwrap()
308 .is_none());
309
310 server.send_to(&[1u8, 2, 3], target).unwrap();
312 let error = channel
313 .blocking_receive_struct::<GripperState>()
314 .unwrap_err();
315 assert_eq!(error.to_string(), "libfranka: incorrect object size");
316 assert!(matches!(error, FrankaError::Protocol(_)));
317
318 let state = GripperState {
320 message_id: U32::new(7),
321 width: F64::new(0.05),
322 max_width: F64::new(0.08),
323 is_grasped: 1,
324 temperature: U16::new(21),
325 };
326 server.send_to(state.as_bytes(), target).unwrap();
327 let received = channel.blocking_receive_struct::<GripperState>().unwrap();
328 assert_eq!(received.message_id.get(), 7);
329 assert_eq!(received.width.get(), 0.05);
330 assert_eq!(received.is_grasped, 1);
331 assert_eq!(received.temperature.get(), 21);
332 }
333
334 #[test]
335 fn blocking_receive_reports_libfranka_timeout_text() {
336 let channel = UdpChannel::bind(Duration::from_millis(50)).unwrap();
337 let mut buf = [0u8; 64];
338 let error = channel.blocking_receive(&mut buf).unwrap_err();
339 assert_eq!(error.to_string(), "libfranka: UDP receive: Timeout");
340 assert!(matches!(error, FrankaError::Network(_)));
341 }
342
343 #[test]
344 fn blocking_receive_returns_a_datagram() {
345 let channel = UdpChannel::bind(Duration::from_secs(2)).unwrap();
346 let server = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
347 let port = channel.port();
348 let sender = std::thread::spawn(move || {
349 std::thread::sleep(Duration::from_millis(20));
350 server
351 .send_to(b"hello", (Ipv4Addr::LOCALHOST, port))
352 .unwrap();
353 });
354
355 let mut buf = [0u8; 64];
356 let n = channel.blocking_receive(&mut buf).unwrap();
357 assert_eq!(&buf[..n], b"hello");
358 sender.join().unwrap();
359 }
360}