diff options
| author | Sean Cross <sean@xobs.io> | 2023-11-07 12:32:04 +0800 | 
|---|---|---|
| committer | Sean Cross <sean@xobs.io> | 2024-01-13 09:38:42 -0800 | 
| commit | aa8acc2215f625db898c0f9feab3a1702f38a190 (patch) | |
| tree | 9d7402f2faa9968207abdb313b6a12e45413558c | |
| parent | ef4f722835ccf11dd002606e3ad4adb342ea4435 (diff) | |
| download | rust-aa8acc2215f625db898c0f9feab3a1702f38a190.tar.gz rust-aa8acc2215f625db898c0f9feab3a1702f38a190.zip | |
xous: net: initial commit of network support
This is an initial commit of network support for Xous. On hardware, is backed by smoltcp running via a Xous server in a separate process space. This patch adds TCP and UDP client and server support as well as DNS resolution support using the dns Xous server. Signed-off-by: Sean Cross <sean@xobs.io>
| -rw-r--r-- | library/std/src/os/xous/services.rs | 6 | ||||
| -rw-r--r-- | library/std/src/os/xous/services/dns.rs | 28 | ||||
| -rw-r--r-- | library/std/src/os/xous/services/net.rs | 95 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/mod.rs | 1 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/net/dns.rs | 127 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/net/mod.rs | 84 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/net/tcplistener.rs | 247 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/net/tcpstream.rs | 435 | ||||
| -rw-r--r-- | library/std/src/sys/pal/xous/net/udp.rs | 471 | 
9 files changed, 1493 insertions, 1 deletions
| diff --git a/library/std/src/os/xous/services.rs b/library/std/src/os/xous/services.rs index 5c219f1fbb9..a75be1b8570 100644 --- a/library/std/src/os/xous/services.rs +++ b/library/std/src/os/xous/services.rs @@ -1,9 +1,15 @@ use crate::os::xous::ffi::Connection; use core::sync::atomic::{AtomicU32, Ordering}; +mod dns; +pub(crate) use dns::*; + mod log; pub(crate) use log::*; +mod net; +pub(crate) use net::*; + mod systime; pub(crate) use systime::*; diff --git a/library/std/src/os/xous/services/dns.rs b/library/std/src/os/xous/services/dns.rs new file mode 100644 index 00000000000..a7d88f4892c --- /dev/null +++ b/library/std/src/os/xous/services/dns.rs @@ -0,0 +1,28 @@ +use crate::os::xous::ffi::Connection; +use crate::os::xous::services::connect; +use core::sync::atomic::{AtomicU32, Ordering}; + +#[repr(usize)] +pub(crate) enum DnsLendMut { + RawLookup = 6, +} + +impl Into<usize> for DnsLendMut { + fn into(self) -> usize { + self as usize + } +} + +/// Return a `Connection` to the DNS lookup server. This server is used for +/// querying domain name values. +pub(crate) fn dns_server() -> Connection { + static DNS_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = DNS_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = connect("_DNS Resolver Middleware_").unwrap(); + DNS_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/os/xous/services/net.rs b/library/std/src/os/xous/services/net.rs new file mode 100644 index 00000000000..26d337dcef1 --- /dev/null +++ b/library/std/src/os/xous/services/net.rs @@ -0,0 +1,95 @@ +use crate::os::xous::ffi::Connection; +use crate::os::xous::services::connect; +use core::sync::atomic::{AtomicU32, Ordering}; + +pub(crate) enum NetBlockingScalar { + StdGetTtlUdp(u16 /* fd */), /* 36 */ + StdSetTtlUdp(u16 /* fd */, u32 /* ttl */), /* 37 */ + StdGetTtlTcp(u16 /* fd */), /* 36 */ + StdSetTtlTcp(u16 /* fd */, u32 /* ttl */), /* 37 */ + StdGetNodelay(u16 /* fd */), /* 38 */ + StdSetNodelay(u16 /* fd */, bool), /* 39 */ + StdTcpClose(u16 /* fd */), /* 34 */ + StdUdpClose(u16 /* fd */), /* 41 */ + StdTcpStreamShutdown(u16 /* fd */, crate::net::Shutdown /* how */), /* 46 */ +} + +pub(crate) enum NetLendMut { + StdTcpConnect, /* 30 */ + StdTcpTx(u16 /* fd */), /* 31 */ + StdTcpPeek(u16 /* fd */, bool /* nonblocking */), /* 32 */ + StdTcpRx(u16 /* fd */, bool /* nonblocking */), /* 33 */ + StdGetAddress(u16 /* fd */), /* 35 */ + StdUdpBind, /* 40 */ + StdUdpRx(u16 /* fd */), /* 42 */ + StdUdpTx(u16 /* fd */), /* 43 */ + StdTcpListen, /* 44 */ + StdTcpAccept(u16 /* fd */), /* 45 */ +} + +impl Into<usize> for NetLendMut { + fn into(self) -> usize { + match self { + NetLendMut::StdTcpConnect => 30, + NetLendMut::StdTcpTx(fd) => 31 | ((fd as usize) << 16), + NetLendMut::StdTcpPeek(fd, blocking) => { + 32 | ((fd as usize) << 16) | if blocking { 0x8000 } else { 0 } + } + NetLendMut::StdTcpRx(fd, blocking) => { + 33 | ((fd as usize) << 16) | if blocking { 0x8000 } else { 0 } + } + NetLendMut::StdGetAddress(fd) => 35 | ((fd as usize) << 16), + NetLendMut::StdUdpBind => 40, + NetLendMut::StdUdpRx(fd) => 42 | ((fd as usize) << 16), + NetLendMut::StdUdpTx(fd) => 43 | ((fd as usize) << 16), + NetLendMut::StdTcpListen => 44, + NetLendMut::StdTcpAccept(fd) => 45 | ((fd as usize) << 16), + } + } +} + +impl<'a> Into<[usize; 5]> for NetBlockingScalar { + fn into(self) -> [usize; 5] { + match self { + NetBlockingScalar::StdGetTtlTcp(fd) => [36 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdGetTtlUdp(fd) => [36 | ((fd as usize) << 16), 0, 0, 0, 1], + NetBlockingScalar::StdSetTtlTcp(fd, ttl) => { + [37 | ((fd as usize) << 16), ttl as _, 0, 0, 0] + } + NetBlockingScalar::StdSetTtlUdp(fd, ttl) => { + [37 | ((fd as usize) << 16), ttl as _, 0, 0, 1] + } + NetBlockingScalar::StdGetNodelay(fd) => [38 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdSetNodelay(fd, enabled) => { + [39 | ((fd as usize) << 16), if enabled { 1 } else { 0 }, 0, 0, 1] + } + NetBlockingScalar::StdTcpClose(fd) => [34 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdUdpClose(fd) => [41 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdTcpStreamShutdown(fd, how) => [ + 46 | ((fd as usize) << 16), + match how { + crate::net::Shutdown::Read => 1, + crate::net::Shutdown::Write => 2, + crate::net::Shutdown::Both => 3, + }, + 0, + 0, + 0, + ], + } + } +} + +/// Return a `Connection` to the Network server. This server provides all +/// OS-level networking functions. +pub(crate) fn net_server() -> Connection { + static NET_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = NET_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = connect("_Middleware Network Server_").unwrap(); + NET_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index aa96925d859..7c49fb88a4e 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -12,7 +12,6 @@ pub mod fs; #[path = "../unsupported/io.rs"] pub mod io; pub mod locks; -#[path = "../unsupported/net.rs"] pub mod net; pub mod os; #[path = "../unix/os_str.rs"] diff --git a/library/std/src/sys/pal/xous/net/dns.rs b/library/std/src/sys/pal/xous/net/dns.rs new file mode 100644 index 00000000000..63056324bfb --- /dev/null +++ b/library/std/src/sys/pal/xous/net/dns.rs @@ -0,0 +1,127 @@ +use crate::io; +use crate::net::{Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::os::xous::ffi::lend_mut; +use crate::os::xous::services::{dns_server, DnsLendMut}; +use core::convert::{TryFrom, TryInto}; + +pub struct DnsError { + pub code: u8, +} + +#[repr(C, align(4096))] +struct LookupHostQuery([u8; 4096]); + +pub struct LookupHost { + data: LookupHostQuery, + port: u16, + offset: usize, + count: usize, +} + +impl LookupHost { + pub fn port(&self) -> u16 { + self.port + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option<SocketAddr> { + if self.offset >= self.data.0.len() { + return None; + } + match self.data.0.get(self.offset) { + Some(&4) => { + self.offset += 1; + if self.offset + 4 > self.data.0.len() { + return None; + } + let result = Some(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new( + self.data.0[self.offset], + self.data.0[self.offset + 1], + self.data.0[self.offset + 2], + self.data.0[self.offset + 3], + ), + self.port, + ))); + self.offset += 4; + result + } + Some(&6) => { + self.offset += 1; + if self.offset + 16 > self.data.0.len() { + return None; + } + let mut new_addr = [0u8; 16]; + for (src, octet) in self.data.0[(self.offset + 1)..(self.offset + 16 + 1)] + .iter() + .zip(new_addr.iter_mut()) + { + *octet = *src; + } + let result = + Some(SocketAddr::V6(SocketAddrV6::new(new_addr.into(), self.port, 0, 0))); + self.offset += 16; + result + } + _ => None, + } + } +} + +pub fn lookup(query: &str, port: u16) -> Result<LookupHost, DnsError> { + let mut result = LookupHost { data: LookupHostQuery([0u8; 4096]), offset: 0, count: 0, port }; + + // Copy the query into the message that gets sent to the DNS server + for (query_byte, result_byte) in query.as_bytes().iter().zip(result.data.0.iter_mut()) { + *result_byte = *query_byte; + } + + lend_mut( + dns_server(), + DnsLendMut::RawLookup.into(), + &mut result.data.0, + 0, + query.as_bytes().len(), + ) + .unwrap(); + if result.data.0[0] != 0 { + return Err(DnsError { code: result.data.0[1] }); + } + assert_eq!(result.offset, 0); + result.count = result.data.0[1] as usize; + + // Advance the offset to the first record + result.offset = 2; + Ok(result) +} + +impl TryFrom<&str> for LookupHost { + type Error = io::Error; + + fn try_from(s: &str) -> io::Result<LookupHost> { + macro_rules! try_opt { + ($e:expr, $msg:expr) => { + match $e { + Some(r) => r, + None => return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &$msg)), + } + }; + } + + // split the string by ':' and convert the second part to u16 + let (host, port_str) = try_opt!(s.rsplit_once(':'), "invalid socket address"); + let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value"); + (host, port).try_into() + } +} + +impl TryFrom<(&str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(v: (&str, u16)) -> io::Result<LookupHost> { + lookup(v.0, v.1) + .map_err(|_e| io::const_io_error!(io::ErrorKind::InvalidInput, &"DNS failure")) + } +} diff --git a/library/std/src/sys/pal/xous/net/mod.rs b/library/std/src/sys/pal/xous/net/mod.rs new file mode 100644 index 00000000000..b5a3da136a6 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/mod.rs @@ -0,0 +1,84 @@ +mod dns; + +mod tcpstream; +pub use tcpstream::*; + +mod tcplistener; +pub use tcplistener::*; + +mod udp; +pub use udp::*; + +// this structure needs to be synchronized with what's in net/src/api.rs +#[repr(C)] +#[derive(Debug)] +enum NetError { + // Ok = 0, + Unaddressable = 1, + SocketInUse = 2, + // AccessDenied = 3, + Invalid = 4, + // Finished = 5, + LibraryError = 6, + // AlreadyUsed = 7, + TimedOut = 8, + WouldBlock = 9, +} + +#[repr(C, align(4096))] +struct ConnectRequest { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +struct SendData { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +pub struct ReceiveData { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +pub struct GetAddress { + raw: [u8; 4096], +} + +pub use dns::LookupHost; + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_INET: u8 = 0; + pub const AF_INET6: u8 = 1; + pub type sa_family_t = u8; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} +} diff --git a/library/std/src/sys/pal/xous/net/tcplistener.rs b/library/std/src/sys/pal/xous/net/tcplistener.rs new file mode 100644 index 00000000000..47305013083 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/tcplistener.rs @@ -0,0 +1,247 @@ +use super::*; +use crate::fmt; +use crate::io; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::os::xous::services; +use crate::sync::Arc; +use core::convert::TryInto; +use core::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +#[derive(Clone)] +pub struct TcpListener { + fd: Arc<AtomicU16>, + local: SocketAddr, + handle_count: Arc<AtomicUsize>, + nonblocking: Arc<AtomicBool>, +} + +impl TcpListener { + pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result<TcpListener> { + let mut addr = *socketaddr?; + + let fd = TcpListener::bind_inner(&mut addr)?; + return Ok(TcpListener { + fd: Arc::new(AtomicU16::new(fd)), + local: addr, + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + }); + } + + /// This returns the raw fd of a Listener, so that it can also be used by the + /// accept routine to replenish the Listener object after its handle has been converted into + /// a TcpStream object. + fn bind_inner(addr: &mut SocketAddr) -> io::Result<u16> { + // Construct the request + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Serialize the StdUdpBind structure. This is done "manually" because we don't want to + // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. + let port_bytes = addr.port().to_le_bytes(); + connect_request.raw[0] = port_bytes[0]; + connect_request.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + connect_request.raw[2] = 4; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + connect_request.raw[2] = 6; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + + let Ok((_, valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpListen.into(), + &mut connect_request.raw, + 0, + 4096, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")); + }; + + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use")); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::AddrNotAvailable, + &"Invalid address" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect or internal error" + )); + } + } + let fd = response[1] as usize; + if addr.port() == 0 { + // oddly enough, this is a valid port and it means "give me something valid, up to you what that is" + let assigned_port = u16::from_le_bytes(response[2..4].try_into().unwrap()); + addr.set_port(assigned_port); + } + // println!("TcpListening with file handle of {}\r\n", fd); + Ok(fd.try_into().unwrap()) + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + Ok(self.local) + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + + if self.nonblocking.load(Ordering::Relaxed) { + // nonblocking + receive_request.raw[0] = 0; + } else { + // blocking + receive_request.raw[0] = 1; + } + + if let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpAccept(self.fd.load(Ordering::Relaxed)).into(), + &mut receive_request.raw, + 0, + 0, + ) { + if receive_request.raw[0] != 0 { + // error case + if receive_request.raw[1] == NetError::TimedOut as u8 { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"accept timed out",)); + } else if receive_request.raw[1] == NetError::WouldBlock as u8 { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"accept would block", + )); + } else if receive_request.raw[1] == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + } + } else { + // accept successful + let rr = &receive_request.raw; + let stream_fd = u16::from_le_bytes(rr[1..3].try_into().unwrap()); + let port = u16::from_le_bytes(rr[20..22].try_into().unwrap()); + let addr = if rr[3] == 4 { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(rr[4], rr[5], rr[6], rr[7])), port) + } else if rr[3] == 6 { + SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + u16::from_be_bytes(rr[4..6].try_into().unwrap()), + u16::from_be_bytes(rr[6..8].try_into().unwrap()), + u16::from_be_bytes(rr[8..10].try_into().unwrap()), + u16::from_be_bytes(rr[10..12].try_into().unwrap()), + u16::from_be_bytes(rr[12..14].try_into().unwrap()), + u16::from_be_bytes(rr[14..16].try_into().unwrap()), + u16::from_be_bytes(rr[16..18].try_into().unwrap()), + u16::from_be_bytes(rr[18..20].try_into().unwrap()), + )), + port, + ) + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + }; + + // replenish the listener + let mut local_copy = self.local.clone(); // port is non-0 by this time, but the method signature needs a mut + let new_fd = TcpListener::bind_inner(&mut local_copy)?; + self.fd.store(new_fd, Ordering::Relaxed); + + // now return a stream converted from the old stream's fd + Ok((TcpStream::from_listener(stream_fd, self.local.port(), port, addr), addr)) + } + } else { + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unable to accept")) + } + } + + pub fn duplicate(&self) -> io::Result<TcpListener> { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlTcp(self.fd.load(Ordering::Relaxed), ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result<u32> { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlTcp(self.fd.load(Ordering::Relaxed)).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn set_only_v6(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn only_v6(&self) -> io::Result<bool> { + unimpl!(); + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.store(nonblocking, Ordering::Relaxed); + Ok(()) + } +} + +impl fmt::Debug for TcpListener { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TCP listening on {:?}", self.local) + } +} + +impl Drop for TcpListener { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + crate::os::xous::services::NetBlockingScalar::StdTcpClose( + self.fd.load(Ordering::Relaxed), + ) + .into(), + ) + .unwrap(); + } + } +} diff --git a/library/std/src/sys/pal/xous/net/tcpstream.rs b/library/std/src/sys/pal/xous/net/tcpstream.rs new file mode 100644 index 00000000000..7149678118a --- /dev/null +++ b/library/std/src/sys/pal/xous/net/tcpstream.rs @@ -0,0 +1,435 @@ +use super::*; +use crate::fmt; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::os::xous::services; +use crate::sync::Arc; +use crate::time::Duration; +use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +enum ReadOrPeek { + Read, + Peek, +} + +#[derive(Clone)] +pub struct TcpStream { + fd: u16, + local_port: u16, + remote_port: u16, + peer_addr: SocketAddr, + // milliseconds + read_timeout: Arc<AtomicU32>, + // milliseconds + write_timeout: Arc<AtomicU32>, + handle_count: Arc<AtomicUsize>, + nonblocking: Arc<AtomicBool>, +} + +fn sockaddr_to_buf(duration: Duration, addr: &SocketAddr, buf: &mut [u8]) { + // Construct the request. + let port_bytes = addr.port().to_le_bytes(); + buf[0] = port_bytes[0]; + buf[1] = port_bytes[1]; + for (dest, src) in buf[2..].iter_mut().zip((duration.as_millis() as u64).to_le_bytes()) { + *dest = src; + } + match addr.ip() { + IpAddr::V4(addr) => { + buf[10] = 4; + for (dest, src) in buf[11..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + buf[10] = 6; + for (dest, src) in buf[11..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } +} + +impl TcpStream { + pub(crate) fn from_listener( + fd: u16, + local_port: u16, + remote_port: u16, + peer_addr: SocketAddr, + ) -> TcpStream { + TcpStream { + fd, + local_port, + remote_port, + peer_addr, + read_timeout: Arc::new(AtomicU32::new(0)), + write_timeout: Arc::new(AtomicU32::new(0)), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + } + } + + pub fn connect(socketaddr: io::Result<&SocketAddr>) -> io::Result<TcpStream> { + Self::connect_timeout(socketaddr?, Duration::ZERO) + } + + pub fn connect_timeout(addr: &SocketAddr, duration: Duration) -> io::Result<TcpStream> { + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Construct the request. + sockaddr_to_buf(duration, &addr, &mut connect_request.raw); + + let Ok((_, valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpConnect.into(), + &mut connect_request.raw, + 0, + 4096, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")); + }; + + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + // errcode is a u8 but stuck in a u16 where the upper byte is invalid. Mask & decode accordingly. + let errcode = response[0]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use",)); + } else if errcode == NetError::Unaddressable as u8 { + return Err(io::const_io_error!( + io::ErrorKind::AddrNotAvailable, + &"Invalid address", + )); + } else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Unable to connect or internal error", + )); + } + } + let fd = u16::from_le_bytes([response[2], response[3]]); + let local_port = u16::from_le_bytes([response[4], response[5]]); + let remote_port = u16::from_le_bytes([response[6], response[7]]); + // println!( + // "Connected with local port of {}, remote port of {}, file handle of {}", + // local_port, remote_port, fd + // ); + Ok(TcpStream { + fd, + local_port, + remote_port, + peer_addr: *addr, + read_timeout: Arc::new(AtomicU32::new(0)), + write_timeout: Arc::new(AtomicU32::new(0)), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + }) + } + + pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> { + if let Some(to) = timeout { + if to.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero is an invalid timeout", + )); + } + } + self.read_timeout.store( + timeout.map(|t| t.as_millis().min(u32::MAX as u128) as u32).unwrap_or_default(), + Ordering::Relaxed, + ); + Ok(()) + } + + pub fn set_write_timeout(&self, timeout: Option<Duration>) -> io::Result<()> { + if let Some(to) = timeout { + if to.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero is an invalid timeout", + )); + } + } + self.write_timeout.store( + timeout.map(|t| t.as_millis().min(u32::MAX as u128) as u32).unwrap_or_default(), + Ordering::Relaxed, + ); + Ok(()) + } + + pub fn read_timeout(&self) -> io::Result<Option<Duration>> { + match self.read_timeout.load(Ordering::Relaxed) { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn write_timeout(&self) -> io::Result<Option<Duration>> { + match self.write_timeout.load(Ordering::Relaxed) { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + fn read_or_peek(&self, buf: &mut [u8], op: ReadOrPeek) -> io::Result<usize> { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + let data_to_read = buf.len().min(receive_request.raw.len()); + + let opcode = match op { + ReadOrPeek::Read => { + services::NetLendMut::StdTcpRx(self.fd, self.nonblocking.load(Ordering::Relaxed)) + } + ReadOrPeek::Peek => { + services::NetLendMut::StdTcpPeek(self.fd, self.nonblocking.load(Ordering::Relaxed)) + } + }; + + let Ok((offset, length)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + opcode.into(), + &mut receive_request.raw, + // Reuse the `offset` as the read timeout + self.read_timeout.load(Ordering::Relaxed) as usize, + data_to_read, + ) else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Library failure: wrong message type or messaging error" + )); + }; + + if offset != 0 { + for (dest, src) in buf.iter_mut().zip(receive_request.raw[..length].iter()) { + *dest = *src; + } + Ok(length) + } else { + let result = receive_request.raw; + if result[0] != 0 { + if result[1] == 8 { + // timed out + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"Timeout",)); + } + if result[1] == 9 { + // would block + return Err(io::const_io_error!(io::ErrorKind::WouldBlock, &"Would block",)); + } + } + Err(io::const_io_error!(io::ErrorKind::Other, &"recv_slice failure")) + } + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> { + self.read_or_peek(buf, ReadOrPeek::Peek) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + self.read_or_peek(buf, ReadOrPeek::Read) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + let mut send_request = SendData { raw: [0u8; 4096] }; + for (dest, src) in send_request.raw.iter_mut().zip(buf) { + *dest = *src; + } + let buf_len = send_request.raw.len().min(buf.len()); + + let (_offset, _valid) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpTx(self.fd).into(), + &mut send_request.raw, + // Reuse the offset as the timeout + self.write_timeout.load(Ordering::Relaxed) as usize, + buf_len, + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")))?; + + if send_request.raw[0] != 0 { + if send_request.raw[4] == 8 { + // timed out + return Err(io::const_io_error!( + io::ErrorKind::BrokenPipe, + &"Timeout or connection closed", + )); + } else if send_request.raw[4] == 9 { + // would block + return Err(io::const_io_error!(io::ErrorKind::WouldBlock, &"Would block",)); + } else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Error when sending", + )); + } + } + Ok(u32::from_le_bytes([ + send_request.raw[4], + send_request.raw[5], + send_request.raw[6], + send_request.raw[7], + ]) as usize) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn peer_addr(&self) -> io::Result<SocketAddr> { + Ok(self.peer_addr) + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + let mut get_addr = GetAddress { raw: [0u8; 4096] }; + + let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdGetAddress(self.fd).into(), + &mut get_addr.raw, + 0, + 0, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")); + }; + let mut i = get_addr.raw.iter(); + match *i.next().unwrap() { + 4 => Ok(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new( + *i.next().unwrap(), + *i.next().unwrap(), + *i.next().unwrap(), + *i.next().unwrap(), + ), + self.local_port, + ))), + 6 => { + let mut new_addr = [0u8; 16]; + for (src, octet) in i.zip(new_addr.iter_mut()) { + *octet = *src; + } + Ok(SocketAddr::V6(SocketAddrV6::new(new_addr.into(), self.local_port, 0, 0))) + } + _ => Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")), + } + } + + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdTcpStreamShutdown(self.fd, how).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn duplicate(&self) -> io::Result<TcpStream> { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_linger(&self, _: Option<Duration>) -> io::Result<()> { + unimpl!(); + } + + pub fn linger(&self) -> io::Result<Option<Duration>> { + unimpl!(); + } + + pub fn set_nodelay(&self, enabled: bool) -> io::Result<()> { + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetNodelay(self.fd, enabled).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn nodelay(&self) -> io::Result<bool> { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetNodelay(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] != 0)?) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlTcp(self.fd, ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result<u32> { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlTcp(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.store(nonblocking, Ordering::SeqCst); + Ok(()) + } +} + +impl fmt::Debug for TcpStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "TCP connection to {:?} port {} to local port {}", + self.peer_addr, self.remote_port, self.local_port + ) + } +} + +impl Drop for TcpStream { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdTcpClose(self.fd).into(), + ) + .unwrap(); + } + } +} diff --git a/library/std/src/sys/pal/xous/net/udp.rs b/library/std/src/sys/pal/xous/net/udp.rs new file mode 100644 index 00000000000..cafa5b3bde8 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/udp.rs @@ -0,0 +1,471 @@ +use super::*; +use crate::cell::Cell; +use crate::fmt; +use crate::io; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::os::xous::services; +use crate::sync::Arc; +use crate::time::Duration; +use core::convert::TryInto; +use core::sync::atomic::{AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +#[derive(Clone)] +pub struct UdpSocket { + fd: u16, + local: SocketAddr, + remote: Cell<Option<SocketAddr>>, + // in milliseconds. The setting applies only to `recv` calls after the timeout is set. + read_timeout: Cell<u64>, + // in milliseconds. The setting applies only to `send` calls after the timeout is set. + write_timeout: Cell<u64>, + handle_count: Arc<AtomicUsize>, + nonblocking: Cell<bool>, +} + +impl UdpSocket { + pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + let addr = socketaddr?; + // Construct the request + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Serialize the StdUdpBind structure. This is done "manually" because we don't want to + // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. + let port_bytes = addr.port().to_le_bytes(); + connect_request.raw[0] = port_bytes[0]; + connect_request.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + connect_request.raw[2] = 4; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + connect_request.raw[2] = 6; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + + let response = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdUdpBind.into(), + &mut connect_request.raw, + 0, + 4096, + ); + + if let Ok((_, valid)) = response { + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use")); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Port can't be 0 or invalid address" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect or internal error" + )); + } + } + let fd = response[1] as u16; + return Ok(UdpSocket { + fd, + local: *addr, + remote: Cell::new(None), + read_timeout: Cell::new(0), + write_timeout: Cell::new(0), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Cell::new(false), + }); + } + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")) + } + + pub fn peer_addr(&self) -> io::Result<SocketAddr> { + match self.remote.get() { + Some(dest) => Ok(dest), + None => Err(io::const_io_error!(io::ErrorKind::NotConnected, &"No peer specified")), + } + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + Ok(self.local) + } + + fn recv_inner(&self, buf: &mut [u8], do_peek: bool) -> io::Result<(usize, SocketAddr)> { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + + if self.nonblocking.get() { + // nonblocking + receive_request.raw[0] = 0; + } else { + // blocking + receive_request.raw[0] = 1; + for (&s, d) in self + .read_timeout + .get() + .to_le_bytes() + .iter() + .zip(receive_request.raw[1..9].iter_mut()) + { + *d = s; + } + } + if let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdUdpRx(self.fd).into(), + &mut receive_request.raw, + if do_peek { 1 } else { 0 }, + 0, + ) { + if receive_request.raw[0] != 0 { + // error case + if receive_request.raw[1] == NetError::TimedOut as u8 { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"recv timed out",)); + } else if receive_request.raw[1] == NetError::WouldBlock as u8 { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"recv would block", + )); + } else if receive_request.raw[1] == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + } + } else { + let rr = &receive_request.raw; + let rxlen = u16::from_le_bytes(rr[1..3].try_into().unwrap()); + let port = u16::from_le_bytes(rr[20..22].try_into().unwrap()); + let addr = if rr[3] == 4 { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(rr[4], rr[5], rr[6], rr[7])), port) + } else if rr[3] == 6 { + SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + u16::from_be_bytes(rr[4..6].try_into().unwrap()), + u16::from_be_bytes(rr[6..8].try_into().unwrap()), + u16::from_be_bytes(rr[8..10].try_into().unwrap()), + u16::from_be_bytes(rr[10..12].try_into().unwrap()), + u16::from_be_bytes(rr[12..14].try_into().unwrap()), + u16::from_be_bytes(rr[14..16].try_into().unwrap()), + u16::from_be_bytes(rr[16..18].try_into().unwrap()), + u16::from_be_bytes(rr[18..20].try_into().unwrap()), + )), + port, + ) + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + }; + for (&s, d) in rr[22..22 + rxlen as usize].iter().zip(buf.iter_mut()) { + *d = s; + } + Ok((rxlen as usize, addr)) + } + } else { + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unable to recv")) + } + } + + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_inner(buf, false) + } + + pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> { + self.recv_from(buf).map(|(len, _addr)| len) + } + + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_inner(buf, true) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> { + self.peek_from(buf).map(|(len, _addr)| len) + } + + pub fn connect(&self, maybe_addr: io::Result<&SocketAddr>) -> io::Result<()> { + let addr = maybe_addr?; + self.remote.set(Some(*addr)); + Ok(()) + } + + pub fn send(&self, buf: &[u8]) -> io::Result<usize> { + if let Some(addr) = self.remote.get() { + self.send_to(buf, &addr) + } else { + Err(io::const_io_error!(io::ErrorKind::NotConnected, &"No remote specified")) + } + } + + pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result<usize> { + let mut tx_req = SendData { raw: [0u8; 4096] }; + + // Construct the request. + let port_bytes = addr.port().to_le_bytes(); + tx_req.raw[0] = port_bytes[0]; + tx_req.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + tx_req.raw[2] = 4; + for (dest, src) in tx_req.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + tx_req.raw[2] = 6; + for (dest, src) in tx_req.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + let len = buf.len() as u16; + let len_bytes = len.to_le_bytes(); + tx_req.raw[19] = len_bytes[0]; + tx_req.raw[20] = len_bytes[1]; + for (&s, d) in buf.iter().zip(tx_req.raw[21..].iter_mut()) { + *d = s; + } + + // let buf = unsafe { + // xous::MemoryRange::new( + // &mut tx_req as *mut SendData as usize, + // core::mem::size_of::<SendData>(), + // ) + // .unwrap() + // }; + + // write time-outs are implemented on the caller side. Basically, if the Net crate server + // is too busy to take the call immediately: retry, until the timeout is reached. + let now = crate::time::Instant::now(); + let write_timeout = if self.nonblocking.get() { + // nonblocking + core::time::Duration::ZERO + } else { + // blocking + if self.write_timeout.get() == 0 { + // forever + core::time::Duration::from_millis(u64::MAX) + } else { + // or this amount of time + core::time::Duration::from_millis(self.write_timeout.get()) + } + }; + loop { + let response = crate::os::xous::ffi::try_lend_mut( + services::net_server(), + services::NetLendMut::StdUdpTx(self.fd).into(), + &mut tx_req.raw, + 0, + 4096, + ); + match response { + Ok((_, valid)) => { + let response = &tx_req.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!( + io::ErrorKind::ResourceBusy, + &"Socket in use" + )); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Socket not valid" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Library error" + )); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect" + )); + } + } else { + // no error + return Ok(len as usize); + } + } + Err(crate::os::xous::ffi::Error::ServerQueueFull) => { + if now.elapsed() >= write_timeout { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"Write timed out" + )); + } else { + // question: do we want to do something a bit more gentle than immediately retrying? + crate::thread::yield_now(); + } + } + _ => return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")), + } + } + } + + pub fn duplicate(&self) -> io::Result<UdpSocket> { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> { + if let Some(d) = timeout { + if d.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero duration is invalid" + )); + } + } + self.read_timeout + .set(timeout.map(|t| t.as_millis().min(u64::MAX as u128) as u64).unwrap_or_default()); + Ok(()) + } + + pub fn set_write_timeout(&self, timeout: Option<Duration>) -> io::Result<()> { + if let Some(d) = timeout { + if d.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero duration is invalid" + )); + } + } + self.write_timeout + .set(timeout.map(|t| t.as_millis().min(u64::MAX as u128) as u64).unwrap_or_default()); + Ok(()) + } + + pub fn read_timeout(&self) -> io::Result<Option<Duration>> { + match self.read_timeout.get() { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn write_timeout(&self) -> io::Result<Option<Duration>> { + match self.write_timeout.get() { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlUdp(self.fd, ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result<u32> { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlUdp(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.set(nonblocking); + Ok(()) + } + + // ------------- smoltcp base stack does not have multicast or broadcast support --------------- + pub fn set_broadcast(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn broadcast(&self) -> io::Result<bool> { + unimpl!(); + } + + pub fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_loop_v4(&self) -> io::Result<bool> { + unimpl!(); + } + + pub fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_ttl_v4(&self) -> io::Result<u32> { + unimpl!(); + } + + pub fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_loop_v6(&self) -> io::Result<bool> { + unimpl!(); + } + + pub fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unimpl!(); + } + + pub fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unimpl!(); + } + + pub fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unimpl!(); + } + + pub fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unimpl!(); + } +} + +impl fmt::Debug for UdpSocket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "UDP listening on {:?} to {:?}", self.local, self.remote.get(),) + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdUdpClose(self.fd).into(), + ) + .unwrap(); + } + } +} | 
