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