diff options
Diffstat (limited to 'lib/Net.Compression/VNLib.Net.Compression')
7 files changed, 446 insertions, 52 deletions
diff --git a/lib/Net.Compression/VNLib.Net.Compression/CompressionExtensions.cs b/lib/Net.Compression/VNLib.Net.Compression/CompressionExtensions.cs new file mode 100644 index 0000000..0a4f7aa --- /dev/null +++ b/lib/Net.Compression/VNLib.Net.Compression/CompressionExtensions.cs @@ -0,0 +1,75 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Compression +* File: CompressionExtensions.cs +* +* CompressionExtensions.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.Buffers; + +using VNLib.Net.Http; + +namespace VNLib.Net.Compression +{ + internal static class CompressionExtensions + { + /// <summary> + /// Compresses a block using the compressor context pointer provided + /// </summary> + /// <param name="nativeLib"></param> + /// <param name="comp">A pointer to the compressor context</param> + /// <param name="output">A buffer to write the result to</param> + /// <param name="input">The input block of memory to compress</param> + /// <param name="finalBlock">A value that indicates if a flush is requested</param> + /// <returns>The results of the compression operation</returns> + public static unsafe CompressionResult CompressBlock(this LibraryWrapper nativeLib, IntPtr comp, Memory<byte> output, ReadOnlyMemory<byte> input, bool finalBlock) + { + //get pointers to the input and output buffers + using MemoryHandle inPtr = input.Pin(); + using MemoryHandle outPtr = output.Pin(); + + //Create the operation struct + CompressionOperation operation; + CompressionOperation* op = &operation; + + op->flush = finalBlock ? 1 : 0; + op->bytesRead = 0; + op->bytesWritten = 0; + + //Configure the input and output buffers + op->inputBuffer = inPtr.Pointer; + op->inputSize = input.Length; + + op->outputBuffer = outPtr.Pointer; + op->outputSize = output.Length; + + //Call the native compress function + nativeLib!.CompressBlock(comp, &operation); + + //Return the number of bytes written + return new() + { + BytesRead = op->bytesRead, + BytesWritten = op->bytesWritten + }; + } + } +}
\ No newline at end of file diff --git a/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs b/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs index 5a86c62..bcc85c9 100644 --- a/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs +++ b/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs @@ -35,10 +35,10 @@ */ using System; -using System.Buffers; using System.Text.Json; using System.Diagnostics; using System.IO.Compression; +using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using VNLib.Net.Http; @@ -46,6 +46,10 @@ using VNLib.Utils.Logging; namespace VNLib.Net.Compression { + + /// <summary> + /// A compressor manager that implements the IHttpCompressorManager interface, for runtime loading. + /// </summary> public sealed class CompressorManager : IHttpCompressorManager { const string NATIVE_LIB_NAME = "vnlib_compress.dll"; @@ -85,7 +89,7 @@ namespace VNLib.Net.Compression log?.Debug("Attempting to load native compression library from: {lib}", libPath); //Load the native library - _nativeLib = LibraryWrapper.LoadLibrary(libPath); + _nativeLib = LibraryWrapper.LoadLibrary(libPath, DllImportSearchPath.SafeDirectories); log?.Debug("Loaded native compression library with compression level {l}", _compLevel.ToString()); } @@ -102,26 +106,17 @@ namespace VNLib.Net.Compression } ///<inheritdoc/> - public object AllocCompressor() - { - return new Compressor(); - } + public object AllocCompressor() => new Compressor(); ///<inheritdoc/> public int InitCompressor(object compressorState, CompressionMethod compMethod) { - //For now do not allow empty compression methods, later we should allow this to be used as a passthrough - if(compMethod == CompressionMethod.None) - { - throw new ArgumentException("Compression method cannot be None", nameof(compMethod)); - } - Compressor compressor = Unsafe.As<Compressor>(compressorState) ?? throw new ArgumentNullException(nameof(compressorState)); //Instance should be null during initialization calls Debug.Assert(compressor.Instance == IntPtr.Zero, "Init was called but and old compressor instance was not properly freed"); - //Alloc the compressor + //Alloc the compressor, let native lib raise exception for supported methods compressor.Instance = _nativeLib!.AllocateCompressor(compMethod, _compLevel); //Return the compressor block size @@ -156,7 +151,7 @@ namespace VNLib.Net.Compression } //Force a flush until no more data is available - CompressionResult result = CompressBlock(compressor.Instance, output, default, true); + CompressionResult result = _nativeLib.CompressBlock(compressor.Instance, output, default, true); return result.BytesWritten; } @@ -171,41 +166,9 @@ namespace VNLib.Net.Compression } //Compress the block - return CompressBlock(compressor.Instance, output, input, false); + return _nativeLib.CompressBlock(compressor.Instance, output, input, false); } - - private unsafe CompressionResult CompressBlock(IntPtr comp, Memory<byte> output, ReadOnlyMemory<byte> input, bool finalBlock) - { - //get pointers to the input and output buffers - using MemoryHandle inPtr = input.Pin(); - using MemoryHandle outPtr = output.Pin(); - - //Create the operation struct - CompressionOperation operation; - CompressionOperation* op = &operation; - - op->flush = finalBlock ? 1 : 0; - op->bytesRead = 0; - op->bytesWritten = 0; - - //Configure the input and output buffers - op->inputBuffer = inPtr.Pointer; - op->inputSize = input.Length; - - op->outputBuffer = outPtr.Pointer; - op->outputSize = output.Length; - - //Call the native compress function - _nativeLib!.CompressBlock(comp, &operation); - - //Return the number of bytes written - return new() - { - BytesRead = op->bytesRead, - BytesWritten = op->bytesWritten - }; - } - + /* * A class to contain the compressor state diff --git a/lib/Net.Compression/VNLib.Net.Compression/INativeCompressionLib.cs b/lib/Net.Compression/VNLib.Net.Compression/INativeCompressionLib.cs new file mode 100644 index 0000000..878dc5c --- /dev/null +++ b/lib/Net.Compression/VNLib.Net.Compression/INativeCompressionLib.cs @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Compression +* File: INativeCompressionLib.cs +* +* INativeCompressionLib.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 VNLib.Net.Http; + +namespace VNLib.Net.Compression +{ + /// <summary> + /// Represents a native compression library that can create native + /// compressor instances. + /// </summary> + public interface INativeCompressionLib + { + /// <summary> + /// Gets the compression methods supported by the underluing library + /// </summary> + /// <returns>The supported compression methods</returns> + CompressionMethod GetSupportedMethods(); + + /// <summary> + /// Allocates a new <see cref="INativeCompressor"/> implementation that allows for + /// compressing stream data. + /// </summary> + /// <param name="method">The desired <see cref="CompressionMethod"/>, must be a supported method</param> + /// <param name="level">The desired <see cref="CompressionLevel"/> to compress blocks with</param> + /// <returns>The new <see cref="INativeCompressor"/></returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="NotSupportedException">The the level or method are not supported by the underlying library</exception> + INativeCompressor AllocCompressor(CompressionMethod method, CompressionLevel level); + + /// <summary> + /// Allocates a safe compressor handle to allow native operations if preferred. + /// </summary> + ///<param name="method">The desired <see cref="CompressionMethod"/>, must be a supported method</param> + /// <param name="level">The desired <see cref="CompressionLevel"/> to compress blocks with</param> + /// <returns>A new <see cref="SafeHandle"/> that holds a pointer to the native compressor context</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="NotSupportedException">The the level or method are not supported by the underlying library</exception> + SafeHandle AllocSafeCompressorHandle(CompressionMethod method, CompressionLevel level); + } +}
\ No newline at end of file diff --git a/lib/Net.Compression/VNLib.Net.Compression/INativeCompressor.cs b/lib/Net.Compression/VNLib.Net.Compression/INativeCompressor.cs new file mode 100644 index 0000000..3455ea0 --- /dev/null +++ b/lib/Net.Compression/VNLib.Net.Compression/INativeCompressor.cs @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Compression +* File: INativeCompressor.cs +* +* INativeCompressor.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 VNLib.Net.Http; + +namespace VNLib.Net.Compression +{ + /// <summary> + /// Represents a native compressor instance + /// </summary> + public interface INativeCompressor : IDisposable + { + /// <summary> + /// Gets the underlying compressor type + /// </summary> + /// <returns>The underlying compressor type</returns> + CompressionMethod GetCompressionMethod(); + + /// <summary> + /// Gets the underlying compressor's compression level + /// </summary> + /// <returns>The configured <see cref="CompressionLevel"/> of the current compressor</returns> + CompressionLevel GetCompressionLevel(); + + /// <summary> + /// Flushes all remaining data in the compressor to the output buffer + /// </summary> + /// <param name="buffer">The output buffer to write flushed compressor data to</param> + /// <returns>The number of bytes written to the output buffer</returns> + int Flush(Memory<byte> buffer); + + /// <summary> + /// Compresses the input block and writes the compressed data to the output block + /// </summary> + /// <param name="input">The input buffer to compress</param> + /// <param name="bytesRead">The number of bytes read by the compressor</param> + /// <param name="output">The output buffer to write compressed data to</param> + /// <param name="bytesWritten">The number of bytes written to the output buffer</param> + CompressionResult Compress(ReadOnlyMemory<byte> input, Memory<byte> output); + + /// <summary> + /// Gets the compressor block size if configured + /// </summary> + /// <returns>The ideal input buffer size for compressing blocks, or <![CDATA[<1]]> if block size is unlimited</returns> + int GetBlockSize(); + + /// <summary> + /// Determines the maximum number of output bytes for the given number of input bytes + /// specified by the size parameter + /// </summary> + /// <param name="size">The number of bytes to get the compressed size of</param> + /// <returns>The maxium size of the compressed data</returns> + int GetCompressedSize(int size); + } +}
\ No newline at end of file diff --git a/lib/Net.Compression/VNLib.Net.Compression/LibraryWrapper.cs b/lib/Net.Compression/VNLib.Net.Compression/LibraryWrapper.cs index 0f86107..2ee6018 100644 --- a/lib/Net.Compression/VNLib.Net.Compression/LibraryWrapper.cs +++ b/lib/Net.Compression/VNLib.Net.Compression/LibraryWrapper.cs @@ -85,10 +85,10 @@ namespace VNLib.Net.Compression /// and used for the lifetime of the application. /// </para> /// </summary> - internal sealed class LibraryWrapper + internal sealed class LibraryWrapper { private readonly SafeLibraryHandle _lib; - private readonly MethodTable _methodTable; + private MethodTable _methodTable; public string LibFilePath { get; } @@ -104,10 +104,10 @@ namespace VNLib.Net.Compression /// </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) + public static LibraryWrapper LoadLibrary(string filePath, DllImportSearchPath searchType) { //Load the library into the current process - SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(filePath, DllImportSearchPath.SafeDirectories); + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(filePath, searchType); try { @@ -223,6 +223,14 @@ namespace VNLib.Net.Compression } /// <summary> + /// Frees the specified compressor instance without raising exceptions + /// </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> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int FreeSafeCompressor(IntPtr compressor) => _methodTable.Free(compressor); + + /// <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> @@ -255,6 +263,23 @@ namespace VNLib.Net.Compression return result; } + ///<inheritdoc/> + ~LibraryWrapper() + { + _methodTable = default; + _lib.Dispose(); + } + + /// <summary> + /// Manually releases the library + /// </summary> + internal void ManualRelease() + { + _methodTable = default; + _lib.Dispose(); + GC.SuppressFinalize(this); + } + private readonly struct MethodTable { public GetSupportedMethodsDelegate GetMethods { get; init; } diff --git a/lib/Net.Compression/VNLib.Net.Compression/NativeCompressionLib.cs b/lib/Net.Compression/VNLib.Net.Compression/NativeCompressionLib.cs new file mode 100644 index 0000000..f5b9071 --- /dev/null +++ b/lib/Net.Compression/VNLib.Net.Compression/NativeCompressionLib.cs @@ -0,0 +1,141 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Compression +* File: NativeCompressionLib.cs +* +* NativeCompressionLib.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 VNLib.Net.Http; +using VNLib.Utils; +using VNLib.Utils.Extensions; + +namespace VNLib.Net.Compression +{ + /// <summary> + /// A referrence native compression library implementation. Allows for creating compressor instances + /// from a native dll. + /// </summary> + public sealed class NativeCompressionLib : VnDisposeable, INativeCompressionLib + { + private readonly LibraryWrapper _library; + + private NativeCompressionLib(LibraryWrapper nativeLib) => _library = nativeLib; + + ///<inheritdoc/> + protected override void Free() => _library.ManualRelease(); + + /// <summary> + /// Loads the native compression DLL at the specified file path and search pattern + /// </summary> + /// <param name="libPath">The path (relative or absolute) path to the native dll to load</param> + /// <param name="searchPath">The dll search pattern</param> + /// <returns>A new <see cref="NativeCompressionLib"/> library handle</returns> + public static NativeCompressionLib LoadLibrary(string libPath, DllImportSearchPath searchPath) + { + LibraryWrapper wrapper = LibraryWrapper.LoadLibrary(libPath, searchPath); + return new NativeCompressionLib(wrapper); + } + + ///<inheritdoc/> + ///<exception cref="NativeCompressionException"></exception> + public CompressionMethod GetSupportedMethods() => _library.GetSupportedMethods(); + + ///<inheritdoc/> + ///<exception cref="NativeCompressionException"></exception> + public INativeCompressor AllocCompressor(CompressionMethod method, CompressionLevel level) + { +#pragma warning disable CA2000 // Dispose objects before losing scope + + SafeHandle libHandle = AllocSafeCompressorHandle(method, level); + return new Compressor(_library, libHandle); + +#pragma warning restore CA2000 // Dispose objects before losing scope + } + + ///<inheritdoc/> + ///<exception cref="NativeCompressionException"></exception> + public SafeHandle AllocSafeCompressorHandle(CompressionMethod method, CompressionLevel level) + { + //Alloc compressor then craete a safe handle + IntPtr comp = _library.AllocateCompressor(method, level); + return new SafeCompressorHandle(_library, comp); + } + + internal sealed record class Compressor(LibraryWrapper LibComp, SafeHandle Handle) : INativeCompressor + { + + ///<inheritdoc/> + public CompressionResult Compress(ReadOnlyMemory<byte> input, Memory<byte> output) + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + return LibComp.CompressBlock(compressor, output, input, false); + } + + ///<inheritdoc/> + public void Dispose() => Handle.Dispose(); + + ///<inheritdoc/> + public int Flush(Memory<byte> buffer) + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + CompressionResult result = LibComp.CompressBlock(compressor, buffer, ReadOnlyMemory<byte>.Empty, true); + return result.BytesWritten; + } + + ///<inheritdoc/> + public int GetBlockSize() + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + return LibComp.GetBlockSize(compressor); + } + + ///<inheritdoc/> + public int GetCompressedSize(int size) + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + return LibComp.GetOutputSize(compressor, size, 1); + } + + ///<inheritdoc/> + public CompressionLevel GetCompressionLevel() + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + return LibComp.GetCompressorLevel(compressor); + } + + ///<inheritdoc/> + public CompressionMethod GetCompressionMethod() + { + Handle.ThrowIfClosed(); + IntPtr compressor = Handle.DangerousGetHandle(); + return LibComp.GetCompressorType(compressor); + } + } + } +}
\ No newline at end of file diff --git a/lib/Net.Compression/VNLib.Net.Compression/SafeCompressorHandle.cs b/lib/Net.Compression/VNLib.Net.Compression/SafeCompressorHandle.cs new file mode 100644 index 0000000..4c21b6b --- /dev/null +++ b/lib/Net.Compression/VNLib.Net.Compression/SafeCompressorHandle.cs @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Compression +* File: SafeCompressorHandle.cs +* +* SafeCompressorHandle.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 Microsoft.Win32.SafeHandles; + +namespace VNLib.Net.Compression +{ + internal sealed class SafeCompressorHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly LibraryWrapper _library; + + internal SafeCompressorHandle(LibraryWrapper libComp, IntPtr compressor): base(true) + { + _library = libComp; + SetHandle(compressor); + } + + ///<inheritdoc/> + protected override bool ReleaseHandle() => _library.FreeSafeCompressor(handle) > 0; + } +}
\ No newline at end of file |