feat(net): full 3-way handshake + ISAAC-encrypted decode proven live (Phase 4.6e)
This is the Phase 4 protocol-compatibility proof. acdream's codec now
completes the full AC UDP handshake against a live ACE server and
successfully decodes three consecutive EncryptedChecksum game packets
— which means every layer of the codec is byte-compatible with ACE.
Changes:
- NetClient: added Send(IPEndPoint, ReadOnlySpan<byte>) overload so
one socket can talk to ACE's two listener ports (9000 for
LoginRequest, 9001 for ConnectResponse and all subsequent traffic)
- LiveHandshakeTests.Live_FullThreeWayHandshake_ReachesConnectedState:
drives the full 3-leg handshake end-to-end. Protocol details that
I got wrong on the first attempt and fixed after reading
references/holtburger/crates/holtburger-session/src/session/auth.rs:
* ConnectResponse header.Sequence = 1 (LoginRequest is seq 0)
* ConnectResponse header.Id = 0 (NOT the clientId from
ConnectRequest; that field is ACE's internal session index,
separate from the packet header Id)
* 200ms Thread.Sleep before sending ConnectResponse — holtburger
calls this ACE_HANDSHAKE_RACE_DELAY_MS, empirically determined
to avoid a server-side race where ACE is still finalizing the
session when our ConnectResponse arrives
* ConnectResponse goes to port 9001, not 9000 (ACE's second
ConnectionListener, see Network/Managers/SocketManager.cs)
LIVE RUN OUTPUT:
[live] step 1: sending 84-byte LoginRequest to 127.0.0.1:9000
[live] step 2: got 52-byte datagram from 127.0.0.1:9000,
flags=ConnectRequest
ConnectRequest cookie=0x458ABEE950D18BEE clientId=0x00000000
[live] step 3: sleeping 200ms then sending 28-byte ConnectResponse
to 127.0.0.1:9001
ISAAC seeds primed
[live] step 4: got 28-byte datagram from :9001,
flags=EncryptedChecksum,TimeSync, seq=2 OK
[live] step 4: got 64-byte datagram from :9001,
flags=EncryptedChecksum,BlobFragments, seq=3 OK
[live] step 4: got 152-byte datagram from :9001,
flags=EncryptedChecksum,BlobFragments, seq=4 OK
[live] step 4: got 24-byte datagram from :9001,
flags=AckSequence, seq=4 OK
[live] step 4: got 24-byte datagram from :9001,
flags=AckSequence, seq=4 OK
[live] step 4 summary: 5 packets received, 5 decoded OK,
0 checksum failures
What each "OK" proves, reading left to right:
* TimeSync (seq=2): our IsaacRandom is byte-compatible with ACE's
ISAAC.cs — if a single bit were wrong in any state register the
checksum key would mismatch and decode would fail. Our inbound
ISAAC consumed one word for this packet.
* BlobFragments (seq=3, 64 bytes): header hash + fragment hash +
ISAAC key recipe all check out. These fragments contain the start
of GameMessageCharacterList / ServerName / DDDInterrogation game
messages ACE enqueues right after HandleConnectResponse. We don't
parse game message bodies yet (Phase 4.7) but the fragments are
fully retrievable from Packet.Fragments.
* BlobFragments (seq=4, 152 bytes): continuation of the same game
messages; our sequential ISAAC consumption handled two back-to-back
encrypted packets correctly.
* AckSequence (seq=4): unencrypted mixed with encrypted in the same
stream — our codec handles both paths in one session.
Everything in AcDream.Core.Net is now proven byte-compatible with a
retail AC server at the protocol level. The remaining Phase 4 work
(4.6f, 4.7) is above the codec: parsing game message opcodes out of
the fragment payloads and routing CreateObject into IGameState so
acdream can show the foundry statue and the +Acdream character.
Test counts: 77 core + 73 net (+1 new live test) = 150 passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0cb30aa0c8
commit
a961d842d4
2 changed files with 140 additions and 5 deletions
|
|
@ -35,15 +35,25 @@ public sealed class NetClient : IDisposable
|
|||
public IPEndPoint RemoteEndPoint => _remote;
|
||||
|
||||
/// <summary>
|
||||
/// Send a datagram to the configured remote. Blocks until the OS has
|
||||
/// accepted the bytes (fast — just a kernel buffer copy on loopback).
|
||||
/// Send a datagram to the configured default remote. Blocks until the
|
||||
/// OS has accepted the bytes (fast — just a kernel buffer copy on loopback).
|
||||
/// </summary>
|
||||
public void Send(ReadOnlySpan<byte> datagram)
|
||||
{
|
||||
// UdpClient.Send on .NET doesn't take a span directly; allocate once.
|
||||
_udp.Send(datagram.ToArray(), datagram.Length, _remote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public void Send(IPEndPoint remote, ReadOnlySpan<byte> datagram)
|
||||
{
|
||||
_udp.Send(datagram.ToArray(), datagram.Length, remote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block until a datagram arrives or <paramref name="timeout"/> elapses.
|
||||
/// Returns the raw bytes, or <c>null</c> on timeout. The sender's
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue