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();
}