Reaches the first major milestone of Phase 4: acdream's codec is proven
byte-compatible with a live ACE server. LiveHandshakeTests drives a real
UDP exchange against 127.0.0.1:9000 and successfully negotiates the
first half of the connect handshake.
Added:
- Packets/PacketHeaderOptional.cs: new ConnectRequest flag branch.
ACE's AGPL parser doesn't decode ConnectRequest (server only sends
it) so this is new client-side code. Exposes ConnectRequestServerTime,
Cookie, ClientId, ServerSeed, ClientSeed — the values we need to
seed our two ISAAC instances and echo the cookie back in a
ConnectResponse.
- NetClient.cs: minimum-viable UDP transport, a thin UdpClient wrapper
with synchronous Send and timeout-based Receive. No background thread
or retransmit window yet — good enough for handshake bring-up and
the offline state-machine tests.
- LiveHandshakeTests.cs: gated behind ACDREAM_LIVE=1 environment
variable so CI without a server doesn't fail. Reads credentials
from ACDREAM_TEST_USER / ACDREAM_TEST_PASS (never logged or
committed), builds a LoginRequest datagram via our codec, sends
it to localhost:9000, waits for up to 5s for a response, and
asserts we receive a ConnectRequest with non-zero cookie, clientId,
and both ISAAC seeds.
Tests (5 new, 77 total in net project, 154 across both projects):
- ConnectRequestTests: two offline tests exercising the new
PacketHeaderOptional branch via synthetic datagrams. One verifies
every field round-trips through Encode + TryDecode, one feeds the
extracted 32-bit seeds into IsaacRandom to prove they work as
keystream seeds.
- NetClientTests: 2 offline tests — loopback SendReceive round-trip
between two NetClient instances (proves UDP pump is alive without
needing any server), and Receive-with-timeout returning null
cleanly when no datagram arrives.
- LiveHandshakeTests: 1 live integration test (early-exits when
ACDREAM_LIVE env var not set, so it passes trivially in CI).
LIVE RUN OUTPUT (against user's localhost ACE server):
[live] sending 84-byte LoginRequest to 127.0.0.1:9000 (user.len=11, pass.len=12)
[live] received 52-byte datagram from 127.0.0.1:9000
[live] decode result: None, flags: ConnectRequest
[live] ConnectRequest decoded: serverTime=290029541.121 cookie=0xAC45998D06754133
clientId=0x00000001 serverSeed=0x4CC09763 clientSeed=0x5C3DE13E
Meaning: 84-byte LoginRequest went out, 52-byte ConnectRequest came
back, codec.TryDecode returned None error, every field parsed to a
sensible value. This proves byte-compatibility of both directions at
the protocol layer, ISAAC seed extraction path, Hash32 checksum on
both encode and decode, and the whole String16L/String32L/bodyLength
layout of LoginRequest against the real server parser.
Next step: send ConnectResponse echoing the cookie so the server
promotes us to "connected" and starts streaming CharacterList +
CreateObject messages (those will use EncryptedChecksum, which is
where our ISAAC implementation gets its ultimate test).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
39 lines
1.4 KiB
C#
39 lines
1.4 KiB
C#
using System.Net;
|
|
using AcDream.Core.Net;
|
|
|
|
namespace AcDream.Core.Net.Tests;
|
|
|
|
public class NetClientTests
|
|
{
|
|
[Fact]
|
|
public void SendReceive_LoopbackSelfTest_EchoRoundTrip()
|
|
{
|
|
// Two NetClients on loopback that send messages to each other.
|
|
// Proves Send + Receive actually work end-to-end over real UDP
|
|
// without depending on any remote server.
|
|
using var serverSide = new NetClient(new IPEndPoint(IPAddress.Loopback, 0));
|
|
using var clientSide = new NetClient(new IPEndPoint(IPAddress.Loopback, serverSide.LocalEndPoint.Port));
|
|
|
|
byte[] outbound = { 0xDE, 0xAD, 0xBE, 0xEF };
|
|
clientSide.Send(outbound);
|
|
|
|
var received = serverSide.Receive(TimeSpan.FromSeconds(2), out var from);
|
|
|
|
Assert.NotNull(received);
|
|
Assert.Equal(outbound, received);
|
|
Assert.NotNull(from);
|
|
Assert.Equal(IPAddress.Loopback, from!.Address);
|
|
Assert.Equal(clientSide.LocalEndPoint.Port, from.Port);
|
|
}
|
|
|
|
[Fact]
|
|
public void Receive_Timeout_ReturnsNullAndNoException()
|
|
{
|
|
// Listen for something that will never arrive; the timeout path
|
|
// must return null cleanly rather than leaking a SocketException.
|
|
using var client = new NetClient(new IPEndPoint(IPAddress.Loopback, 1));
|
|
var result = client.Receive(TimeSpan.FromMilliseconds(100), out var from);
|
|
Assert.Null(result);
|
|
Assert.Null(from);
|
|
}
|
|
}
|