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