Beispiel #1
0
        /// <summary>
        /// Creates a binary patch (in <a href="http://www.daemonology.net/bsdiff/">bsdiff</a> format) that can be used
        /// (by <see cref="ApplyAsync"/>) to transform <paramref name="oldData"/> into <paramref name="newData"/>.
        /// </summary>
        /// <param name="oldData">The original binary data.</param>
        /// <param name="newData">The new binary data.</param>
        /// <param name="output">A <see cref="Stream"/> to which the patch will be written.</param>
        /// <param name="cancellationToken"></param>
        public async Task CreateAsync(ReadOnlyMemory <byte> oldData, ReadOnlyMemory <byte> newData, Stream output, CancellationToken cancellationToken)
        {
            // check arguments
            if (oldData.IsEmpty)
            {
                throw new ArgumentNullException(nameof(oldData));
            }
            if (newData.IsEmpty)
            {
                throw new ArgumentNullException(nameof(newData));
            }
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }
            if (!output.CanSeek)
            {
                throw new ArgumentException("Output stream must be seekable.", nameof(output));
            }
            if (!output.CanWrite)
            {
                throw new ArgumentException("Output stream must be writable.", nameof(output));
            }

            var header = ArrayPool <byte> .Shared.Rent(CHeaderSize);

            var db = ArrayPool <byte> .Shared.Rent(newData.Length);

            var eb = ArrayPool <byte> .Shared.Rent(newData.Length);

            try
            {
                await DiffAsync();
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(header);

                ArrayPool <byte> .Shared.Return(db);

                ArrayPool <byte> .Shared.Return(eb);
            }

            async Task DiffAsync()
            {
                /* Header is
                 *  0   8    "BSDIFF40"
                 *  8   8   length of bzip2ed ctrl block
                 *  16  8   length of bzip2ed diff block
                 *  24  8   length of new file */
                /* File is
                 *  0   32  Header
                 *  32  ??  Bzip2ed ctrl block
                 *  ??  ??  Bzip2ed diff block
                 *  ??  ??  Bzip2ed extra block */

                WriteInt64(CFileSignature, header, 0); // "BSDIFF40"
                WriteInt64(0, header, 8);
                WriteInt64(0, header, 16);
                WriteInt64(newData.Length, header, 24);

                var startPosition = output.Position;
                await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken);

                var I = SuffixSort(oldData);

                var dblen = 0;
                var eblen = 0;

                await using (var wrappingStream = new WrappingStream(output, Ownership.None))
                {
                    await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);

                    // compute the differences, writing ctrl as we go

                    var scan       = 0;
                    var pos        = 0;
                    var len        = 0;
                    var lastscan   = 0;
                    var lastpos    = 0;
                    var lastoffset = 0;

                    while (scan < newData.Length)
                    {
                        var oldscore = 0;

                        for (var scsc = scan += len; scan < newData.Length; scan++)
                        {
                            len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos);

                            for (; scsc < scan + len; scsc++)
                            {
                                if (scsc + lastoffset < oldData.Length && oldData.Span[scsc + lastoffset] == newData.Span[scsc])
                                {
                                    oldscore++;
                                }
                            }

                            if (len == oldscore && len != 0 || len > oldscore + 8)
                            {
                                break;
                            }

                            if (scan + lastoffset < oldData.Length && oldData.Span[scan + lastoffset] == newData.Span[scan])
                            {
                                oldscore--;
                            }
                        }

                        if (len != oldscore || scan == newData.Length)
                        {
                            var s    = 0;
                            var sf   = 0;
                            var lenf = 0;
                            for (var i = 0; lastscan + i < scan && lastpos + i < oldData.Length;)
                            {
                                if (oldData.Span[lastpos + i] == newData.Span[lastscan + i])
                                {
                                    s++;
                                }
                                i++;
                                if (s * 2 - i > sf * 2 - lenf)
                                {
                                    sf   = s;
                                    lenf = i;
                                }
                            }

                            var lenb = 0;
                            if (scan < newData.Length)
                            {
                                s = 0;
                                var sb = 0;
                                for (var i = 1; scan >= lastscan + i && pos >= i; i++)
                                {
                                    if (oldData.Span[pos - i] == newData.Span[scan - i])
                                    {
                                        s++;
                                    }
                                    if (s * 2 - i > sb * 2 - lenb)
                                    {
                                        sb   = s;
                                        lenb = i;
                                    }
                                }
                            }

                            if (lastscan + lenf > scan - lenb)
                            {
                                var overlap = lastscan + lenf - (scan - lenb);
                                s = 0;
                                var ss   = 0;
                                var lens = 0;
                                for (var i = 0; i < overlap; i++)
                                {
                                    if (newData.Span[lastscan + lenf - overlap + i] == oldData.Span[lastpos + lenf - overlap + i])
                                    {
                                        s++;
                                    }
                                    if (newData.Span[scan - lenb + i] == oldData.Span[pos - lenb + i])
                                    {
                                        s--;
                                    }
                                    if (s > ss)
                                    {
                                        ss   = s;
                                        lens = i + 1;
                                    }
                                }

                                lenf += lens - overlap;
                                lenb -= lens;
                            }

                            for (var i = 0; i < lenf; i++)
                            {
                                db[dblen + i] = (byte)(newData.Span[lastscan + i] - oldData.Span[lastpos + i]);
                            }
                            for (var i = 0; i < scan - lenb - (lastscan + lenf); i++)
                            {
                                eb[eblen + i] = newData.Span[lastscan + lenf + i];
                            }

                            dblen += lenf;
                            eblen += scan - lenb - (lastscan + lenf);

                            var buf = ArrayPool <byte> .Shared.Rent(8);

                            try
                            {
                                WriteInt64(lenf, buf, 0);
                                await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken);

                                WriteInt64(scan - lenb - (lastscan + lenf), buf, 0);
                                await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken);

                                WriteInt64(pos - lenb - (lastpos + lenf), buf, 0);
                                await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken);
                            }
                            finally
                            {
                                ArrayPool <byte> .Shared.Return(buf);
                            }

                            lastscan   = scan - lenb;
                            lastpos    = pos - lenb;
                            lastoffset = pos - scan;
                        }
                    }
                }

                // compute size of compressed ctrl data
                var controlEndPosition = output.Position;

                WriteInt64(controlEndPosition - startPosition - CHeaderSize, header, 8);

                // write compressed diff data
                await using (var wrappingStream = new WrappingStream(output, Ownership.None))
                {
                    await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);
                    await bz2Stream.WriteAsync(db.AsMemory(0, dblen), cancellationToken);
                }

                // compute size of compressed diff data
                var diffEndPosition = output.Position;

                WriteInt64(diffEndPosition - controlEndPosition, header, 16);

                // write compressed extra data, if any
                if (eblen > 0)
                {
                    await using var wrappingStream = new WrappingStream(output, Ownership.None);
                    await using var bz2Stream      = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);
                    await bz2Stream.WriteAsync(eb.AsMemory(0, eblen), cancellationToken);
                }

                // seek to the beginning, write the header, then seek back to end
                var endPosition = output.Position;

                output.Position = startPosition;
                await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken);

                output.Position = endPosition;
            }
        }
Beispiel #2
0
        static void CreateInternal(byte[] oldData, byte[] newData, Stream output)
        {
            // check arguments
            if (oldData == null)
            {
                throw new ArgumentNullException("oldData");
            }
            if (newData == null)
            {
                throw new ArgumentNullException("newData");
            }
            if (output == null)
            {
                throw new ArgumentNullException("output");
            }
            if (!output.CanSeek)
            {
                throw new ArgumentException("Output stream must be seekable.", "output");
            }
            if (!output.CanWrite)
            {
                throw new ArgumentException("Output stream must be writable.", "output");
            }

            /* Header is
             *  0   8    "BSDIFF40"
             *  8   8   length of bzip2ed ctrl block
             *  16  8   length of bzip2ed diff block
             *  24  8   length of new file */
            /* File is
             *  0   32  Header
             *  32  ??  Bzip2ed ctrl block
             *  ??  ??  Bzip2ed diff block
             *  ??  ??  Bzip2ed extra block */
            byte[] header = new byte[c_headerSize];
            WriteInt64(c_fileSignature, header, 0); // "BSDIFF40"
            WriteInt64(0, header, 8);
            WriteInt64(0, header, 16);
            WriteInt64(newData.Length, header, 24);

            long startPosition = output.Position;

            output.Write(header, 0, header.Length);

            int[] I = SuffixSort(oldData);

            byte[] db = new byte[newData.Length];
            byte[] eb = new byte[newData.Length];

            int dblen = 0;
            int eblen = 0;

            using (WrappingStream wrappingStream = new WrappingStream(output, Ownership.None))
            {
                using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);
                // compute the differences, writing ctrl as we go
                int scan       = 0;
                int pos        = 0;
                int len        = 0;
                int lastscan   = 0;
                int lastpos    = 0;
                int lastoffset = 0;
                while (scan < newData.Length)
                {
                    int oldscore = 0;

                    for (int scsc = scan += len; scan < newData.Length; scan++)
                    {
                        len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos);

                        for (; scsc < scan + len; scsc++)
                        {
                            if ((scsc + lastoffset < oldData.Length) && (oldData[scsc + lastoffset] == newData[scsc]))
                            {
                                oldscore++;
                            }
                        }

                        if ((len == oldscore && len != 0) || (len > oldscore + 8))
                        {
                            break;
                        }

                        if ((scan + lastoffset < oldData.Length) && (oldData[scan + lastoffset] == newData[scan]))
                        {
                            oldscore--;
                        }
                    }

                    if (len != oldscore || scan == newData.Length)
                    {
                        int s    = 0;
                        int sf   = 0;
                        int lenf = 0;
                        for (int i = 0; (lastscan + i < scan) && (lastpos + i < oldData.Length);)
                        {
                            if (oldData[lastpos + i] == newData[lastscan + i])
                            {
                                s++;
                            }
                            i++;
                            if (s * 2 - i > sf * 2 - lenf)
                            {
                                sf   = s;
                                lenf = i;
                            }
                        }

                        int lenb = 0;
                        if (scan < newData.Length)
                        {
                            s = 0;
                            int sb = 0;
                            for (int i = 1; (scan >= lastscan + i) && (pos >= i); i++)
                            {
                                if (oldData[pos - i] == newData[scan - i])
                                {
                                    s++;
                                }
                                if (s * 2 - i > sb * 2 - lenb)
                                {
                                    sb   = s;
                                    lenb = i;
                                }
                            }
                        }

                        if (lastscan + lenf > scan - lenb)
                        {
                            int overlap = (lastscan + lenf) - (scan - lenb);
                            s = 0;
                            int ss   = 0;
                            int lens = 0;
                            for (int i = 0; i < overlap; i++)
                            {
                                if (newData[lastscan + lenf - overlap + i] == oldData[lastpos + lenf - overlap + i])
                                {
                                    s++;
                                }
                                if (newData[scan - lenb + i] == oldData[pos - lenb + i])
                                {
                                    s--;
                                }
                                if (s > ss)
                                {
                                    ss   = s;
                                    lens = i + 1;
                                }
                            }

                            lenf += lens - overlap;
                            lenb -= lens;
                        }

                        for (int i = 0; i < lenf; i++)
                        {
                            db[dblen + i] = (byte)(newData[lastscan + i] - oldData[lastpos + i]);
                        }
                        for (int i = 0; i < (scan - lenb) - (lastscan + lenf); i++)
                        {
                            eb[eblen + i] = newData[lastscan + lenf + i];
                        }

                        dblen += lenf;
                        eblen += (scan - lenb) - (lastscan + lenf);

                        byte[] buf = new byte[8];
                        WriteInt64(lenf, buf, 0);
                        bz2Stream.Write(buf, 0, 8);

                        WriteInt64((scan - lenb) - (lastscan + lenf), buf, 0);
                        bz2Stream.Write(buf, 0, 8);

                        WriteInt64((pos - lenb) - (lastpos + lenf), buf, 0);
                        bz2Stream.Write(buf, 0, 8);

                        lastscan   = scan - lenb;
                        lastpos    = pos - lenb;
                        lastoffset = pos - scan;
                    }
                }
            }

            // compute size of compressed ctrl data
            long controlEndPosition = output.Position;

            WriteInt64(controlEndPosition - startPosition - c_headerSize, header, 8);

            // write compressed diff data
            using (WrappingStream wrappingStream = new WrappingStream(output, Ownership.None))
            {
                using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);
                bz2Stream.Write(db, 0, dblen);
            }

            // compute size of compressed diff data
            long diffEndPosition = output.Position;

            WriteInt64(diffEndPosition - controlEndPosition, header, 16);

            // write compressed extra data, if any
            if (eblen > 0)
            {
                using WrappingStream wrappingStream = new WrappingStream(output, Ownership.None);
                using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true);
                bz2Stream.Write(eb, 0, eblen);
            }

            // seek to the beginning, write the header, then seek back to end
            long endPosition = output.Position;

            output.Position = startPosition;
            output.Write(header, 0, header.Length);
            output.Position = endPosition;
        }