Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Brotli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,27 @@ internal static unsafe partial BOOL BrotliEncoderCompressStream(

[LibraryImport(Libraries.CompressionNative)]
internal static unsafe partial BOOL BrotliEncoderCompress(int quality, int window, int v, nuint availableInput, byte* inBytes, nuint* availableOutput, byte* outBytes);

internal enum BrotliSharedDictionaryType
{
// Raw LZ77 prefix dictionary.
RAW = 0
}

[LibraryImport(Libraries.CompressionNative)]
internal static unsafe partial SafeBrotliPreparedDictionaryHandle BrotliEncoderPrepareDictionary(
BrotliSharedDictionaryType type, nuint size, byte* data, int quality,
IntPtr allocFunc, IntPtr freeFunc, IntPtr opaque);

[LibraryImport(Libraries.CompressionNative)]
internal static partial BOOL BrotliEncoderAttachPreparedDictionary(
SafeBrotliEncoderHandle state, SafeBrotliPreparedDictionaryHandle preparedDictionary);

[LibraryImport(Libraries.CompressionNative)]
internal static unsafe partial BOOL BrotliDecoderAttachDictionary(
SafeBrotliDecoderHandle state, BrotliSharedDictionaryType type, nuint size, byte* data);

[LibraryImport(Libraries.CompressionNative)]
internal static partial void BrotliEncoderDestroyPreparedDictionary(IntPtr dictionary);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,35 @@ protected override bool ReleaseHandle()

public override bool IsInvalid => handle == IntPtr.Zero;
}

internal sealed class SafeBrotliPreparedDictionaryHandle : SafeHandle
{
internal IntPtr _dictionaryBytes;
internal int _dictionaryLength;

public int DictionaryLength => _dictionaryLength;
public unsafe byte* DictionaryBytes => (byte*)_dictionaryBytes.ToPointer();

public SafeBrotliPreparedDictionaryHandle() : base(IntPtr.Zero, true) { }

public void SetDictionaryBytes(IntPtr dictionaryBytes, int dictionaryLength)
{
_dictionaryBytes = dictionaryBytes;
_dictionaryLength = dictionaryLength;
}

protected override bool ReleaseHandle()
{
Interop.Brotli.BrotliEncoderDestroyPreparedDictionary(handle);
unsafe
{
NativeMemory.Free(_dictionaryBytes.ToPointer());
}
_dictionaryBytes = IntPtr.Zero;
_dictionaryLength = 0;
return true;
}

public override bool IsInvalid => handle == IntPtr.Zero;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,24 @@ public partial struct BrotliDecoder : System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public void AttachDictionary(System.IO.Compression.BrotliDictionary dictionary) { }
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 BrotliDictionary : System.IDisposable
{
internal BrotliDictionary() { }
public static System.IO.Compression.BrotliDictionary CreateFromBuffer(System.ReadOnlySpan<byte> buffer) { throw null; }
public static System.IO.Compression.BrotliDictionary CreateFromBuffer(System.ReadOnlySpan<byte> buffer, int quality) { throw null; }
public void Dispose() { }
}
public partial struct BrotliEncoder : System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public BrotliEncoder(int quality, int window) { throw null; }
public void AttachDictionary(System.IO.Compression.BrotliDictionary dictionary) { }
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; }
Expand All @@ -44,6 +53,7 @@ public BrotliStream(System.IO.Stream stream, System.IO.Compression.CompressionMo
public override bool CanWrite { get { throw null; } }
public override long Length { get { throw null; } }
public override long Position { get { throw null; } set { } }
public void AttachDictionary(System.IO.Compression.BrotliDictionary dictionary) { }
public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? asyncCallback, object? asyncState) { throw null; }
protected override void Dispose(bool disposing) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,17 @@
<data name="IOCompressionBrotli_PlatformNotSupported" xml:space="preserve">
<value>System.IO.Compression.Brotli is not supported on this platform.</value>
</data>
<!-- BrotliDictionary -->
<data name="BrotliDictionary_Create" xml:space="preserve">
<value>Failed to create Brotli dictionary.</value>
</data>
<data name="BrotliDictionary_EmptyBuffer" xml:space="preserve">
<value>Dictionary buffer cannot be empty.</value>
</data>
<data name="BrotliEncoder_AttachDictionary" xml:space="preserve">
<value>Failed to attach dictionary to Brotli encoder.</value>
</data>
<data name="BrotliDecoder_AttachDictionary" xml:space="preserve">
<value>Failed to attach dictionary to Brotli decoder.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<Compile Include="System\IO\Compression\enc\BrotliEncoderOperation.cs" />
<Compile Include="System\IO\Compression\enc\BrotliEncoderParameter.cs" />
<Compile Include="System\IO\Compression\BrotliStream.cs" />
<Compile Include="System\IO\Compression\BrotliDictionary.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBrotliHandle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeBrotliHandle.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace System.IO.Compression
{
/// <summary>
/// Represents a Brotli dictionary used for compression and decompression.
/// </summary>
public sealed class BrotliDictionary : IDisposable
{
private readonly SafeBrotliPreparedDictionaryHandle _preparedDictionary;
private bool _disposed;

private BrotliDictionary(SafeBrotliPreparedDictionaryHandle preparedDictionary)
{
_preparedDictionary = preparedDictionary ?? throw new ArgumentNullException(nameof(preparedDictionary));
_disposed = false;
}

/// <summary>
/// Creates a new <see cref="BrotliDictionary"/> from a buffer.
/// </summary>
/// <param name="buffer">The buffer containing the dictionary data.</param>
/// <returns>A new <see cref="BrotliDictionary"/> instance.</returns>
/// <exception cref="ArgumentException">Thrown when the buffer is empty.</exception>
public static BrotliDictionary CreateFromBuffer(ReadOnlySpan<byte> buffer) => CreateFromBuffer(buffer, BrotliUtils.Quality_Max);

/// <summary>
/// Creates a new prepared <see cref="BrotliDictionary"/> from a buffer for use with an encoder.
/// </summary>
/// <param name="buffer">The buffer containing the dictionary data.</param>
/// <param name="quality">The quality level used for preparing the dictionary.</param>
/// <returns>A new <see cref="BrotliDictionary"/> instance.</returns>
/// <exception cref="ArgumentException">Thrown when the buffer is empty.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when quality is not between 0 and 11.</exception>
public static BrotliDictionary CreateFromBuffer(ReadOnlySpan<byte> buffer, int quality)
{
if (buffer.IsEmpty)
{
throw new ArgumentException(SR.BrotliDictionary_EmptyBuffer, nameof(buffer));
}

if (quality < BrotliUtils.Quality_Min || quality > BrotliUtils.Quality_Max)
{
throw new ArgumentOutOfRangeException(nameof(quality), SR.Format(SR.BrotliEncoder_Quality, quality, BrotliUtils.Quality_Min, BrotliUtils.Quality_Max));
}

SafeBrotliPreparedDictionaryHandle? preparedDictionary;

unsafe
{
// BrotliPreparedDictionary references the memory used to create the dictionary,
// so we make a copy of it on the native heap.

IntPtr nativeMemory = (IntPtr)NativeMemory.Alloc((nuint)buffer.Length, 1);
buffer.CopyTo(new Span<byte>(nativeMemory.ToPointer(), buffer.Length));

preparedDictionary = Interop.Brotli.BrotliEncoderPrepareDictionary(
Interop.Brotli.BrotliSharedDictionaryType.RAW,
(nuint)buffer.Length,
(byte*)nativeMemory.ToPointer(),
quality,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);

if (preparedDictionary == null || preparedDictionary.IsInvalid)
{
NativeMemory.Free(nativeMemory.ToPointer());
throw new IOException(SR.BrotliDictionary_Create);
}

preparedDictionary.SetDictionaryBytes(nativeMemory, buffer.Length);
}

return new BrotliDictionary(preparedDictionary);
}

internal bool AttachToEncoder(SafeBrotliEncoderHandle encoderHandle)
{
ObjectDisposedException.ThrowIf(_disposed, this);

return Interop.Brotli.BrotliEncoderAttachPreparedDictionary(encoderHandle, _preparedDictionary) != Interop.BOOL.FALSE;
}

internal bool AttachToDecoder(SafeBrotliDecoderHandle decoderHandle)
{
ObjectDisposedException.ThrowIf(_disposed, this);

unsafe
{
return Interop.Brotli.BrotliDecoderAttachDictionary(
decoderHandle,
Interop.Brotli.BrotliSharedDictionaryType.RAW,
(nuint)_preparedDictionary.DictionaryLength,
_preparedDictionary.DictionaryBytes) != Interop.BOOL.FALSE;
}
}

/// <summary>
/// Releases all resources used by the current instance of the <see cref="BrotliDictionary"/> class.
/// </summary>
public void Dispose()
{
if (!_disposed)
{
_preparedDictionary.Dispose();
_disposed = true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ public BrotliStream(Stream stream, CompressionMode mode, bool leaveOpen)
_buffer = ArrayPool<byte>.Shared.Rent(DefaultInternalBufferSize);
}

public void AttachDictionary(BrotliDictionary dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);

EnsureNotDisposed();

if (_mode == CompressionMode.Compress)
{
_encoder.AttachDictionary(dictionary);
}
else if (_mode == CompressionMode.Decompress)
{
_decoder.AttachDictionary(dictionary);
}
}

private void EnsureNotDisposed()
{
ObjectDisposedException.ThrowIf(_stream is null, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

Expand All @@ -21,11 +22,27 @@ internal void InitializeDecoder()
throw new IOException(SR.BrotliDecoder_Create);
}

/// <summary>
/// Attaches a Brotli dictionary to the decoder.
/// </summary>
/// <param name="dictionary">The Brotli dictionary to attach.</param>
public void AttachDictionary(BrotliDictionary dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);

EnsureInitialized();

dictionary.AttachToDecoder(_state);
}

[MemberNotNull(nameof(_state))]
internal void EnsureInitialized()
{
EnsureNotDisposed();
if (_state == null)
InitializeDecoder();

Debug.Assert(_state != null && !_state.IsInvalid && !_state.IsClosed);
}

/// <summary>Releases all resources used by the current Brotli decoder instance.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

Expand Down Expand Up @@ -31,6 +32,19 @@ public BrotliEncoder(int quality, int window)
SetWindow(window);
}

/// <summary>
/// Attaches a Brotli dictionary to the encoder.
/// </summary>
/// <param name="dictionary">The Brotli dictionary to attach.</param>
public void AttachDictionary(BrotliDictionary dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);

EnsureInitialized();

dictionary.AttachToEncoder(_state);
}

/// <summary>
/// Performs a lazy initialization of the native encoder using the default Quality and Window values:
/// BROTLI_DEFAULT_WINDOW 22
Expand All @@ -44,13 +58,16 @@ internal void InitializeEncoder()
throw new IOException(SR.BrotliEncoder_Create);
}

[MemberNotNull(nameof(_state))]
internal void EnsureInitialized()
{
EnsureNotDisposed();
if (_state == null)
{
InitializeEncoder();
}

Debug.Assert(_state != null && !_state.IsInvalid && !_state.IsClosed);
}

/// <summary>Frees and disposes unmanaged resources.</summary>
Expand Down
Loading
Loading