use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::{mpsc, Mutex};
const RECV_BUF_SIZE: usize = 1500;
pub trait AsyncDatagram {
fn send_to(
&self,
buf: &[u8],
peer: SocketAddr,
) -> impl std::future::Future<Output = io::Result<()>> + Send;
fn recv_from(
&self,
) -> impl std::future::Future<Output = io::Result<(Vec<u8>, SocketAddr)>> + Send;
}
impl AsyncDatagram for matter_transport::TokioUdpTransport {
async fn send_to(&self, buf: &[u8], peer: SocketAddr) -> io::Result<()> {
let peer = match (self.socket().local_addr(), peer) {
(Ok(SocketAddr::V6(_)), SocketAddr::V4(v4)) => {
SocketAddr::new(std::net::IpAddr::V6(v4.ip().to_ipv6_mapped()), v4.port())
}
_ => peer,
};
self.socket().send_to(buf, peer).await.map(|_n| ())
}
async fn recv_from(&self) -> io::Result<(Vec<u8>, SocketAddr)> {
let mut buf = vec![0u8; RECV_BUF_SIZE];
let (n, from) = self.socket().recv_from(&mut buf).await?;
buf.truncate(n);
Ok((buf, from))
}
}
pub struct InMemoryDatagram {
addr: SocketAddr,
tx: mpsc::UnboundedSender<(Vec<u8>, SocketAddr)>,
rx: Mutex<mpsc::UnboundedReceiver<(Vec<u8>, SocketAddr)>>,
drops_remaining: AtomicUsize,
}
impl InMemoryDatagram {
#[must_use]
pub fn pair() -> (Self, Self) {
let addr_a = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1));
let addr_b = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2));
let (tx_to_a, rx_a) = mpsc::unbounded_channel();
let (tx_to_b, rx_b) = mpsc::unbounded_channel();
let a = Self {
addr: addr_a,
tx: tx_to_b,
rx: Mutex::new(rx_a),
drops_remaining: AtomicUsize::new(0),
};
let b = Self {
addr: addr_b,
tx: tx_to_a,
rx: Mutex::new(rx_b),
drops_remaining: AtomicUsize::new(0),
};
(a, b)
}
#[must_use]
pub fn local_addr(&self) -> SocketAddr {
self.addr
}
pub fn set_drops(&self, n: usize) {
self.drops_remaining.store(n, Ordering::SeqCst);
}
}
impl AsyncDatagram for InMemoryDatagram {
async fn send_to(&self, buf: &[u8], _peer: SocketAddr) -> io::Result<()> {
let consumed_drop = self
.drops_remaining
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
if v > 0 {
Some(v - 1)
} else {
None
}
})
.is_ok();
if consumed_drop {
return Ok(());
}
let _ = self.tx.send((buf.to_vec(), self.addr));
Ok(())
}
async fn recv_from(&self) -> io::Result<(Vec<u8>, SocketAddr)> {
let mut rx = self.rx.lock().await;
rx.recv()
.await
.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "peer endpoint closed"))
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
use super::*;
#[tokio::test]
async fn in_memory_datagram_delivers_both_directions() {
let (a, b) = InMemoryDatagram::pair();
a.send_to(b"hello", b.local_addr()).await.unwrap();
let (got, from) = b.recv_from().await.unwrap();
assert_eq!(got, b"hello");
assert_eq!(from, a.local_addr());
b.send_to(b"world", a.local_addr()).await.unwrap();
let (got2, from2) = a.recv_from().await.unwrap();
assert_eq!(got2, b"world");
assert_eq!(from2, b.local_addr());
}
#[tokio::test]
async fn in_memory_datagram_drops_configured_sends() {
let (a, b) = InMemoryDatagram::pair();
a.set_drops(1);
a.send_to(b"dropped", b.local_addr()).await.unwrap(); a.send_to(b"delivered", b.local_addr()).await.unwrap(); let (got, _) = b.recv_from().await.unwrap();
assert_eq!(got, b"delivered");
a.send_to(b"after", b.local_addr()).await.unwrap();
let (after, _) = b.recv_from().await.unwrap();
assert_eq!(after, b"after");
}
#[tokio::test]
async fn tokio_udp_transport_send_recv_loopback() {
use matter_transport::TokioUdpTransport;
use matter_transport::Transport;
let a = TokioUdpTransport::bind_addr("127.0.0.1:0".parse().unwrap())
.await
.unwrap();
let b = TokioUdpTransport::bind_addr("127.0.0.1:0".parse().unwrap())
.await
.unwrap();
let b_addr = b.local_address();
AsyncDatagram::send_to(&a, b"ping", b_addr).await.unwrap();
let (got, _from) = AsyncDatagram::recv_from(&b).await.unwrap();
assert_eq!(got, b"ping");
}
}