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