1pub mod tcp;
7pub mod udp;
8
9use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
10use std::time::Duration;
11
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
13
14use crate::error::{FrankaError, FrankaResult};
15use crate::wire::gripper::{
16 GripperConnectRequest, GripperConnectResponse, GripperConnectStatus, GRIPPER_VERSION,
17};
18use crate::wire::robot::{ConnectRequest, ConnectResponse, ConnectStatus};
19use crate::wire::{parse_response, HeaderLayout};
20
21pub use tcp::{TcpSession, DEFAULT_CONNECT_TIMEOUT, POLL_TIMEOUT};
22pub use udp::{UdpChannel, DEFAULT_UDP_TIMEOUT};
23
24#[derive(Debug)]
26pub struct Network {
27 pub tcp: TcpSession,
29 pub udp: UdpChannel,
31 pub server_ip: IpAddr,
33}
34
35impl Network {
36 pub fn connect(host: &str, tcp_port: u16, layout: HeaderLayout) -> FrankaResult<Network> {
42 Network::connect_with_timeouts(
43 host,
44 tcp_port,
45 layout,
46 DEFAULT_CONNECT_TIMEOUT,
47 DEFAULT_UDP_TIMEOUT,
48 )
49 }
50
51 pub fn connect_with_timeouts(
53 host: &str,
54 tcp_port: u16,
55 layout: HeaderLayout,
56 tcp_timeout: Duration,
57 udp_timeout: Duration,
58 ) -> FrankaResult<Network> {
59 let addr = resolve(host, tcp_port)?;
60 let tcp = TcpSession::connect(addr, tcp_timeout, layout)?;
61 let udp = UdpChannel::bind(udp_timeout)?;
62 Ok(Network {
63 tcp,
64 udp,
65 server_ip: addr.ip(),
66 })
67 }
68
69 pub fn udp_peer(&self) -> Option<SocketAddr> {
71 self.udp.peer()
72 }
73
74 pub fn try_receive_struct<T>(&self) -> FrankaResult<Option<T>>
83 where
84 T: FromBytes + IntoBytes + Immutable + KnownLayout,
85 {
86 self.shutdown_tcp_on_network_error(self.udp.try_receive_struct())
87 }
88
89 pub fn blocking_receive_struct<T>(&self) -> FrankaResult<T>
96 where
97 T: FromBytes + IntoBytes + Immutable + KnownLayout,
98 {
99 self.shutdown_tcp_on_network_error(self.udp.blocking_receive_struct())
100 }
101
102 pub fn try_receive_bytes(&self, buf: &mut [u8]) -> FrankaResult<Option<usize>> {
112 self.shutdown_tcp_on_network_error(self.udp.try_receive(buf))
113 }
114
115 pub fn blocking_receive_bytes(&self, buf: &mut [u8]) -> FrankaResult<usize> {
120 self.shutdown_tcp_on_network_error(self.udp.blocking_receive(buf))
121 }
122
123 fn shutdown_tcp_on_network_error<T>(&self, result: FrankaResult<T>) -> FrankaResult<T> {
124 if let Err(FrankaError::Network(_)) = &result {
125 self.tcp.shutdown();
126 }
127 result
128 }
129
130 pub fn send_udp(&self, data: &[u8]) -> FrankaResult<()> {
136 let peer = self.udp_peer().ok_or_else(|| {
137 FrankaError::Network(
138 "libfranka: UDP send: no state received yet, server address unknown".to_string(),
139 )
140 })?;
141 self.udp.send(peer, data)
142 }
143}
144
145fn resolve(host: &str, default_port: u16) -> FrankaResult<SocketAddr> {
148 if let Ok(mut addrs) = host.to_socket_addrs() {
149 if let Some(addr) = addrs.next() {
150 return Ok(addr);
151 }
152 }
153 let mut addrs = (host, default_port)
154 .to_socket_addrs()
155 .map_err(|e| FrankaError::Network(format!("libfranka: Connection error: {e}")))?;
156 addrs.next().ok_or_else(|| {
157 FrankaError::Network(format!(
158 "libfranka: Connection error: host {host} did not resolve to any address"
159 ))
160 })
161}
162
163pub fn connect_handshake(network: &Network, library_version: u16) -> FrankaResult<u16> {
173 use zerocopy::IntoBytes;
174
175 let layout = network.tcp.layout();
176 let udp_port = network.udp.port();
177
178 let (status, server_version) = match layout {
182 HeaderLayout::Robot => {
183 let request = ConnectRequest::new(library_version, udp_port);
184 let command_id = network.tcp.send_request(
185 crate::wire::robot::Command::Connect.to_u32(),
186 request.as_bytes(),
187 )?;
188 let message = network.tcp.blocking_receive_response(command_id)?;
189 let response: ConnectResponse = parse_response(layout, &message)?;
190 let status = match ConnectStatus::from_u8(response.status) {
191 Some(ConnectStatus::Success) => HandshakeStatus::Success,
192 Some(ConnectStatus::IncompatibleLibraryVersion) => {
193 HandshakeStatus::IncompatibleLibraryVersion
194 }
195 None => HandshakeStatus::Other,
196 };
197 (status, response.version.get())
198 }
199 HeaderLayout::Gripper => {
200 let request = GripperConnectRequest::new(library_version, udp_port);
201 let command_id = network.tcp.send_request(
202 crate::wire::gripper::GripperCommand::Connect.to_u16() as u32,
203 request.as_bytes(),
204 )?;
205 let message = network.tcp.blocking_receive_response(command_id)?;
206 let response: GripperConnectResponse = parse_response(layout, &message)?;
207 let status = match GripperConnectStatus::from_u16(response.status.get()) {
208 Some(GripperConnectStatus::Success) => HandshakeStatus::Success,
209 Some(GripperConnectStatus::IncompatibleLibraryVersion) => {
210 HandshakeStatus::IncompatibleLibraryVersion
211 }
212 None => HandshakeStatus::Other,
213 };
214 (status, response.version.get())
215 }
216 };
217
218 match status {
219 HandshakeStatus::Success => Ok(server_version),
220 HandshakeStatus::IncompatibleLibraryVersion => Err(FrankaError::IncompatibleVersion {
221 server_version,
222 library_version,
223 }),
224 HandshakeStatus::Other => Err(FrankaError::Protocol(
225 "libfranka: Protocol error during connection attempt".to_string(),
226 )),
227 }
228}
229
230enum HandshakeStatus {
233 Success,
234 IncompatibleLibraryVersion,
235 Other,
236}
237
238pub fn connect_robot(host: &str) -> FrankaResult<(Network, u16)> {
241 let network = Network::connect(host, crate::wire::ROBOT_COMMAND_PORT, HeaderLayout::Robot)?;
242 let version = connect_handshake(&network, crate::wire::ROBOT_VERSION)?;
243 Ok((network, version))
244}
245
246pub fn connect_gripper(host: &str) -> FrankaResult<(Network, u16)> {
249 let network = Network::connect(
250 host,
251 crate::wire::gripper::GRIPPER_COMMAND_PORT,
252 HeaderLayout::Gripper,
253 )?;
254 let version = connect_handshake(&network, GRIPPER_VERSION)?;
255 Ok((network, version))
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::wire::gripper::GripperState;
262
263 #[test]
267 fn udp_failure_shuts_down_the_command_socket() {
268 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
269 let addr = listener.local_addr().unwrap();
270 let server = std::thread::spawn(move || {
271 let (stream, _) = listener.accept().unwrap();
272 std::thread::sleep(Duration::from_millis(500));
273 drop(stream);
274 });
275
276 let network = Network::connect_with_timeouts(
277 &addr.to_string(),
278 crate::wire::ROBOT_COMMAND_PORT,
279 HeaderLayout::Robot,
280 Duration::from_secs(1),
281 Duration::from_millis(50),
282 )
283 .unwrap();
284 assert!(network.tcp.send_request(0, &[]).is_ok());
285
286 let error = network
287 .blocking_receive_struct::<GripperState>()
288 .unwrap_err();
289 assert_eq!(error.to_string(), "libfranka: UDP receive: Timeout");
290 assert!(network.tcp.send_request(0, &[]).is_err());
291
292 drop(network);
293 server.join().unwrap();
294 }
295}