Skip to content

Commit f50c8c6

Browse files
authored
Add LZMA Decompression Support (#11)
* Add lzma decompression support * Fix code quality issues * Fix byte buffer not advancing offset in ReadBytesToSpan * Reduce allocations of compressors when not necessary * Extract decompression to separate class * Dispose of compression streams when done * Fix accessibilty error * Add test * Use try/finally for secondary compressor disposal
1 parent 025dc8f commit f50c8c6

13 files changed

Lines changed: 334 additions & 94 deletions

File tree

src/VCDiff.Tests/ExternDiffTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class ExternDiffTests
2020
[InlineData("sample.xdelta")]
2121
[InlineData("sample_nosmallstr.xdelta")]
2222
[InlineData("sample_appheader.xdelta")]
23+
[InlineData("a-to-b-lzma-compression.xdelta")]
2324

2425
public async Task ExternTest_ImplAsync(string patchfile)
2526
{
@@ -53,6 +54,7 @@ public async Task ExternTest_ImplAsync(string patchfile)
5354
[InlineData("sample.xdelta")]
5455
[InlineData("sample_nosmallstr.xdelta")]
5556
[InlineData("sample_appheader.xdelta")]
57+
[InlineData("a-to-b-lzma-compression.xdelta")]
5658

5759
public void ExternTest_Impl(string patchfile)
5860
{
113 KB
Binary file not shown.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using VCDiff.Includes;
2+
using VCDiff.Shared;
3+
4+
namespace VCDiff.Compressors
5+
{
6+
/// <summary>
7+
/// A compression method for secondary compression, for use when <see cref="VCDiffCodeFlags.VCDDECOMPRESS"/> is enabled in the header and the appropriate <see cref="VCDiffCompressFlags"/> flag is enabled for the section in the window.
8+
/// </summary>
9+
/// <remarks>
10+
/// Implementations are stateful, and a single instance must be used for the entire file for a single operation (compression or decompression).
11+
/// </remarks>
12+
internal interface ICompressor
13+
{
14+
PinnedArrayRental Decompress(WindowSectionType windowSectionType, PinnedArrayRental sectionData);
15+
}
16+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using SharpCompress.Compressors.Xz;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
using VCDiff.Shared;
7+
8+
namespace VCDiff.Compressors
9+
{
10+
internal class XzCompressor : ICompressor, IDisposable
11+
{
12+
public XzCompressor()
13+
{
14+
addRunCompressedBuffer = new();
15+
instructionsCompressedBuffer = new();
16+
addressesCompressedBuffer = new();
17+
18+
addRunDecompressor = new(addRunCompressedBuffer);
19+
instructionsDecompressor = new(instructionsCompressedBuffer);
20+
addressesDecompressor = new(addressesCompressedBuffer);
21+
}
22+
23+
private readonly MemoryStream addRunCompressedBuffer;
24+
private readonly MemoryStream instructionsCompressedBuffer;
25+
private readonly MemoryStream addressesCompressedBuffer;
26+
private readonly XZStream addRunDecompressor;
27+
private readonly XZStream instructionsDecompressor;
28+
private readonly XZStream addressesDecompressor;
29+
30+
public PinnedArrayRental Decompress(WindowSectionType windowSectionType, PinnedArrayRental sectionData)
31+
{
32+
if (sectionData.Data == null)
33+
{
34+
throw new ArgumentException("Cannot decompress null data");
35+
}
36+
37+
MemoryStream memoryStream;
38+
XZStream xzStream;
39+
switch (windowSectionType)
40+
{
41+
case WindowSectionType.AddRunData:
42+
memoryStream = addRunCompressedBuffer;
43+
xzStream = addRunDecompressor;
44+
break;
45+
case WindowSectionType.InstructionsAndSizes:
46+
memoryStream = instructionsCompressedBuffer;
47+
xzStream = instructionsDecompressor;
48+
break;
49+
case WindowSectionType.AddressForCopy:
50+
memoryStream = addressesCompressedBuffer;
51+
xzStream = addressesDecompressor;
52+
break;
53+
default:
54+
throw new ArgumentOutOfRangeException(nameof(windowSectionType));
55+
}
56+
57+
var uncompressedLength = VarIntBE.ParseInt32(sectionData.AsSpan(), out int uncompressedLengthByteCount);
58+
var compressedData = sectionData.AsSpan().Slice(uncompressedLengthByteCount);
59+
60+
// Each section in a window uses the same compression stream throughout the file
61+
// If this is not the first window, reuse the same stream from before, just using different data
62+
memoryStream.SetLength(compressedData.Length);
63+
memoryStream.Position = 0;
64+
memoryStream.Write(compressedData);
65+
memoryStream.Position = 0;
66+
67+
var decompressedData = new PinnedArrayRental(uncompressedLength);
68+
xzStream.ReadExactly(decompressedData.AsSpan());
69+
70+
return decompressedData;
71+
}
72+
public void Dispose()
73+
{
74+
addressesCompressedBuffer?.Dispose();
75+
instructionsCompressedBuffer?.Dispose();
76+
addressesCompressedBuffer?.Dispose();
77+
addRunDecompressor?.Dispose();
78+
instructionsDecompressor?.Dispose();
79+
addressesDecompressor?.Dispose();
80+
}
81+
}
82+
}

src/VCDiff/Decoders/BodyDecoder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.IO;
33
using System.Threading;
44
using System.Threading.Tasks;
5-
using Microsoft.IO;
65
using VCDiff.Includes;
76
using VCDiff.Shared;
87

@@ -20,6 +19,7 @@ internal class BodyDecoder<TWindowDecoderByteBuffer, TSourceBuffer, TDeltaBuffer
2019
private AddressCache addressCache;
2120
private MemoryStream targetData;
2221
private CustomCodeTableDecoder? customTable;
22+
private readonly bool disableChecksums;
2323

2424
//the total bytes decoded
2525
public long TotalBytesDecoded { get; private set; }
@@ -32,7 +32,7 @@ internal class BodyDecoder<TWindowDecoderByteBuffer, TSourceBuffer, TDeltaBuffer
3232
/// <param name="delta">The delta</param>
3333
/// <param name="decodedTarget">the out stream</param>
3434
/// <param name="customTable">custom table if any. Default is null.</param>
35-
public BodyDecoder(WindowDecoder<TWindowDecoderByteBuffer> w, TSourceBuffer source, TDeltaBuffer delta, Stream decodedTarget, CustomCodeTableDecoder? customTable = null)
35+
public BodyDecoder(WindowDecoder<TWindowDecoderByteBuffer> w, TSourceBuffer source, TDeltaBuffer delta, Stream decodedTarget, CustomCodeTableDecoder? customTable = null, bool disableChecksums = false)
3636
{
3737
if (customTable != null)
3838
{
@@ -48,6 +48,7 @@ public BodyDecoder(WindowDecoder<TWindowDecoderByteBuffer> w, TSourceBuffer sour
4848
this.source = source;
4949
this.delta = delta;
5050
this.targetData = Pool.MemoryStreamManager.GetStream(nameof(BodyDecoder<TWindowDecoderByteBuffer, TSourceBuffer, TDeltaBuffer>), (int) w.TargetWindowLength);
51+
this.disableChecksums = disableChecksums;
5152
}
5253

5354
private VCDiffResult DecodeInterleaveCore()
@@ -254,7 +255,7 @@ private VCDiffResult DecodeCore()
254255
{
255256
uint adler = Checksum.ComputeGoogleAdler32(targetData.GetBuffer().AsSpan(0, (int)targetData.Length));
256257

257-
if (adler != window.Checksum)
258+
if (adler != window.Checksum && !disableChecksums)
258259
{
259260
result = VCDiffResult.ERROR;
260261
}
@@ -263,7 +264,7 @@ private VCDiffResult DecodeCore()
263264
{
264265
uint adler = Checksum.ComputeXdelta3Adler32(targetData.GetBuffer().AsSpan(0, (int)targetData.Length));
265266

266-
if (adler != window.Checksum)
267+
if (adler != window.Checksum && !disableChecksums)
267268
{
268269
result = VCDiffResult.ERROR;
269270
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using SharpCompress.Compressors.Xz;
2+
using System.IO;
3+
4+
namespace VCDiff.Decoders
5+
{
6+
internal class SharedDecompressors
7+
{
8+
public XZStream? AddRunDecompressor;
9+
public XZStream? InstructionsDecompressor;
10+
public XZStream? AddressesDecompressor;
11+
12+
public MemoryStream? AddRunCompressedBuffer;
13+
public MemoryStream? InstructionsCompressedBuffer;
14+
public MemoryStream? AddressesCompressedBuffer;
15+
}
16+
}

0 commit comments

Comments
 (0)