| File: System\IO\Compression\dec\BrotliDecoder.cs | Web Access |
| Project: src\runtime\src\libraries\System.IO.Compression.Brotli\src\System.IO.Compression.Brotli.csproj (System.IO.Compression.Brotli) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; namespace System.IO.Compression { /// <summary>Provides non-allocating, performant Brotli decompression methods. The methods decompress in a single pass without using a <see cref="System.IO.Compression.BrotliStream" /> instance.</summary> public struct BrotliDecoder : IDisposable { private SafeBrotliDecoderHandle? _state; private bool _disposed; internal void InitializeDecoder() { _state = Interop.Brotli.BrotliDecoderCreateInstance(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (_state.IsInvalid) throw new IOException(SR.BrotliDecoder_Create); } internal void EnsureInitialized() { EnsureNotDisposed(); if (_state == null) InitializeDecoder(); } /// <summary>Releases all resources used by the current Brotli decoder instance.</summary> public void Dispose() { _disposed = true; _state?.Dispose(); } private void EnsureNotDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(BrotliDecoder), SR.BrotliDecoder_Disposed); } /// <summary>Decompresses data that was compressed using the Brotli algorithm.</summary> /// <param name="source">A buffer containing the compressed data.</param> /// <param name="destination">When this method returns, a byte span containing the decompressed data.</param> /// <param name="bytesConsumed">The total number of bytes that were read from <paramref name="source" />.</param> /// <param name="bytesWritten">The total number of bytes that were written in the <paramref name="destination" />.</param> /// <returns>One of the enumeration values that indicates the status of the decompression operation.</returns> /// <remarks>The return value can be as follows: /// - <see cref="System.Buffers.OperationStatus.Done" />: <paramref name="source" /> was successfully and completely decompressed into <paramref name="destination" />. /// - <see cref="System.Buffers.OperationStatus.DestinationTooSmall" />: There is not enough space in <paramref name="destination" /> to decompress <paramref name="source" />. /// - <see cref="System.Buffers.OperationStatus.NeedMoreData" />: The decompression action is partially done at least one more byte is required to complete the decompression task. This method should be called again with more input to decompress. /// - <see cref="System.Buffers.OperationStatus.InvalidData" />: The data in <paramref name="source" /> is invalid and could not be decompressed.</remarks> public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) { EnsureInitialized(); Debug.Assert(_state != null); bytesConsumed = 0; bytesWritten = 0; if (Interop.Brotli.BrotliDecoderIsFinished(_state) != Interop.BOOL.FALSE) return OperationStatus.Done; nuint availableOutput = (nuint)destination.Length; nuint availableInput = (nuint)source.Length; unsafe { // We can freely cast between int and nuint (.NET size_t equivalent) for two reasons: // 1. Interop Brotli functions will always return an availableInput/Output value lower or equal to the one passed to the function // 2. Span's have a maximum length of the int boundary. while ((int)availableOutput > 0) { fixed (byte* inBytes = &MemoryMarshal.GetReference(source)) fixed (byte* outBytes = &MemoryMarshal.GetReference(destination)) { int brotliResult = Interop.Brotli.BrotliDecoderDecompressStream(_state, ref availableInput, &inBytes, ref availableOutput, &outBytes, out _); if (brotliResult == 0) // Error { return OperationStatus.InvalidData; } Debug.Assert(availableInput <= (nuint)source.Length); Debug.Assert(availableOutput <= (nuint)destination.Length); bytesConsumed += source.Length - (int)availableInput; bytesWritten += destination.Length - (int)availableOutput; switch (brotliResult) { case 1: // Success return OperationStatus.Done; case 3: // NeedsMoreOutput return OperationStatus.DestinationTooSmall; case 2: // NeedsMoreInput default: source = source.Slice(source.Length - (int)availableInput); destination = destination.Slice(destination.Length - (int)availableOutput); if (brotliResult == 2 && source.Length == 0) return OperationStatus.NeedMoreData; break; } } } return OperationStatus.DestinationTooSmall; } } /// <summary>Attempts to decompress data that was compressed with the Brotli algorithm.</summary> /// <param name="source">A buffer containing the compressed data.</param> /// <param name="destination">When this method returns, a byte span containing the decompressed data.</param> /// <param name="bytesWritten">The total number of bytes that were written in the <paramref name="destination" />.</param> /// <returns><see langword="true" /> on success; <see langword="false" /> otherwise.</returns> /// <remarks>If this method returns <see langword="false" />, <paramref name="destination" /> may be empty or contain partially decompressed data, with <paramref name="bytesWritten" /> being zero or greater than zero but less than the expected total.</remarks> public static unsafe bool TryDecompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten) { fixed (byte* inBytes = &MemoryMarshal.GetReference(source)) fixed (byte* outBytes = &MemoryMarshal.GetReference(destination)) { nuint availableOutput = (nuint)destination.Length; bool success = Interop.Brotli.BrotliDecoderDecompress((nuint)source.Length, inBytes, &availableOutput, outBytes) != Interop.BOOL.FALSE; Debug.Assert(success ? availableOutput <= (nuint)destination.Length : availableOutput == 0); bytesWritten = (int)availableOutput; return success; } } } }