using System.Net; using System.Net.Sockets; namespace AcDream.Core.Net; /// /// Minimum-viable UDP transport for acdream. Wraps a /// with synchronous send + timeout-based receive — good enough for the /// Phase 4.6 handshake smoke test and early state-machine bring-up. /// /// /// Not yet provided (deferred to a later phase once the handshake /// actually works): background receive thread, outbound queue, ack/retransmit /// window, heartbeat timer, concurrent send/receive. The acdream game loop /// will need a real async pump eventually but building that now would be /// debugging two things at once when we hit the first protocol mismatch. /// /// public sealed class NetClient : IDisposable { private readonly UdpClient _udp; private readonly IPEndPoint _remote; public NetClient(IPEndPoint remote) { _remote = remote; // Bind to an OS-assigned local port; server will reply to it. _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); } /// The local endpoint the OS assigned us. public IPEndPoint LocalEndPoint => (IPEndPoint)_udp.Client.LocalEndPoint!; /// The remote endpoint we're talking to. public IPEndPoint RemoteEndPoint => _remote; /// /// Send a datagram to the configured default remote. Blocks until the /// OS has accepted the bytes (fast — just a kernel buffer copy on loopback). /// public void Send(ReadOnlySpan datagram) { _udp.Send(datagram.ToArray(), datagram.Length, _remote); } /// /// Send a datagram to an arbitrary remote endpoint. Needed for the AC /// handshake because the server binds separate listeners on port 9000 /// (LoginRequest) and port 9001 (ConnectResponse), so the second /// handshake leg targets a different port than the first. /// public void Send(IPEndPoint remote, ReadOnlySpan datagram) { _udp.Send(datagram.ToArray(), datagram.Length, remote); } /// /// Block until a datagram arrives or elapses. /// Returns the raw bytes, or null on timeout. The sender's /// endpoint is recorded in so the caller can /// verify it matches the expected remote. /// public byte[]? Receive(TimeSpan timeout, out IPEndPoint? from) { _udp.Client.ReceiveTimeout = (int)timeout.TotalMilliseconds; try { IPEndPoint any = new(IPAddress.Any, 0); var bytes = _udp.Receive(ref any); from = any; return bytes; } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut) { from = null; return null; } } public void Dispose() => _udp.Dispose(); }