public static List <FileBlock> ComputeDelta(System.IO.Stream input, long inputSize, ChunkedChecksum chunks, out long deltaSize, Action <long, long> progress = null) { List <FileBlock> deltas = new List <FileBlock>(); if (inputSize < chunks.ChunkSize) { deltas.Add(new FileBlock() { Base = false, Offset = 0, Length = inputSize }); deltaSize = 1 + 8 + inputSize; return(deltas); } List <Chunk> sortedChunks = chunks.Chunks.OrderBy(x => new Tuple <uint, long>(x.Adler32, x.Offset)).ToList(); Dictionary <uint, int> adlerToIndex = new Dictionary <uint, int>(); for (int i = 0; i < sortedChunks.Count; i++) { adlerToIndex[sortedChunks[i].Adler32] = i; } BufferedView view = new BufferedView(input, 4 * 1024 * 1024); CircularBuffer circularBuffer = new CircularBuffer(chunks.ChunkSize); long readHead = 0; long processHead = 0; uint checksum = 0; FileBlock lastMatch = null; System.Security.Cryptography.SHA1 sha1 = null; if (chunks.HashType == HashMode.SHA1) { sha1 = System.Security.Cryptography.SHA1.Create(); } while (processHead != inputSize) { // first run through if (readHead == processHead) { for (int i = 0; i < chunks.ChunkSize; i++) { circularBuffer.AddByte(view.Next()); } readHead = chunks.ChunkSize; checksum = FastHash(circularBuffer.ToArray(chunks.ChunkSize), chunks.ChunkSize); } int remainder = (int)(readHead - processHead); if (progress != null) { progress(inputSize, processHead); } int possibleChunkLookup; Chunk match = null; if (adlerToIndex.TryGetValue(checksum, out possibleChunkLookup)) { byte[] data = circularBuffer.ToArray(chunks.ChunkSize > remainder ? remainder : chunks.ChunkSize); byte[] realHash = null; if (sha1 != null) { realHash = sha1.ComputeHash(data, 0, data.Length); } else { realHash = (new Utilities.Murmur3()).ComputeHash(data); } while (true) { Chunk inspectedChunk = sortedChunks[possibleChunkLookup]; if (inspectedChunk.Adler32 != checksum) { break; } if (inspectedChunk.Length == remainder && System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(realHash, inspectedChunk.Hash)) { match = inspectedChunk; break; } possibleChunkLookup--; } } if (match != null) { if (lastMatch == null || (lastMatch.Base == false || lastMatch.End != match.Offset)) { lastMatch = new FileBlock() { Base = true, Length = 0, Offset = match.Offset }; deltas.Add(lastMatch); } lastMatch.Length += match.Length; } else { if (lastMatch == null || lastMatch.Base == true) { lastMatch = new FileBlock() { Base = false, Length = 0, Offset = processHead }; deltas.Add(lastMatch); } lastMatch.Length++; } // eat next byte int bytesToEat = 1; if (match != null) { bytesToEat = match.Length; } while (bytesToEat-- != 0) { remainder = (int)(readHead - processHead); if (readHead < inputSize) { byte add = view.Next(); byte remove = circularBuffer.AddByte(add); checksum = RotateHash(checksum, add, remove, remainder); readHead++; } else if (bytesToEat == 0) { checksum = FastHash(circularBuffer.ToArray(remainder - 1), remainder - 1); } processHead++; } } deltaSize = 0; foreach (var x in deltas) { deltaSize++; if (x.Base == false) { deltaSize += 8; deltaSize += x.Length; } else { deltaSize += 8; deltaSize += 8; } } return(deltas); }