aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Compression/VNLib.Net.Compression/LibraryWrapper.cs
blob: 0f861075f5174a619668c1d1f2e4a0f85f80bafd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
* Copyright (c) 2023 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Net.Compression
* File: LibraryWrapper.cs 
*
* LibraryWrapper.cs is part of VNLib.Net.Compression which is part of 
* the larger VNLib collection of libraries and utilities.
*
* VNLib.Net.Compression is free software: you can redistribute it and/or modify 
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Net.Compression is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License 
* along with VNLib.Net.Compression. If not, see http://www.gnu.org/licenses/.
*/


using System;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

using VNLib.Utils;
using VNLib.Utils.Native;
using VNLib.Utils.Extensions;

using VNLib.Net.Http;

namespace VNLib.Net.Compression
{
    /*
     * Configure the delegate methods for the native library
     * 
     * All calling conventions are set to Cdecl because the native 
     * library is compiled with Cdecl on all platforms.
     */

    [SafeMethodName("GetSupportedCompressors")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate CompressionMethod GetSupportedMethodsDelegate();

    [SafeMethodName("GetCompressorBlockSize")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate int GetBlockSizeDelegate(IntPtr compressor);

    [SafeMethodName("GetCompressorType")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate CompressionMethod GetCompressorTypeDelegate(IntPtr compressor);

    [SafeMethodName("GetCompressorLevel")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate CompressionLevel GetCompressorLevelDelegate(IntPtr compressor);

    [SafeMethodName("AllocateCompressor")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate IntPtr AllocateCompressorDelegate(CompressionMethod type, CompressionLevel level);

    [SafeMethodName("FreeCompressor")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate int FreeCompressorDelegate(IntPtr compressor);

    [SafeMethodName("GetCompressedSize")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate int GetCompressedSizeDelegate(IntPtr compressor, int uncompressedSize, int flush);

    [SafeMethodName("CompressBlock")]
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    unsafe delegate int CompressBlockDelegate(IntPtr compressor, CompressionOperation* operation);

    /// <summary>
    /// <para>
    /// Represents a wrapper that provides access to the native compression library
    /// specified by a file path. 
    /// </para>
    /// <para>
    /// NOTE: This library is not meant to be freed, its meant to be loaded at runtime
    /// and used for the lifetime of the application.
    /// </para>
    /// </summary>
    internal sealed class LibraryWrapper
    {
        private readonly SafeLibraryHandle _lib;
        private readonly MethodTable _methodTable;

        public string LibFilePath { get; }

        private LibraryWrapper(SafeLibraryHandle lib, string path, in MethodTable methodTable)
        {
            _lib =  lib;
            _methodTable = methodTable;
            LibFilePath = path;
        }

        /// <summary>
        /// Loads the native library at the specified path into the current process
        /// </summary>
        /// <param name="filePath">The path to the native library to load</param>
        /// <returns>The native library wrapper</returns>
        public static LibraryWrapper LoadLibrary(string filePath)
        {
            //Load the library into the current process
            SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(filePath, DllImportSearchPath.SafeDirectories);
            
            try
            {
                //build the method table
                MethodTable methods = new()
                {
                    GetMethods = lib.DangerousGetMethod<GetSupportedMethodsDelegate>(),

                    GetBlockSize = lib.DangerousGetMethod<GetBlockSizeDelegate>(),

                    GetCompType = lib.DangerousGetMethod<GetCompressorTypeDelegate>(),

                    GetCompLevel = lib.DangerousGetMethod<GetCompressorLevelDelegate>(),

                    Alloc = lib.DangerousGetMethod<AllocateCompressorDelegate>(),

                    Free = lib.DangerousGetMethod<FreeCompressorDelegate>(),

                    GetOutputSize = lib.DangerousGetMethod<GetCompressedSizeDelegate>(),

                    Compress = lib.DangerousGetMethod<CompressBlockDelegate>()
                };

                return new (lib, filePath, in methods);
            }
            catch
            {
                lib.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Gets an enum value of the supported compression methods by the underlying library
        /// </summary>
        /// <returns>The supported compression methods</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public CompressionMethod GetSupportedMethods() => _methodTable.GetMethods();

        /// <summary>
        /// Gets the block size of the specified compressor
        /// </summary>
        /// <param name="compressor">A pointer to the compressor instance </param>
        /// <returns>A integer value of the compressor block size</returns>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetBlockSize(IntPtr compressor)
        {
            int result = _methodTable.GetBlockSize(compressor);
            ThrowHelper.ThrowIfError((ERRNO)result);
            return result;
        }

        /// <summary>
        /// Gets the compressor type of the specified compressor
        /// </summary>
        /// <param name="compressor">A pointer to the compressor instance</param>
        /// <returns>A enum value that represents the compressor type</returns>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public CompressionMethod GetCompressorType(IntPtr compressor)
        {
            CompressionMethod result = _methodTable.GetCompType(compressor);
            ThrowHelper.ThrowIfError((int)result);
            return result;
        }

        /// <summary>
        /// Gets the compression level of the specified compressor
        /// </summary>
        /// <param name="compressor">A pointer to the compressor instance</param>
        /// <returns>The <see cref="CompressionLevel"/> of the current compressor</returns>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public CompressionLevel GetCompressorLevel(IntPtr compressor)
        {
            CompressionLevel result = _methodTable.GetCompLevel(compressor);
            ThrowHelper.ThrowIfError((int)result);
            return result;
        }

        /// <summary>
        /// Allocates a new compressor instance of the specified type and compression level
        /// </summary>
        /// <param name="type">The compressor type to allocate</param>
        /// <param name="level">The desired compression level</param>
        /// <returns>A pointer to the newly allocated compressor instance</returns>
        /// <exception cref="NotSupportedException"></exception>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public IntPtr AllocateCompressor(CompressionMethod type, CompressionLevel level)
        {
            IntPtr result = _methodTable.Alloc(type, level);
            ThrowHelper.ThrowIfError(result);
            return result;
        }

        /// <summary>
        /// Frees the specified compressor instance
        /// </summary>
        /// <param name="compressor">A pointer to the valid compressor instance to free</param>
        /// <returns>A value indicating the result of the free operation</returns>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void FreeCompressor(IntPtr compressor)
        {
            int result = _methodTable.Free(compressor);
            ThrowHelper.ThrowIfError(result);
            if(result == 0)
            {
                throw new NativeCompressionException("Failed to free the compressor instance");
            }
        }

        /// <summary>
        /// Determines the output size of a given input size and flush mode for the specified compressor
        /// </summary>
        /// <param name="compressor">A pointer to the compressor instance</param>
        /// <param name="inputSize">The size of the input block to compress</param>
        /// <param name="flush">A value that specifies a flush operation</param>
        /// <returns>Returns the size of the required output buffer</returns>
        /// <exception cref="NotSupportedException"></exception>
        /// <exception cref="NativeCompressionException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetOutputSize(IntPtr compressor, int inputSize, int flush)
        {
            int result = _methodTable.GetOutputSize(compressor, inputSize, flush);
            ThrowHelper.ThrowIfError(result);
            return result;
        }

        /// <summary>
        /// Compresses a block of data using the specified compressor instance
        /// </summary>
        /// <param name="compressor">The compressor instance used to compress data</param>
        /// <param name="operation">A pointer to the compression operation structure</param>
        /// <returns>The result of the operation</returns>
        /// <exception cref="NotSupportedException"></exception>
        /// <exception cref="NativeLibraryException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe int CompressBlock(IntPtr compressor, CompressionOperation* operation)
        {
            int result = _methodTable.Compress(compressor, operation);
            ThrowHelper.ThrowIfError(result);
            return result;
        }

        private readonly struct MethodTable
        {
            public GetSupportedMethodsDelegate GetMethods { get; init; }

            public GetBlockSizeDelegate GetBlockSize { get; init; }

            public GetCompressorTypeDelegate GetCompType { get; init; }

            public GetCompressorLevelDelegate GetCompLevel { get; init; }

            public AllocateCompressorDelegate Alloc { get; init; }

            public FreeCompressorDelegate Free { get; init; }

            public GetCompressedSizeDelegate GetOutputSize { get; init; }

            public CompressBlockDelegate Compress { get; init; }
        }
    }
}