Skip to content

Add support for 7z/LZMA1/LZMA2 to System.IO.Compression #1542

@joshfree

Description

@joshfree

Background and motivation

The goal of this proposal is to expose LZMA/XZ compression and decompression through APIs that follow the established patterns in System.IO.Compression.
The native library will be shipped with #124003

API Proposal

namespace System.IO.Compression
{
  public enum LzmaChecksum
  {
      None = 0,
      Crc32 = 1,
      Crc64 = 2,
      Sha256 = 3,
  }
  public sealed partial class LzmaCompressionOptions
  {
      public LzmaCompressionOptions() { }
      public static int DefaultQuality { get { throw null; } }
      public static int DefaultWindowLog { get { throw null; } }
      public static int MaxQuality { get { throw null; } }
      public static int MaxWindowLog { get { throw null; } }
      public static int MinQuality { get { throw null; } }
      public static int MinWindowLog { get { throw null; } }
      public System.IO.Compression.LzmaChecksum Checksum { get { throw null; } set { } }
      public int WindowLog { get { throw null; } set { } }
      public bool EnableExtreme { get { throw null; } set { } }
      public int Quality { get { throw null; } set { } }
  }
  public sealed partial class LzmaDecoder : System.IDisposable
  {
      public LzmaDecoder() { }
      public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
      public void Dispose() { }
      public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
  }
  public sealed partial class LzmaEncoder : System.IDisposable
  {
      public LzmaEncoder() { }
      public LzmaEncoder(int quality) { }
      public LzmaEncoder(int quality, int windowLog) { }
      public LzmaEncoder(System.IO.Compression.LzmaCompressionOptions compressionOptions) { }
      public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
      public void Dispose() { }
      public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
      public static long GetMaxCompressedLength(long inputLength) { throw null; }
      public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
      public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
  }
  public sealed partial class LzmaStream : System.IO.Stream
  {
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel, bool leaveOpen) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.CompressionMode mode) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.CompressionMode mode, bool leaveOpen) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.LzmaCompressionOptions compressionOptions, bool leaveOpen = false) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.LzmaDecoder decoder, bool leaveOpen = false) { }
      public LzmaStream(System.IO.Stream stream, System.IO.Compression.LzmaEncoder encoder, bool leaveOpen = false) { }
      public System.IO.Stream BaseStream { get { throw null; } }
      public override bool CanRead { get { throw null; } }
      public override bool CanSeek { get { throw null; } }
      public override bool CanWrite { get { throw null; } }
      public override long Length { get { throw null; } }
      public override long Position { get { throw null; } set { } }
      public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; }
      public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; }
      protected override void Dispose(bool disposing) { }
      public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
      public override int EndRead(System.IAsyncResult asyncResult) { throw null; }
      public override void EndWrite(System.IAsyncResult asyncResult) { }
      public override void Flush() { }
      public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
      public override int Read(byte[] buffer, int offset, int count) { throw null; }
      public override int Read(System.Span<byte> buffer) { throw null; }
      public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
      public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
      public override int ReadByte() { throw null; }
      public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
      public override void SetLength(long value) { }
      public override void Write(byte[] buffer, int offset, int count) { }
      public override void Write(System.ReadOnlySpan<byte> buffer) { }
      public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
      public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
      public override void WriteByte(byte value) { }
  }
}

Alternative Designs

  1. Use raw byte values for dictionary/window size instead of log₂ value
    LZMA natively uses raw byte values for dict_size (e.g., 8388608 for 8 MiB), while Zstandard uses log₂ values for windowLog . We chose log₂ to maintain consistency with ZstandardCompressionOptions.WindowLog.

  2. Don't expose EnableExtreme
    The EnableExtreme flag (maps to LZMA_PRESET_EXTREME)provides marginal compression improvement at significant speed cost, and no other compression API in System.IO.Compression exposes such an analogous toggle.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions