using System.IO.Hashing;
namespace NATS.Server.Raft;
///
/// Splits a snapshot byte array into fixed-size chunks for streaming transfer
/// and computes a CRC32 checksum over the entire data for integrity validation.
///
/// During an InstallSnapshot RPC the leader streams a large snapshot to a lagging
/// follower chunk-by-chunk rather than sending it in a single message. The follower
/// accumulates the chunks, reassembles them, then validates the CRC32 before
/// applying the snapshot — preventing silent data corruption in transit.
///
/// Go reference: raft.go:3500-3700 (installSnapshot chunked transfer and CRC validation)
///
public sealed class SnapshotChunkEnumerator : IEnumerable
{
private readonly byte[] _data;
private readonly int _chunkSize;
private uint? _crc32Value;
///
/// Default chunk size matching Go's snapshot chunk transfer size (64 KiB).
/// Go reference: raft.go snapshotChunkSize constant.
///
public const int DefaultChunkSize = 65536;
///
/// Creates a new chunk enumerator over .
///
/// The full snapshot data to split into chunks.
/// Maximum size of each chunk in bytes. Defaults to 64 KiB.
///
/// Thrown when is less than or equal to zero.
///
public SnapshotChunkEnumerator(byte[] data, int chunkSize = DefaultChunkSize)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(chunkSize);
_data = data;
_chunkSize = chunkSize;
}
///
/// The CRC32 (IEEE 802.3) checksum of the entire snapshot data.
/// Computed lazily on first access and cached for subsequent reads.
/// The receiver uses this value to validate the reassembled snapshot after
/// all chunks have been received.
/// Go reference: raft.go installSnapshot CRC validation.
///
public uint Crc32Value
{
get
{
if (!_crc32Value.HasValue)
{
var crc = new Crc32();
crc.Append(_data);
_crc32Value = crc.GetCurrentHashAsUInt32();
}
return _crc32Value.Value;
}
}
///
/// The total number of chunks this data splits into given the configured chunk size.
///
public int ChunkCount => _data.Length == 0 ? 1 : ((_data.Length + _chunkSize - 1) / _chunkSize);
///
public IEnumerator GetEnumerator()
{
if (_data.Length == 0)
{
yield return [];
yield break;
}
var offset = 0;
while (offset < _data.Length)
{
var length = Math.Min(_chunkSize, _data.Length - offset);
var chunk = new byte[length];
_data.AsSpan(offset, length).CopyTo(chunk);
offset += length;
yield return chunk;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}