diff options
Diffstat (limited to 'lib')
11 files changed, 596 insertions, 121 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 diff --git a/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs b/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs index f77b3d2..69672c8 100644 --- a/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs +++ b/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs @@ -7,6 +7,7 @@ using System.IO.Compression; using System.Security.Cryptography; using VNLib.Utils.IO; +using VNLib.Utils.Memory; using VNLib.Net.Http; using VNLib.Utils.Extensions; @@ -18,66 +19,42 @@ namespace VNLib.Net.Compression.Tests public class CompressorManagerTests { const string LIB_PATH = @"../../../../vnlib_compress/build/Debug/vnlib_compress.dll"; - - [TestMethod()] - public void CompressDataStreamTest() + [TestMethod] + public void NativeLibApiTest() { - CompressorManager manager = InitCompressorUnderTest(); + //Load library + using NativeCompressionLib lib = NativeCompressionLib.LoadLibrary(LIB_PATH, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories); - //Allocate a compressor instance - object? compressor = manager.AllocCompressor(); + LibTestComp cp = new(lib, CompressionLevel.Fastest); - Assert.IsNotNull(compressor); + TestSupportedMethods(cp); - //Test all 3 compression methods - TestCompressorMethod(manager, compressor, CompressionMethod.Brotli); - TestCompressorMethod(manager, compressor, CompressionMethod.Gzip); - TestCompressorMethod(manager, compressor, CompressionMethod.Deflate); - } + //Test for supported methods + TestCompressionForSupportedMethods(cp); + } [TestMethod()] public void InitCompressorTest() { CompressorManager manager = InitCompressorUnderTest(); - //Allocate a compressor instance - object compressor = manager.AllocCompressor(); - - Assert.IsNotNull(compressor); - Assert.ThrowsException<ArgumentNullException>(() => manager.InitCompressor(null!, CompressionMethod.Deflate)); Assert.ThrowsException<ArgumentNullException>(() => manager.DeinitCompressor(null!)); - //Make sure error occurs with non-supported comp - Assert.ThrowsException<ArgumentException>(() => { manager.InitCompressor(compressor, CompressionMethod.None); }); - - //test out of range, this should be a native lib error - Assert.ThrowsException<NotSupportedException>(() => { manager.InitCompressor(compressor, (CompressionMethod)24); }); + //Allocate a compressor instance + object compressor = manager.AllocCompressor(); - //Test all 3 compression methods - CompressionMethod supported = manager.GetSupportedMethods(); + Assert.IsNotNull(compressor); - if ((supported & CompressionMethod.Gzip) > 0) - { - //Make sure no error occurs with supported comp - manager.InitCompressor(compressor, CompressionMethod.Gzip); - manager.DeinitCompressor(compressor); - } + //Create a new testing wrapper + ManagerTestComp cp = new(compressor, manager); - if((supported & CompressionMethod.Brotli) > 0) - { - //Make sure no error occurs with supported comp - manager.InitCompressor(compressor, CompressionMethod.Brotli); - manager.DeinitCompressor(compressor); - } + //Test supported methods + TestSupportedMethods(cp); - if((supported & CompressionMethod.Deflate) > 0) - { - //Make sure no error occurs with supported comp - manager.InitCompressor(compressor, CompressionMethod.Deflate); - manager.DeinitCompressor(compressor); - } + //Test for supported methods + TestCompressionForSupportedMethods(cp); } private static CompressorManager InitCompressorUnderTest() @@ -92,12 +69,6 @@ namespace VNLib.Net.Compression.Tests //Attempt to load the native library manager.OnLoad(null, doc.RootElement); - //Get supported methods - CompressionMethod methods = manager.GetSupportedMethods(); - - //Verify that at least one method is supported - Assert.IsFalse(methods == CompressionMethod.None); - return manager; } @@ -119,8 +90,69 @@ namespace VNLib.Net.Compression.Tests return Encoding.UTF8.GetString(ms.AsSpan()); } + + + private static void TestCompressionForSupportedMethods(ITestCompressor testCompressor) + { + //Get the compressor's supported methods + CompressionMethod methods = testCompressor.GetSupportedMethods(); + + //Make sure at least on method is supported by the native lib + Assert.IsFalse(methods == CompressionMethod.None); + + //Test for brotli support + if ((methods & CompressionMethod.Brotli) > 0) + { + TestCompressorMethod(testCompressor, CompressionMethod.Brotli); + } + + //Test for deflate support + if ((methods & CompressionMethod.Deflate) > 0) + { + TestCompressorMethod(testCompressor, CompressionMethod.Deflate); + } + + //Test for gzip support + if ((methods & CompressionMethod.Gzip) > 0) + { + TestCompressorMethod(testCompressor, CompressionMethod.Gzip); + } + } - private static void TestCompressorMethod(CompressorManager manager, object compressor, CompressionMethod method) + private static void TestSupportedMethods(ITestCompressor compressor) + { + //Make sure error occurs with non-supported comp + Assert.ThrowsException<NotSupportedException>(() => { compressor.InitCompressor(CompressionMethod.None); }); + + //test out of range, this should be a native lib error + Assert.ThrowsException<NotSupportedException>(() => { compressor.InitCompressor((CompressionMethod)24); }); + + //Test all 3 compression methods + CompressionMethod supported = compressor.GetSupportedMethods(); + + if ((supported & CompressionMethod.Gzip) > 0) + { + //Make sure no error occurs with supported comp + compressor.InitCompressor(CompressionMethod.Gzip); + compressor.DeinitCompressor(); + } + + if ((supported & CompressionMethod.Brotli) > 0) + { + //Make sure no error occurs with supported comp + compressor.InitCompressor(CompressionMethod.Brotli); + compressor.DeinitCompressor(); + } + + if ((supported & CompressionMethod.Deflate) > 0) + { + //Make sure no error occurs with supported comp + compressor.InitCompressor(CompressionMethod.Deflate); + compressor.DeinitCompressor(); + } + } + + private static void TestCompressorMethod(ITestCompressor compressor, CompressionMethod method) { /* * This test method initalizes a new compressor instance of the desired type @@ -132,8 +164,12 @@ namespace VNLib.Net.Compression.Tests */ //Time to initialize the compressor - int blockSize = manager.InitCompressor(compressor, method); + int blockSize = compressor.InitCompressor(method); + /* + * Currently not worrying about block size in the native lib, so this + * should cause tests to fail when block size is supported later on + */ Assert.IsTrue(blockSize == 0); try @@ -141,35 +177,32 @@ namespace VNLib.Net.Compression.Tests using VnMemoryStream outputStream = new(); //Create a buffer to compress - byte[] buffer = new byte[4096]; + byte[] buffer = new byte[1024000]; byte[] output = new byte[4096]; //fill with random data RandomNumberGenerator.Fill(buffer); - int read = 0; + ForwardOnlyMemoryReader<byte> reader = new(buffer); //try to compress the data in chunks - while(read < buffer.Length) + while(reader.WindowSize > 0) { - //Get 4th of a buffer - ReadOnlyMemory<byte> chunk = buffer.AsMemory(read, 1024); - //Compress data - CompressionResult result = manager.CompressBlock(compressor, chunk, output); + CompressionResult result = compressor.CompressBlock(reader.Window, output); //Write the compressed data to the output stream - outputStream.Write(output.Slice(0, result.BytesWritten)); + outputStream.Write(output, 0, result.BytesWritten); - //Increment the read position - read += result.BytesRead; + //Advance reader + reader.Advance(result.BytesRead); } //Flush int flushed = 100; while(flushed > 0) { - flushed = manager.Flush(compressor, output); + flushed = compressor.Flush(output); //Write the compressed data to the output stream outputStream.Write(output.AsSpan()[0..flushed]); @@ -183,11 +216,10 @@ namespace VNLib.Net.Compression.Tests finally { //Always deinitialize the compressor when done - manager.DeinitCompressor(compressor); + compressor.DeinitCompressor(); } } - private static byte[] DecompressData(VnMemoryStream inputStream, CompressionMethod method) { inputStream.Position = 0; @@ -214,5 +246,55 @@ namespace VNLib.Net.Compression.Tests _ => throw new ArgumentException("Unsupported compression method", nameof(method)), }; } + + interface ITestCompressor + { + int InitCompressor(CompressionMethod method); + + void DeinitCompressor(); + + CompressionResult CompressBlock(ReadOnlyMemory<byte> input, Memory<byte> output); + + int Flush(Memory<byte> buffer); + + CompressionMethod GetSupportedMethods(); + } + + sealed record class ManagerTestComp(object Compressor, CompressorManager Manager) : ITestCompressor + { + public CompressionResult CompressBlock(ReadOnlyMemory<byte> input, Memory<byte> output) => Manager.CompressBlock(Compressor, input, output); + + public void DeinitCompressor() => Manager.DeinitCompressor(Compressor); + + public int Flush(Memory<byte> buffer) => Manager.Flush(Compressor, buffer); + + public CompressionMethod GetSupportedMethods() => Manager.GetSupportedMethods(); + + public int InitCompressor(CompressionMethod level) => Manager.InitCompressor(Compressor, level); + + } + + sealed record class LibTestComp(NativeCompressionLib Library, CompressionLevel level) : ITestCompressor + { + private INativeCompressor? _comp; + + public CompressionResult CompressBlock(ReadOnlyMemory<byte> input, Memory<byte> output) => _comp!.Compress(input, output); + + public void DeinitCompressor() + { + _comp!.Dispose(); + _comp = null; + } + + public int Flush(Memory<byte> buffer) => _comp!.Flush(buffer); + + public CompressionMethod GetSupportedMethods() => Library.GetSupportedMethods(); + + public int InitCompressor(CompressionMethod method) + { + _comp = Library.AllocCompressor(method, level); + return _comp.GetBlockSize(); + } + } } }
\ No newline at end of file diff --git a/lib/Net.Compression/vnlib_compress/compression.c b/lib/Net.Compression/vnlib_compress/compression.c index 959540d..1139762 100644 --- a/lib/Net.Compression/vnlib_compress/compression.c +++ b/lib/Net.Compression/vnlib_compress/compression.c @@ -220,10 +220,10 @@ VNLIB_EXPORT void* VNLIB_CC AllocateCompressor(CompressorType type, CompressionL return (void*)result; #pragma GCC diagnostic pop #elif defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable: 4312) +#pragma warning(push) +#pragma warning(disable: 4312) return (void*)result; - #pragma warning(pop) +#pragma warning(pop) #else return (void*)result; #endif diff --git a/lib/Net.Http/src/Core/Response/ResponseWriter.cs b/lib/Net.Http/src/Core/Response/ResponseWriter.cs index 97b734a..b6b2488 100644 --- a/lib/Net.Http/src/Core/Response/ResponseWriter.cs +++ b/lib/Net.Http/src/Core/Response/ResponseWriter.cs @@ -24,7 +24,7 @@ /* * This file handles response entity processing. It handles in-memory response - * processing, as well as stream response processing. It handles contraints + * processing, as well as stream response processing. It handles constraints * such as content-range limits. I tried to eliminate or reduce the amount of * memory copying required to process the response entity. */ @@ -35,7 +35,6 @@ using System.Threading; using System.Threading.Tasks; using System.Runtime.CompilerServices; -using VNLib.Utils.IO; using VNLib.Utils.Memory; using VNLib.Net.Http.Core.Response; using VNLib.Net.Http.Core.Compression; diff --git a/lib/WinRpMalloc/Taskfile.yaml b/lib/WinRpMalloc/Taskfile.yaml index 90a0898..356db70 100644 --- a/lib/WinRpMalloc/Taskfile.yaml +++ b/lib/WinRpMalloc/Taskfile.yaml @@ -17,7 +17,7 @@ tasks: build: #build from the src directory - dir: '{{.USER_WORKING_DIR}}/src' + dir: '{{.USER_WORKING_DIR}}' cmds: #build in debug mode - msbuild /p:Configuration=debug {{.BUILD_FLAGS}} {{.MS_ARGS}} |