public void Apply(Stream basisFileStream, IDeltaReader delta, Stream outputStream)
        {
            delta.Apply(
                writeData: (data) => outputStream.Write(data, 0, data.Length),
                copy: (startPosition, length) =>
            {
                basisFileStream.Seek(startPosition, SeekOrigin.Begin);

                var buffer = new byte[4 * 1024 * 1024];
                int read;
                long soFar = 0;
                while ((read = basisFileStream.Read(buffer, 0, (int)Math.Min(length - soFar, buffer.Length))) > 0)
                {
                    soFar += read;
                    outputStream.Write(buffer, 0, read);
                }
            });

            if (!SkipHashCheck)
            {
                outputStream.Seek(0, SeekOrigin.Begin);

                var sourceFileHash = delta.ExpectedHash;
                var algorithm      = delta.HashAlgorithm;

                var actualHash = algorithm.ComputeHash(outputStream);

                if (!BinaryComparer.CompareArray(sourceFileHash, actualHash))
                {
                    throw new UsageException("Verification of the patched file failed. The SHA1 hash of the patch result file, and the file that was used as input for the delta, do not match. This can happen if the basis file changed since the signatures were calculated.");
                }
            }
        }
Example #2
0
        void EnsureMetadata()
        {
            if (hasReadMetadata)
            {
                return;
            }

            reader.BaseStream.Seek(0, SeekOrigin.Begin);

            var first = reader.ReadBytes(BinaryFormat.DeltaHeader.Length);

            if (!BinaryComparer.CompareArray(first, BinaryFormat.DeltaHeader))
            {
                throw new CorruptFileFormatException("The delta file appears to be corrupt.");
            }

            var version = reader.ReadByte();

            if (version != BinaryFormat.Version)
            {
                throw new CorruptFileFormatException("The delta file uses a newer file format than this program can handle.");
            }

            var hashAlgorithmName = reader.ReadString();

            hashAlgorithm = SupportedAlgorithms.Hashing.Create(hashAlgorithmName);

            var hashLength = reader.ReadInt32();

            expectedHash = reader.ReadBytes(hashLength);
            var endOfMeta = reader.ReadBytes(BinaryFormat.EndOfMetadata.Length);

            if (!BinaryComparer.CompareArray(BinaryFormat.EndOfMetadata, endOfMeta))
            {
                throw new CorruptFileFormatException("The signature file appears to be corrupt.");
            }

            hasReadMetadata = true;
        }
        public void BuildDelta(Stream newFileStream, ISignatureReader signatureReader, IDeltaWriter deltaWriter)
        {
            var signature = signatureReader.ReadSignature();
            var chunks    = signature.Chunks;

            var hash = signature.HashAlgorithm.ComputeHash(newFileStream);

            newFileStream.Seek(0, SeekOrigin.Begin);

            deltaWriter.WriteMetadata(signature.HashAlgorithm, hash);

            chunks = OrderChunksByChecksum(chunks);

            int minChunkSize;
            int maxChunkSize;
            var chunkMap = CreateChunkMap(chunks, out maxChunkSize, out minChunkSize);

            var  buffer            = new byte[ReadBufferSize];
            long lastMatchPosition = 0;

            var fileSize = newFileStream.Length;

            ProgressReporter.ReportProgress("Building delta", 0, fileSize);

            while (true)
            {
                var startPosition = newFileStream.Position;
                var read          = newFileStream.Read(buffer, 0, buffer.Length);
                if (read < 0)
                {
                    break;
                }

                var  checksumAlgorithm = signature.RollingChecksumAlgorithm;
                uint checksum          = 0;

                var remainingPossibleChunkSize = maxChunkSize;

                for (var i = 0; i < read - minChunkSize + 1; i++)
                {
                    var readSoFar = startPosition + i;

                    var remainingBytes = read - i;
                    if (remainingBytes < maxChunkSize)
                    {
                        remainingPossibleChunkSize = minChunkSize;
                    }

                    if (i == 0 || remainingBytes < maxChunkSize)
                    {
                        checksum = checksumAlgorithm.Calculate(buffer, i, remainingPossibleChunkSize);
                    }
                    else
                    {
                        var remove = buffer[i - 1];
                        var add    = buffer[i + remainingPossibleChunkSize - 1];
                        checksum = checksumAlgorithm.Rotate(checksum, remove, add, remainingPossibleChunkSize);
                    }

                    ProgressReporter.ReportProgress("Building delta", readSoFar, fileSize);

                    if (readSoFar - (lastMatchPosition - remainingPossibleChunkSize) < remainingPossibleChunkSize)
                    {
                        continue;
                    }

                    if (!chunkMap.ContainsKey(checksum))
                    {
                        continue;
                    }

                    var startIndex = chunkMap[checksum];

                    for (var j = startIndex; j < chunks.Count && chunks[j].RollingChecksum == checksum; j++)
                    {
                        var chunk = chunks[j];

                        var sha = signature.HashAlgorithm.ComputeHash(buffer, i, remainingPossibleChunkSize);

                        if (BinaryComparer.CompareArray(sha, chunks[j].Hash))
                        {
                            readSoFar = readSoFar + remainingPossibleChunkSize;

                            var missing = readSoFar - lastMatchPosition;
                            if (missing > remainingPossibleChunkSize)
                            {
                                deltaWriter.WriteDataCommand(newFileStream, lastMatchPosition, missing - remainingPossibleChunkSize);
                            }

                            deltaWriter.WriteCopyCommand(new DataRange(chunk.StartOffset, chunk.Length));
                            lastMatchPosition = readSoFar;
                            break;
                        }
                    }
                }

                if (read < buffer.Length)
                {
                    break;
                }

                newFileStream.Position = newFileStream.Position - maxChunkSize + 1;
            }

            if (newFileStream.Length != lastMatchPosition)
            {
                deltaWriter.WriteDataCommand(newFileStream, lastMatchPosition, newFileStream.Length - lastMatchPosition);
            }

            deltaWriter.Finish();
        }
        public Signature ReadSignature()
        {
            Progress();
            var header = reader.ReadBytes(BinaryFormat.SignatureHeader.Length);

            if (!BinaryComparer.CompareArray(BinaryFormat.SignatureHeader, header))
            {
                throw new CorruptFileFormatException("The signature file appears to be corrupt.");
            }

            var version = reader.ReadByte();

            if (version != BinaryFormat.Version)
            {
                throw new CorruptFileFormatException("The signature file uses a newer file format than this program can handle.");
            }

            var hashAlgorithm            = reader.ReadString();
            var rollingChecksumAlgorithm = reader.ReadString();

            var endOfMeta = reader.ReadBytes(BinaryFormat.EndOfMetadata.Length);

            if (!BinaryComparer.CompareArray(BinaryFormat.EndOfMetadata, endOfMeta))
            {
                throw new CorruptFileFormatException("The signature file appears to be corrupt.");
            }

            Progress();

            var hashAlgo  = SupportedAlgorithms.Hashing.Create(hashAlgorithm);
            var signature = new Signature(
                hashAlgo,
                SupportedAlgorithms.Checksum.Create(rollingChecksumAlgorithm));

            var  expectedHashLength = hashAlgo.HashLength;
            long start = 0;

            var fileLength     = reader.BaseStream.Length;
            var remainingBytes = fileLength - reader.BaseStream.Position;
            var signatureSize  = sizeof(ushort) + sizeof(uint) + expectedHashLength;

            if (remainingBytes % signatureSize != 0)
            {
                throw new CorruptFileFormatException("The signature file appears to be corrupt; at least one chunk has data missing.");
            }

            while (reader.BaseStream.Position < fileLength - 1)
            {
                var length    = reader.ReadInt16();
                var checksum  = reader.ReadUInt32();
                var chunkHash = reader.ReadBytes(expectedHashLength);

                signature.Chunks.Add(new ChunkSignature
                {
                    StartOffset     = start,
                    Length          = length,
                    RollingChecksum = checksum,
                    Hash            = chunkHash
                });

                start += length;

                Progress();
            }

            return(signature);
        }