1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Buffers; 6 using System.Runtime.InteropServices; 7 using Microsoft.Win32.SafeHandles; 8 using size_t = System.IntPtr; 9 10 namespace System.IO.Compression 11 { 12 public partial struct BrotliEncoder : IDisposable 13 { 14 internal SafeBrotliEncoderHandle _state; 15 private bool _disposed; 16 BrotliEncoderSystem.IO.Compression.BrotliEncoder17 public BrotliEncoder(int quality, int window) 18 { 19 _disposed = false; 20 _state = Interop.Brotli.BrotliEncoderCreateInstance(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 21 if (_state.IsInvalid) 22 throw new IOException(SR.BrotliEncoder_Create); 23 SetQuality(quality); 24 SetWindow(window); 25 } 26 27 /// <summary> 28 /// Performs a lazy initialization of the native encoder using the default Quality and Window values: 29 /// BROTLI_DEFAULT_WINDOW 22 30 /// BROTLI_DEFAULT_QUALITY 11 31 /// </summary> InitializeEncoderSystem.IO.Compression.BrotliEncoder32 internal void InitializeEncoder() 33 { 34 EnsureNotDisposed(); 35 _state = Interop.Brotli.BrotliEncoderCreateInstance(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 36 if (_state.IsInvalid) 37 throw new IOException(SR.BrotliEncoder_Create); 38 } 39 EnsureInitializedSystem.IO.Compression.BrotliEncoder40 internal void EnsureInitialized() 41 { 42 EnsureNotDisposed(); 43 if (_state == null) 44 { 45 InitializeEncoder(); 46 } 47 } 48 DisposeSystem.IO.Compression.BrotliEncoder49 public void Dispose() 50 { 51 _disposed = true; 52 _state?.Dispose(); 53 } 54 EnsureNotDisposedSystem.IO.Compression.BrotliEncoder55 private void EnsureNotDisposed() 56 { 57 if (_disposed) 58 throw new ObjectDisposedException(nameof(BrotliEncoder), SR.BrotliEncoder_Disposed); 59 } 60 SetQualitySystem.IO.Compression.BrotliEncoder61 internal void SetQuality(int quality) 62 { 63 EnsureNotDisposed(); 64 if (_state == null || _state.IsInvalid || _state.IsClosed) 65 { 66 InitializeEncoder(); 67 } 68 if (quality < BrotliUtils.Quality_Min || quality > BrotliUtils.Quality_Max) 69 { 70 throw new ArgumentOutOfRangeException(nameof(quality), SR.Format(SR.BrotliEncoder_Quality, quality, 0, BrotliUtils.Quality_Max)); 71 } 72 if (!Interop.Brotli.BrotliEncoderSetParameter(_state, BrotliEncoderParameter.Quality, (uint)quality)) 73 { 74 throw new InvalidOperationException(SR.Format(SR.BrotliEncoder_InvalidSetParameter, "Quality")); 75 } 76 } 77 SetWindowSystem.IO.Compression.BrotliEncoder78 internal void SetWindow(int window) 79 { 80 EnsureNotDisposed(); 81 if (_state == null || _state.IsInvalid || _state.IsClosed) 82 { 83 InitializeEncoder(); 84 } 85 if (window < BrotliUtils.WindowBits_Min || window > BrotliUtils.WindowBits_Max) 86 { 87 throw new ArgumentOutOfRangeException(nameof(window), SR.Format(SR.BrotliEncoder_Window, window, BrotliUtils.WindowBits_Min, BrotliUtils.WindowBits_Max)); 88 } 89 if (!Interop.Brotli.BrotliEncoderSetParameter(_state, BrotliEncoderParameter.LGWin, (uint)window)) 90 { 91 throw new InvalidOperationException(SR.Format(SR.BrotliEncoder_InvalidSetParameter, "Window")); 92 } 93 } 94 GetMaxCompressedLengthSystem.IO.Compression.BrotliEncoder95 public static int GetMaxCompressedLength(int length) 96 { 97 if (length < 0 || length > BrotliUtils.MaxInputSize) 98 { 99 throw new ArgumentOutOfRangeException(nameof(length)); 100 } 101 if (length == 0) 102 return 1; 103 int numLargeBlocks = length >> 24; 104 int tail = length & 0xFFFFFF; 105 int tailOverhead = (tail > (1 << 20)) ? 4 : 3; 106 int overhead = 2 + (4 * numLargeBlocks) + tailOverhead + 1; 107 int result = length + overhead; 108 return result; 109 } 110 FlushSystem.IO.Compression.BrotliEncoder111 internal OperationStatus Flush(Memory<byte> destination, out int bytesWritten) => Flush(destination.Span, out bytesWritten); 112 FlushSystem.IO.Compression.BrotliEncoder113 public OperationStatus Flush(Span<byte> destination, out int bytesWritten) => Compress(ReadOnlySpan<byte>.Empty, destination, out int bytesConsumed, out bytesWritten, BrotliEncoderOperation.Flush); 114 CompressSystem.IO.Compression.BrotliEncoder115 internal OperationStatus Compress(ReadOnlyMemory<byte> source, Memory<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) => Compress(source.Span, destination.Span, out bytesConsumed, out bytesWritten, isFinalBlock); 116 CompressSystem.IO.Compression.BrotliEncoder117 public OperationStatus Compress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) => Compress(source, destination, out bytesConsumed, out bytesWritten, isFinalBlock ? BrotliEncoderOperation.Finish : BrotliEncoderOperation.Process); 118 CompressSystem.IO.Compression.BrotliEncoder119 internal OperationStatus Compress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten, BrotliEncoderOperation operation) 120 { 121 EnsureInitialized(); 122 bytesWritten = 0; 123 bytesConsumed = 0; 124 size_t availableOutput = (size_t)destination.Length; 125 size_t availableInput = (size_t)source.Length; 126 unsafe 127 { 128 // We can freely cast between int and size_t for two reasons: 129 // 1. Interop Brotli functions will always return an availableInput/Output value lower or equal to the one passed to the function 130 // 2. Span's have a maximum length of the int boundary. 131 while ((int)availableOutput > 0) 132 { 133 fixed (byte* inBytes = &MemoryMarshal.GetReference(source)) 134 fixed (byte* outBytes = &MemoryMarshal.GetReference(destination)) 135 { 136 if (!Interop.Brotli.BrotliEncoderCompressStream(_state, operation, ref availableInput, &inBytes, ref availableOutput, &outBytes, out size_t totalOut)) 137 { 138 return OperationStatus.InvalidData; 139 } 140 bytesConsumed += source.Length - (int)availableInput; 141 bytesWritten += destination.Length - (int)availableOutput; 142 // no bytes written, no remaining input to give to the encoder, and no output in need of retrieving means we are Done 143 if ((int)availableOutput == destination.Length && !Interop.Brotli.BrotliEncoderHasMoreOutput(_state) && (int)availableInput == 0) 144 { 145 return OperationStatus.Done; 146 } 147 148 source = source.Slice(source.Length - (int)availableInput); 149 destination = destination.Slice(destination.Length - (int)availableOutput); 150 } 151 } 152 153 return OperationStatus.DestinationTooSmall; 154 } 155 } 156 TryCompressSystem.IO.Compression.BrotliEncoder157 public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten) => TryCompress(source, destination, out bytesWritten, BrotliUtils.Quality_Default, BrotliUtils.WindowBits_Default); 158 TryCompressSystem.IO.Compression.BrotliEncoder159 public static bool TryCompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int quality, int window) 160 { 161 if (quality < 0 || quality > BrotliUtils.Quality_Max) 162 { 163 throw new ArgumentOutOfRangeException(nameof(quality), SR.Format(SR.BrotliEncoder_Quality, quality, 0, BrotliUtils.Quality_Max)); 164 } 165 if (window < BrotliUtils.WindowBits_Min || window > BrotliUtils.WindowBits_Max) 166 { 167 throw new ArgumentOutOfRangeException(nameof(window), SR.Format(SR.BrotliEncoder_Window, window, BrotliUtils.WindowBits_Min, BrotliUtils.WindowBits_Max)); 168 } 169 unsafe 170 { 171 fixed (byte* inBytes = &MemoryMarshal.GetReference(source)) 172 fixed (byte* outBytes = &MemoryMarshal.GetReference(destination)) 173 { 174 size_t availableOutput = (size_t)destination.Length; 175 bool success = Interop.Brotli.BrotliEncoderCompress(quality, window, /*BrotliEncoderMode*/ 0, (size_t)source.Length, inBytes, ref availableOutput, outBytes); 176 bytesWritten = (int)availableOutput; 177 return success; 178 } 179 } 180 } 181 } 182 } 183