From c5fc13d892038deae9c539055055ebb4fb228c7a Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 18:53:07 +0200 Subject: [PATCH] feat(client): expose cloneable relay I/O handle The Windows client frame pump needs one side reading TAP frames and sending them to the relay while another side receives relay datagrams and writes them to TAP. That should not require the binary to reach into `ClientSession` internals. Introduce `ClientRelayIo`, a cloneable handle around the accepted QUIC connection and server welcome. It owns the Ethernet datagram send/receive logic that previously lived directly on `ClientSession`, while `ClientSession` keeps convenience forwarding methods for existing callers. This is an enabling slice only. TAP frame pumping can now hold independent relay I/O handles without broadening the platform-specific TAP crate. Test Plan: - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - Windows-target cargo clippy for lanparty-client-tap with -D warnings - git diff --check Refs: PLAN.md Windows TAP frame pump --- crates/lanparty-client-core/src/lib.rs | 52 ++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/crates/lanparty-client-core/src/lib.rs b/crates/lanparty-client-core/src/lib.rs index 496c340..70789bf 100644 --- a/crates/lanparty-client-core/src/lib.rs +++ b/crates/lanparty-client-core/src/lib.rs @@ -241,6 +241,45 @@ impl ClientSession { &self.welcome } + #[must_use] + pub fn relay_io(&self) -> ClientRelayIo { + ClientRelayIo::new(self.connection.clone(), self.welcome.clone()) + } + + pub fn send_ethernet(&self, frame: &[u8]) -> Result<()> { + self.relay_io().send_ethernet(frame) + } + + pub async fn recv_ethernet(&self) -> Result { + self.relay_io().recv_ethernet().await + } + + pub async fn shutdown(self, reason: &str) { + self.connection.close(0_u32.into(), reason.as_bytes()); + self.endpoint.wait_idle().await; + } +} + +#[derive(Debug, Clone)] +pub struct ClientRelayIo { + connection: quinn::Connection, + welcome: ServerWelcome, +} + +impl ClientRelayIo { + #[must_use] + fn new(connection: quinn::Connection, welcome: ServerWelcome) -> Self { + Self { + connection, + welcome, + } + } + + #[must_use] + pub const fn welcome(&self) -> &ServerWelcome { + &self.welcome + } + pub fn send_ethernet(&self, frame: &[u8]) -> Result<()> { EthernetFrame::parse(frame).context("client Ethernet frame is malformed")?; let datagram = encode_datagram( @@ -282,11 +321,6 @@ impl ClientSession { }); } } - - pub async fn shutdown(self, reason: &str) { - self.connection.close(0_u32.into(), reason.as_bytes()); - self.endpoint.wait_idle().await; - } } pub async fn connect_client(config: ClientSessionConfig) -> Result { @@ -534,9 +568,13 @@ mod tests { ); assert_eq!(client.welcome().room_id(), 7); assert_eq!(client.welcome().peer_id(), 2); + let relay_io = client.relay_io(); + assert_eq!(relay_io.welcome().peer_id(), 2); - client.send_ethernet(ðernet_frame(b"to relay")).unwrap(); - let received = tokio::time::timeout(Duration::from_secs(5), client.recv_ethernet()) + relay_io + .send_ethernet(ðernet_frame(b"to relay")) + .unwrap(); + let received = tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet()) .await .unwrap() .unwrap();