use std::io::{self, Read, Write};
pub use crate::engine::codec::ikonvert::{Config, Ikonvert, pgn_list_status};
use super::DeviceHandle;
use crate::io::net::{open_tcp, open_udp};
use crate::io::open_serial_rw;
pub const NAVLINK2_PORT: u16 = 2000;
pub(crate) type Link = (Box<dyn Read + Send>, Box<dyn Write + Send>, bool);
pub(crate) fn open(device: &str, baud: u32) -> io::Result<Link> {
if device.starts_with("tcp://") {
let (r, w) = open_tcp(device, NAVLINK2_PORT)?;
return Ok((r, w, false));
}
if let Some(rest) = device.strip_prefix("udp://") {
let rest = if rest.is_empty() {
NAVLINK2_PORT.to_string()
} else {
rest.to_string()
};
let ((r, w), sends) = open_udp(&rest)?;
if sends {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"ikonvert: udp://{rest} names the gateway, to send to it, but a \
NavLink2 only sends over UDP: give a port (udp://{NAVLINK2_PORT}) \
to receive, or tcp://host to send"
),
));
}
log::warn!(
"ikonvert over udp:// only receives: canboat expects the gateway to be on \
the bus already, put there by another client, and never takes it off; \
tcp:// is the better mode"
);
return Ok((r, w, true));
}
let (r, w) = open_serial_rw(device, baud)
.map_err(|e| io::Error::new(e.kind(), format!("opening serial port {device}: {e}")))?;
Ok((r, w, false))
}
pub fn run(
reader: Box<dyn Read + Send>,
writer: Box<dyn Write + Send>,
config: Config,
) -> DeviceHandle {
super::run_codec(Ikonvert::new(config), None, reader, writer)
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::UdpSocket;
#[test]
fn udp_receives_on_a_local_address() {
let (port, (mut reader, _writer, receive_only)) = (0..10)
.find_map(|_| {
let port = UdpSocket::bind("127.0.0.1:0")
.ok()?
.local_addr()
.ok()?
.port();
Some((port, open(&format!("udp://127.0.0.1:{port}"), 0).ok()?))
})
.expect("a free UDP port");
assert!(receive_only);
let line = b"!PDGY,127250,2,3,255,481.734,2C20AAAAAAE=\r\n";
UdpSocket::bind("127.0.0.1:0")
.unwrap()
.send_to(line, ("127.0.0.1", port))
.unwrap();
let mut buf = [0u8; 256];
let n = reader.read(&mut buf).unwrap();
assert_eq!(&buf[..n], line);
}
#[test]
fn udp_to_the_gateway_is_refused() {
let Err(e) = open("udp://192.0.2.1:2000", 0) else {
panic!("opened udp:// to a gateway");
};
assert_eq!(e.kind(), io::ErrorKind::InvalidInput, "{e}");
assert!(e.to_string().contains("tcp://"), "{e}");
}
}