use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::info; use crate::error::Result; use crate::transport::serial::{ SerialDataBits, SerialFlowControl, SerialParity, SerialStopBits, SerialTransport, }; use crate::transport::tcp::TcpTransport; use crate::transport::udp::UdpTransport; pub mod serial; pub mod tcp; pub mod udp; fn default_serial_data_bits() -> SerialDataBits { SerialDataBits::Eight } fn default_serial_parity() -> SerialParity { SerialParity::None } fn default_serial_stop_bits() -> SerialStopBits { SerialStopBits::One } fn default_serial_flow_control() -> SerialFlowControl { SerialFlowControl::None } fn default_serial_dtr() -> bool { false } fn default_serial_rts() -> bool { false } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum TransportType { Serial, Tcp, Udp, } impl std::fmt::Display for TransportType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TransportType::Serial => write!(f, "Serial"), TransportType::Tcp => write!(f, "Tcp"), TransportType::Udp => write!(f, "Udp"), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TransportConfig { Serial { port: String, baud_rate: u32, #[serde(default = "default_serial_data_bits")] data_bits: SerialDataBits, #[serde(default = "default_serial_parity")] parity: SerialParity, #[serde(default = "default_serial_stop_bits")] stop_bits: SerialStopBits, #[serde(default = "default_serial_flow_control")] flow_control: SerialFlowControl, #[serde(default = "default_serial_dtr")] dtr: bool, #[serde(default = "default_serial_rts")] rts: bool, }, Tcp { addr: String, }, Udp { bind_addr: String, remote_addr: Option, }, } #[async_trait] pub trait Transport: AsyncRead + AsyncWrite + Send + Sync + Unpin { fn name(&self) -> &str; fn transport_type(&self) -> TransportType; fn is_connected(&self) -> bool; async fn connect(&mut self) -> Result<()>; async fn disconnect(&mut self) -> Result<()>; } #[derive(Debug)] pub enum Connection { Serial(SerialTransport), Tcp(TcpTransport), Udp(UdpTransport), } impl Connection { pub fn new(config: TransportConfig) -> Self { match config { TransportConfig::Serial { port, baud_rate, data_bits, parity, stop_bits, flow_control, dtr, rts, } => Connection::Serial(SerialTransport::new( port, baud_rate, data_bits, parity, stop_bits, flow_control, dtr, rts, )), TransportConfig::Tcp { addr } => Connection::Tcp(TcpTransport::new(addr)), TransportConfig::Udp { bind_addr, remote_addr, } => Connection::Udp(UdpTransport::new(bind_addr, remote_addr)), } } pub fn transport_type(&self) -> TransportType { match self { Connection::Serial(_) => TransportType::Serial, Connection::Tcp(_) => TransportType::Tcp, Connection::Udp(_) => TransportType::Udp, } } pub fn name(&self) -> &str { match self { Connection::Serial(t) => t.name(), Connection::Tcp(t) => t.name(), Connection::Udp(t) => t.name(), } } pub fn is_connected(&self) -> bool { match self { Connection::Serial(t) => t.is_connected(), Connection::Tcp(t) => t.is_connected(), Connection::Udp(t) => t.is_connected(), } } pub async fn connect(&mut self) -> Result<()> { info!(transport = ?self.transport_type(), name = self.name(), "Connection connecting"); match self { Connection::Serial(t) => t.connect().await, Connection::Tcp(t) => t.connect().await, Connection::Udp(t) => t.connect().await, } } pub async fn disconnect(&mut self) -> Result<()> { info!(name = self.name(), "Connection disconnecting"); match self { Connection::Serial(t) => t.disconnect().await, Connection::Tcp(t) => t.disconnect().await, Connection::Udp(t) => t.disconnect().await, } } pub fn set_dtr(&mut self, state: bool) -> Result<()> { match self { Connection::Serial(t) => t.set_dtr(state), Connection::Tcp(_) | Connection::Udp(_) => Err(crate::error::Error::ConnectionFailed( "DTR only supported on Serial connections".into(), )), } } pub fn set_rts(&mut self, state: bool) -> Result<()> { match self { Connection::Serial(t) => t.set_rts(state), Connection::Tcp(_) | Connection::Udp(_) => Err(crate::error::Error::ConnectionFailed( "RTS only supported on Serial connections".into(), )), } } } impl AsyncRead for Connection { fn poll_read( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> std::task::Poll> { match self.get_mut() { Connection::Serial(t) => std::pin::Pin::new(t).poll_read(cx, buf), Connection::Tcp(t) => std::pin::Pin::new(t).poll_read(cx, buf), Connection::Udp(t) => std::pin::Pin::new(t).poll_read(cx, buf), } } } impl AsyncWrite for Connection { fn poll_write( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { match self.get_mut() { Connection::Serial(t) => std::pin::Pin::new(t).poll_write(cx, buf), Connection::Tcp(t) => std::pin::Pin::new(t).poll_write(cx, buf), Connection::Udp(t) => std::pin::Pin::new(t).poll_write(cx, buf), } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { match self.get_mut() { Connection::Serial(t) => std::pin::Pin::new(t).poll_flush(cx), Connection::Tcp(t) => std::pin::Pin::new(t).poll_flush(cx), Connection::Udp(t) => std::pin::Pin::new(t).poll_flush(cx), } } fn poll_shutdown( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { match self.get_mut() { Connection::Serial(t) => std::pin::Pin::new(t).poll_shutdown(cx), Connection::Tcp(t) => std::pin::Pin::new(t).poll_shutdown(cx), Connection::Udp(t) => std::pin::Pin::new(t).poll_shutdown(cx), } } } #[cfg(test)] mod tests { use super::*; use crate::transport::serial::{ SerialDataBits, SerialFlowControl, SerialParity, SerialStopBits, }; fn serial_config(port: &str, baud_rate: u32) -> TransportConfig { TransportConfig::Serial { port: port.into(), baud_rate, data_bits: SerialDataBits::Eight, parity: SerialParity::None, stop_bits: SerialStopBits::One, flow_control: SerialFlowControl::None, dtr: false, rts: false, } } #[test] fn test_transport_type_display() { assert_eq!(TransportType::Serial.to_string(), "Serial"); assert_eq!(TransportType::Tcp.to_string(), "Tcp"); assert_eq!(TransportType::Udp.to_string(), "Udp"); } #[test] fn test_transport_type_debug() { let _ = format!("{:?}", TransportType::Serial); let _ = format!("{:?}", TransportType::Tcp); let _ = format!("{:?}", TransportType::Udp); } #[test] fn test_transport_type_eq() { assert_eq!(TransportType::Serial, TransportType::Serial); assert_eq!(TransportType::Tcp, TransportType::Tcp); assert_eq!(TransportType::Udp, TransportType::Udp); assert_ne!(TransportType::Serial, TransportType::Tcp); assert_ne!(TransportType::Serial, TransportType::Udp); assert_ne!(TransportType::Tcp, TransportType::Udp); } #[test] fn test_transport_type_copy_clone() { let original = TransportType::Serial; let cloned = original; assert_eq!(original, cloned); let original = TransportType::Tcp; let cloned = original; assert_eq!(original, cloned); let original = TransportType::Udp; let cloned = original; assert_eq!(original, cloned); } #[test] fn test_transport_type_serialize() { assert_eq!( serde_json::to_string(&TransportType::Serial).unwrap(), "\"Serial\"" ); assert_eq!( serde_json::to_string(&TransportType::Tcp).unwrap(), "\"Tcp\"" ); assert_eq!( serde_json::to_string(&TransportType::Udp).unwrap(), "\"Udp\"" ); } #[test] fn test_transport_type_deserialize() { let t: TransportType = serde_json::from_str("\"Serial\"").unwrap(); assert_eq!(t, TransportType::Serial); let t: TransportType = serde_json::from_str("\"Tcp\"").unwrap(); assert_eq!(t, TransportType::Tcp); let t: TransportType = serde_json::from_str("\"Udp\"").unwrap(); assert_eq!(t, TransportType::Udp); } #[test] fn test_transport_type_roundtrip() { for original in [ TransportType::Serial, TransportType::Tcp, TransportType::Udp, ] { let json = serde_json::to_string(&original).unwrap(); let roundtripped: TransportType = serde_json::from_str(&json).unwrap(); assert_eq!(original, roundtripped); } } #[test] fn test_transport_config_debug() { let cfg = TransportConfig::Serial { port: "COM1".into(), baud_rate: 115200, data_bits: SerialDataBits::Eight, parity: SerialParity::None, stop_bits: SerialStopBits::One, flow_control: SerialFlowControl::None, dtr: false, rts: false, }; let _ = format!("{:?}", cfg); let cfg = TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }; let _ = format!("{:?}", cfg); let cfg = TransportConfig::Udp { bind_addr: "0.0.0.0:9000".into(), remote_addr: Some("192.168.1.1:9001".into()), }; let _ = format!("{:?}", cfg); } #[test] fn test_transport_config_clone() { let original = serial_config("COM1", 115200); let cloned = original.clone(); assert_eq!(format!("{:?}", original), format!("{:?}", cloned)); let original = TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }; let cloned = original.clone(); assert_eq!(format!("{:?}", original), format!("{:?}", cloned)); let original = TransportConfig::Udp { bind_addr: "0.0.0.0:9000".into(), remote_addr: Some("192.168.1.1:9001".into()), }; let cloned = original.clone(); assert_eq!(format!("{:?}", original), format!("{:?}", cloned)); } #[test] fn test_transport_config_serialize_serial() { let cfg = serial_config("COM1", 115200); let json = serde_json::to_string(&cfg).unwrap(); assert!(json.contains("COM1")); assert!(json.contains("115200")); assert!(json.contains("data_bits")); assert!(json.contains("parity")); assert!(json.contains("stop_bits")); assert!(json.contains("flow_control")); } #[test] fn test_transport_config_deserialize_serial() { let json = r#"{"Serial":{"port":"COM1","baud_rate":9600}}"#; let cfg: TransportConfig = serde_json::from_str(json).unwrap(); match cfg { TransportConfig::Serial { port, baud_rate, data_bits, parity, stop_bits, flow_control, dtr, rts, } => { assert_eq!(port, "COM1"); assert_eq!(baud_rate, 9600); assert_eq!(data_bits, SerialDataBits::Eight); assert_eq!(parity, SerialParity::None); assert_eq!(stop_bits, SerialStopBits::One); assert_eq!(flow_control, SerialFlowControl::None); assert!(!dtr); assert!(!rts); } _ => panic!("expected Serial variant"), } } #[test] fn test_transport_config_deserialize_serial_with_explicit_options() { let json = r#"{"Serial":{"port":"COM2","baud_rate":57600,"data_bits":"Seven","parity":"Even","stop_bits":"Two","flow_control":"Hardware"}}"#; let cfg: TransportConfig = serde_json::from_str(json).unwrap(); match cfg { TransportConfig::Serial { port, baud_rate, data_bits, parity, stop_bits, flow_control, dtr, rts, } => { assert_eq!(port, "COM2"); assert_eq!(baud_rate, 57600); assert_eq!(data_bits, SerialDataBits::Seven); assert_eq!(parity, SerialParity::Even); assert_eq!(stop_bits, SerialStopBits::Two); assert_eq!(flow_control, SerialFlowControl::Hardware); assert!(!dtr); assert!(!rts); } _ => panic!("expected Serial variant"), } } #[test] fn test_transport_config_serialize_tcp() { let cfg = TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }; let json = serde_json::to_string(&cfg).unwrap(); assert!(json.contains("127.0.0.1:8080")); } #[test] fn test_transport_config_deserialize_tcp() { let json = r#"{"Tcp":{"addr":"127.0.0.1:8080"}}"#; let cfg: TransportConfig = serde_json::from_str(json).unwrap(); match cfg { TransportConfig::Tcp { addr } => { assert_eq!(addr, "127.0.0.1:8080"); } _ => panic!("expected Tcp variant"), } } #[test] fn test_transport_config_serialize_udp_with_remote() { let cfg = TransportConfig::Udp { bind_addr: "0.0.0.0:9000".into(), remote_addr: Some("192.168.1.1:9001".into()), }; let json = serde_json::to_string(&cfg).unwrap(); assert!(json.contains("0.0.0.0:9000")); assert!(json.contains("192.168.1.1:9001")); } #[test] fn test_transport_config_serialize_udp_without_remote() { let cfg = TransportConfig::Udp { bind_addr: "0.0.0.0:9000".into(), remote_addr: None, }; let json = serde_json::to_string(&cfg).unwrap(); assert!(json.contains("0.0.0.0:9000")); assert!(json.contains("null")); } #[test] fn test_transport_config_deserialize_udp() { let json = r#"{"Udp":{"bind_addr":"0.0.0.0:9000","remote_addr":"192.168.1.1:9001"}}"#; let cfg: TransportConfig = serde_json::from_str(json).unwrap(); match cfg { TransportConfig::Udp { bind_addr, remote_addr, } => { assert_eq!(bind_addr, "0.0.0.0:9000"); assert_eq!(remote_addr, Some("192.168.1.1:9001".into())); } _ => panic!("expected Udp variant"), } let json = r#"{"Udp":{"bind_addr":"0.0.0.0:9000","remote_addr":null}}"#; let cfg: TransportConfig = serde_json::from_str(json).unwrap(); match cfg { TransportConfig::Udp { bind_addr, remote_addr, } => { assert_eq!(bind_addr, "0.0.0.0:9000"); assert_eq!(remote_addr, None); } _ => panic!("expected Udp variant"), } } #[test] fn test_transport_config_roundtrip() { let configs = vec![ serial_config("COM3", 57600), TransportConfig::Tcp { addr: "10.0.0.1:9999".into(), }, TransportConfig::Udp { bind_addr: "0.0.0.0:7000".into(), remote_addr: Some("10.0.0.2:7001".into()), }, TransportConfig::Udp { bind_addr: "127.0.0.1:8000".into(), remote_addr: None, }, ]; for original in &configs { let json = serde_json::to_string(original).unwrap(); let roundtripped: TransportConfig = serde_json::from_str(&json).unwrap(); assert_eq!( serde_json::to_string(&roundtripped).unwrap(), json, "roundtrip serialized forms must match" ); } } // ── Connection tests ────────────────────────────────────────────── #[test] fn test_connection_new_serial() { let conn = Connection::new(serial_config("COM1", 115200)); assert_eq!(conn.transport_type(), TransportType::Serial); assert_eq!(conn.name(), "COM1"); assert!(!conn.is_connected()); } #[test] fn test_connection_new_tcp() { let conn = Connection::new(TransportConfig::Tcp { addr: "192.168.1.1:8080".into(), }); assert_eq!(conn.transport_type(), TransportType::Tcp); assert_eq!(conn.name(), "192.168.1.1:8080"); assert!(!conn.is_connected()); } #[test] fn test_connection_new_udp_with_remote() { let conn = Connection::new(TransportConfig::Udp { bind_addr: "0.0.0.0:9000".into(), remote_addr: Some("192.168.1.1:9001".into()), }); assert_eq!(conn.transport_type(), TransportType::Udp); assert_eq!(conn.name(), "0.0.0.0:9000"); assert!(!conn.is_connected()); } #[test] fn test_connection_new_udp_without_remote() { let conn = Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: None, }); assert_eq!(conn.transport_type(), TransportType::Udp); assert_eq!(conn.name(), "127.0.0.1:0"); assert!(!conn.is_connected()); } #[test] fn test_connection_is_connected_default() { let serial = Connection::new(serial_config("COM1", 9600)); let tcp = Connection::new(TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }); let udp = Connection::new(TransportConfig::Udp { bind_addr: "0.0.0.0:0".into(), remote_addr: None, }); assert!(!serial.is_connected()); assert!(!tcp.is_connected()); assert!(!udp.is_connected()); } #[test] fn test_connection_debug() { let serial = Connection::new(serial_config("COM1", 115200)); let tcp = Connection::new(TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }); let udp = Connection::new(TransportConfig::Udp { bind_addr: "0.0.0.0:0".into(), remote_addr: None, }); let _ = format!("{:?}", serial); let _ = format!("{:?}", tcp); let _ = format!("{:?}", udp); } fn noop_waker() -> std::task::Waker { use std::task::{RawWaker, RawWakerVTable}; unsafe fn clone(_: *const ()) -> RawWaker { RawWaker::new(std::ptr::null(), &VTABLE) } unsafe fn wake(_: *const ()) {} unsafe fn wake_by_ref(_: *const ()) {} unsafe fn drop(_: *const ()) {} static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); unsafe { std::task::Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) } } fn serial_conn() -> Connection { Connection::new(serial_config("COM1", 115200)) } fn tcp_conn() -> Connection { Connection::new(TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }) } fn udp_conn() -> Connection { Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: None, }) } #[test] fn test_connection_poll_read_not_connected() { use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::ReadBuf; for mut conn in [serial_conn(), tcp_conn(), udp_conn()] { let pinned = Pin::new(&mut conn); let waker = noop_waker(); let mut cx = Context::from_waker(&waker); let mut buf_data = [0u8; 16]; let mut buf = ReadBuf::new(&mut buf_data); match pinned.poll_read(&mut cx, &mut buf) { Poll::Ready(Err(e)) => { assert_eq!(e.kind(), std::io::ErrorKind::NotConnected); } other => panic!("expected Poll::Ready(Err(NotConnected)), got {:?}", other), } } } #[test] fn test_connection_poll_write_not_connected() { use std::pin::Pin; use std::task::{Context, Poll}; for mut conn in [serial_conn(), tcp_conn(), udp_conn()] { let pinned = Pin::new(&mut conn); let waker = noop_waker(); let mut cx = Context::from_waker(&waker); match pinned.poll_write(&mut cx, b"hello") { Poll::Ready(Err(e)) => { assert_eq!(e.kind(), std::io::ErrorKind::NotConnected); } other => panic!("expected Poll::Ready(Err(NotConnected)), got {:?}", other), } } } #[test] fn test_connection_poll_flush_not_connected() { use std::pin::Pin; use std::task::{Context, Poll}; for mut conn in [serial_conn(), tcp_conn(), udp_conn()] { let pinned = Pin::new(&mut conn); let waker = noop_waker(); let mut cx = Context::from_waker(&waker); match pinned.poll_flush(&mut cx) { Poll::Ready(Ok(())) => {} other => panic!("expected Poll::Ready(Ok(())), got {:?}", other), } } } #[test] fn test_connection_poll_shutdown_not_connected() { use std::pin::Pin; use std::task::{Context, Poll}; for mut conn in [serial_conn(), tcp_conn(), udp_conn()] { let pinned = Pin::new(&mut conn); let waker = noop_waker(); let mut cx = Context::from_waker(&waker); match pinned.poll_shutdown(&mut cx) { Poll::Ready(Ok(())) => {} other => panic!("expected Poll::Ready(Ok(())), got {:?}", other), } } } #[tokio::test] async fn test_connection_tcp_connect_and_disconnect() { use tokio::net::TcpListener; let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap().to_string(); let mut conn = Connection::new(TransportConfig::Tcp { addr }); assert!(!conn.is_connected()); conn.connect().await.unwrap(); assert!(conn.is_connected()); conn.disconnect().await.unwrap(); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_udp_connect_and_disconnect_without_remote() { let mut conn = Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: None, }); assert!(!conn.is_connected()); conn.connect().await.unwrap(); assert!(conn.is_connected()); conn.disconnect().await.unwrap(); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_udp_connect_and_disconnect_with_remote() { use tokio::net::UdpSocket; let remote = UdpSocket::bind("127.0.0.1:0").await.unwrap(); let remote_addr = remote.local_addr().unwrap().to_string(); let mut conn = Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: Some(remote_addr), }); assert!(!conn.is_connected()); conn.connect().await.unwrap(); assert!(conn.is_connected()); conn.disconnect().await.unwrap(); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_tcp_double_connect_is_noop() { use tokio::net::TcpListener; let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap().to_string(); let mut conn = Connection::new(TransportConfig::Tcp { addr }); conn.connect().await.unwrap(); assert!(conn.is_connected()); let result = conn.connect().await; assert!(result.is_ok(), "double connect should be a no-op"); assert!(conn.is_connected()); } #[tokio::test] async fn test_connection_tcp_disconnect_without_connect() { let mut conn = Connection::new(TransportConfig::Tcp { addr: "127.0.0.1:8080".into(), }); let result = conn.disconnect().await; assert!(result.is_ok(), "disconnect without connect should be safe"); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_tcp_connect_to_unreachable() { let mut conn = Connection::new(TransportConfig::Tcp { addr: "127.0.0.1:1".into(), }); let result = conn.connect().await; assert!(result.is_err()); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_udp_connect_bind_failure() { let mut conn = Connection::new(TransportConfig::Udp { bind_addr: "invalid_addr".into(), remote_addr: None, }); let result = conn.connect().await; assert!(result.is_err()); assert!(!conn.is_connected()); } #[tokio::test] async fn test_connection_udp_double_connect_is_noop() { let mut conn = Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: None, }); conn.connect().await.unwrap(); assert!(conn.is_connected()); let result = conn.connect().await; assert!(result.is_ok()); assert!(conn.is_connected()); } #[tokio::test] async fn test_connection_tcp_async_read_write() { use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap().to_string(); let server = tokio::spawn(async move { let (mut stream, _) = listener.accept().await.unwrap(); AsyncWriteExt::write_all(&mut stream, b"hello") .await .unwrap(); let mut buf = [0u8; 5]; AsyncReadExt::read_exact(&mut stream, &mut buf) .await .unwrap(); assert_eq!(&buf, b"world"); }); let mut conn = Connection::new(TransportConfig::Tcp { addr }); conn.connect().await.unwrap(); let mut buf = [0u8; 5]; AsyncReadExt::read_exact(&mut conn, &mut buf).await.unwrap(); assert_eq!(&buf, b"hello"); AsyncWriteExt::write_all(&mut conn, b"world").await.unwrap(); conn.disconnect().await.unwrap(); server.await.unwrap(); } #[tokio::test] async fn test_connection_udp_async_read_write_with_remote() { use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::UdpSocket; let remote = UdpSocket::bind("127.0.0.1:0").await.unwrap(); let remote_addr = remote.local_addr().unwrap().to_string(); let mut conn = Connection::new(TransportConfig::Udp { bind_addr: "127.0.0.1:0".into(), remote_addr: Some(remote_addr), }); conn.connect().await.unwrap(); AsyncWriteExt::write_all(&mut conn, b"hello").await.unwrap(); let mut buf = [0u8; 1024]; let (n, from) = remote.recv_from(&mut buf).await.unwrap(); assert_eq!(&buf[..n], b"hello"); remote.send_to(b"world", from).await.unwrap(); let mut read_buf = [0u8; 5]; AsyncReadExt::read_exact(&mut conn, &mut read_buf) .await .unwrap(); assert_eq!(&read_buf, b"world"); } }