using System.Buffers.Binary; using AcDream.Core.Net.Cryptography; namespace AcDream.Core.Net.Tests.Transport; /// /// Faithful port of ACE's server-side C2S checksum-key discipline /// (references/ACE/Source/ACE.Common/Cryptography/CryptoSystem.cs) /// over acdream's . This is the exact machinery /// Coldeve uses to verify every encrypted client packet, so the double must /// reproduce its behavior word-for-word — in particular the 256-key search /// window, the parked-key set ("xors"), and the way an unexpected key /// permanently orphans window capacity. /// /// /// Behavior summary (all ACE, none invented): /// /// The keystream is one ISAAC word per ENCRYPTED packet, in the /// order the client SENT them (drew them), not arrival order. /// walks forward at most /// − |xors| words hunting for the /// presented key, parking every skipped word in the xors set. /// advances the wheel when the presented /// key is the current one, otherwise un-parks it from xors. /// A key that is BEHIND the wheel (already consumed) can never be /// found again — searching for it burns the remaining window. /// /// /// internal sealed class AceCryptoModel { /// CryptoSystem.cs:8 — the 256-key search window. public const int MaximumEffortLevel = 256; private readonly IsaacRandom _keystream; /// /// CryptoSystem.cs:9 — keys the search walked past while hunting for an /// out-of-order arrival, parked so the retransmission (carrying the /// ORIGINAL key) can still verify. /// private readonly HashSet _xors = new(); /// CryptoSystem.cs:10 — the next expected keystream word. public uint CurrentKey { get; private set; } /// /// CryptoSystem.cs:11-14 — seed the ISAAC wheel and pre-draw the first /// key. ACE's CryptoSystem(uint seed) passes /// BitConverter.GetBytes(seed) (little-endian), which we mirror. /// public AceCryptoModel(uint seed) { Span seedBytes = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(seedBytes, seed); _keystream = new IsaacRandom(seedBytes); CurrentKey = _keystream.Next(); } /// /// Remaining search capacity: 256 − |xors|. Every parked key that is /// never consumed (an orphan — e.g. from a re-keyed resend) shrinks this /// permanently. When it reaches zero, the next packet loss is /// unrecoverable. /// public int Headroom => MaximumEffortLevel - _xors.Count; /// /// Number of currently parked keys. A parked key is either a pending /// retransmission's original key (healthy, recovered on arrival) or a /// permanent orphan (the client re-keyed the resend and the original /// word will never be presented). /// public int OrphanCount => _xors.Count; /// /// CryptoSystem.cs:19-29 — advance the wheel if is /// the current key; otherwise remove it from the parked set. /// public void ConsumeKey(uint x) { if (CurrentKey == x) CurrentKey = _keystream.Next(); else _xors.Remove(x); } /// /// CryptoSystem.cs:30-49 — is the current key, a /// parked key, or reachable within the remaining search window? Walking /// parks every skipped word. Verbatim port including the loop bound /// being captured BEFORE the walk starts. /// public bool Search(uint x) { if (CurrentKey == x) return true; if (_xors.Contains(x)) return true; int g = _xors.Count; for (int i = 0; i < MaximumEffortLevel - g; i++) { _xors.Add(CurrentKey); ConsumeKey(CurrentKey); if (CurrentKey == x) return true; } return false; } }