aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-29 21:23:26 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-29 21:23:26 -0500
commitb679ddd4e647ac915febd0d5a5e488a1e8e48842 (patch)
treecf414be9a53342e8d59194198cde5bf3c2187fc1 /lib
parent2b1314c1475e7e1831c691cf349cb89c66fa320c (diff)
Squashed commit of the following:
commit 231e26e5c6731e6e156d7c0591518e84a3b82f5a Author: vnugent <public@vaughnnugent.com> Date: Thu Feb 29 20:59:42 2024 -0500 fix: #5 find and patch Windows compression bug and some deployment commit d0bfe14e0a0e27172b8dd41f468265e651784837 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 21 21:39:19 2024 -0500 fix: #4 fix readme licensing message for accuracy commit 6f37f152fcd105e40af6689192a36b87eda95f51 Author: vnugent <public@vaughnnugent.com> Date: Wed Feb 21 21:37:55 2024 -0500 fix: jwt hashalg sizing and public api for hashalg sizes
Diffstat (limited to 'lib')
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs7
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs10
-rw-r--r--lib/Hashing.Portable/src/ManagedHash.cs57
-rw-r--r--lib/Hashing.Portable/src/ManagedHashAlgImpl.cs29
-rw-r--r--lib/Hashing.Portable/tests/ManagedHashTests.cs81
-rw-r--r--lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs33
-rw-r--r--lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs46
-rw-r--r--lib/Net.Compression/vnlib_compress/CMakeLists.txt26
-rw-r--r--lib/Net.Compression/vnlib_compress/Taskfile.yaml5
-rw-r--r--lib/Net.Compression/vnlib_compress/compression.c67
-rw-r--r--lib/Net.Compression/vnlib_compress/feature_brotli.c3
-rw-r--r--lib/Net.Compression/vnlib_compress/feature_brotli.h2
-rw-r--r--lib/Net.Compression/vnlib_compress/util.h82
-rw-r--r--lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs12
-rw-r--r--lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs10
-rw-r--r--lib/Net.Http/src/Core/Compression/ManagedHttpCompressor.cs21
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs26
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs32
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs27
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs52
-rw-r--r--lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs14
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs44
-rw-r--r--lib/Net.Http/src/Core/Response/HttpStreamResponse.cs50
-rw-r--r--lib/Net.Http/src/Core/Response/HttpstreamResponse.cs50
-rw-r--r--lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs100
-rw-r--r--lib/Net.Http/src/Core/Response/ResponseWriter.cs298
-rw-r--r--lib/Plugins.Essentials/src/Accounts/UserCreationRequest.cs16
-rw-r--r--lib/Plugins.Essentials/src/EventProcessor.cs18
-rw-r--r--lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs1
-rw-r--r--lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs98
-rw-r--r--lib/Plugins.Essentials/src/SemiConsistentVeTable.cs25
-rw-r--r--lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs4
-rw-r--r--lib/Plugins.Essentials/src/Users/IUserCreationRequest.cs4
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs18
-rw-r--r--lib/Utils/src/Memory/Caching/IReusable.cs4
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs7
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs38
-rw-r--r--lib/Utils/src/Memory/ProcessHeap.cs14
-rw-r--r--lib/Utils/src/Native/SafeLibraryHandle.cs20
39 files changed, 845 insertions, 606 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
index 2a18cfc..12ba095 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs
@@ -194,6 +194,13 @@ namespace VNLib.Hashing.IdentityUtility
}
/// <summary>
+ /// Gets the hash size in bytes for the current <see cref="HashAlg"/> value
+ /// </summary>
+ /// <param name="alg"></param>
+ /// <returns>The size (in bytes) of the algorithm's hash</returns>
+ public static int HashSize(this HashAlg alg) => ManagedHash.GetHashSize(alg);
+
+ /// <summary>
/// Gets the <see cref="HashAlgorithmName"/> for the current <see cref="HashAlg"/>
/// value.
/// </summary>
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
index ef6715b..68033cd 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
@@ -275,7 +275,7 @@ namespace VNLib.Hashing.IdentityUtility
public static void Sign(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg)
{
//Stack hash output buffer, will be the size of the alg
- Span<byte> sigOut = stackalloc byte[(int)alg];
+ Span<byte> sigOut = stackalloc byte[alg.HashSize()];
//Compute
ERRNO count = ManagedHash.ComputeHmac(key, jwt.HeaderAndPayload, sigOut, alg);
@@ -394,7 +394,7 @@ namespace VNLib.Hashing.IdentityUtility
int sigBufSize = CalcPadding(signature.Length) + signature.Length;
//Calc full buffer size
- nint bufferSize = MemoryUtil.NearestPage(sigBufSize + (int)alg);
+ nint bufferSize = MemoryUtil.NearestPage(sigBufSize + alg.HashSize());
//Alloc buffer to decode data, as a full page, all buffers will be used from the block for better cache
using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>((int)bufferSize);
@@ -442,10 +442,10 @@ namespace VNLib.Hashing.IdentityUtility
/// <exception cref="InternalBufferTooSmallException"></exception>
public static bool Verify(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg)
{
- _ = jwt ?? throw new ArgumentNullException(nameof(jwt));
+ ArgumentNullException.ThrowIfNull(jwt);
//Get base64 buffer size for in-place conversion
- int bufferSize = Base64.GetMaxEncodedToUtf8Length((int)alg);
+ int bufferSize = Base64.GetMaxEncodedToUtf8Length(alg.HashSize());
//Alloc buffer for signature output
Span<byte> signatureBuffer = stackalloc byte[bufferSize];
@@ -458,7 +458,7 @@ namespace VNLib.Hashing.IdentityUtility
}
//Do an in-place base64 conversion of the signature to base64url
- ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, (int)alg, false);
+ ERRNO encoded = VnEncoding.Base64UrlEncodeInPlace(signatureBuffer, alg.HashSize(), false);
if (!encoded)
{
diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs
index 7791eed..429660f 100644
--- a/lib/Hashing.Portable/src/ManagedHash.cs
+++ b/lib/Hashing.Portable/src/ManagedHash.cs
@@ -29,7 +29,6 @@ using System.Security.Cryptography;
using VNLib.Utils;
using VNLib.Utils.Memory;
-using VNLib.Hashing.Native.MonoCypher;
namespace VNLib.Hashing
{
@@ -84,7 +83,7 @@ namespace VNLib.Hashing
/// Gets a value that indicates whether the current runtime has the required libraries
/// available to support the Blake2b hashing algorithm
/// </summary>
- public static bool SupportsBlake2b => MonoCypherLibrary.CanLoadDefaultLibrary();
+ public static bool SupportsBlake2b => IsAlgSupported(HashAlg.BlAKE2B);
/// <summary>
/// Gets a value that indicates whether the current platform supports the SHA3
@@ -103,8 +102,8 @@ namespace VNLib.Hashing
HashAlg.SHA3_512 => Sha3_512.IsSupported,
HashAlg.SHA3_384 => Sha3_384.IsSupported,
HashAlg.SHA3_256 => Sha3_256.IsSupported,
- HashAlg.BlAKE2B => SupportsBlake2b,
- HashAlg.SHA512 => true,
+ HashAlg.BlAKE2B => Blake2b.IsSupported,
+ HashAlg.SHA512 => true, //Built-in functions are always supported
HashAlg.SHA384 => true,
HashAlg.SHA256 => true,
HashAlg.SHA1 => true,
@@ -113,6 +112,26 @@ namespace VNLib.Hashing
};
/// <summary>
+ /// Gets the size of the hash (in bytes) for the specified algorithm
+ /// </summary>
+ /// <param name="type">The hash algorithm to get the size of</param>
+ /// <returns>A positive 32-bit integer size (in bytes) of the algorithm hash size</returns>
+ /// <exception cref="ArgumentException"></exception>
+ public static int GetHashSize(HashAlg type) => type switch
+ {
+ HashAlg.SHA3_512 => _3_sha512.HashSize,
+ HashAlg.SHA3_384 => _3_sha384.HashSize,
+ HashAlg.SHA3_256 => _3_sha256.HashSize,
+ HashAlg.BlAKE2B => _blake2bAlg.HashSize,
+ HashAlg.SHA512 => _sha512Alg.HashSize,
+ HashAlg.SHA384 => _sha384Alg.HashSize,
+ HashAlg.SHA256 => _sha256Alg.HashSize,
+ HashAlg.SHA1 => _sha1Alg.HashSize,
+ HashAlg.MD5 => _md5Alg.HashSize,
+ _ => throw new ArgumentException("Invalid hash algorithm", nameof(type))
+ };
+
+ /// <summary>
/// Uses the UTF8 character encoding to encode the string, then
/// attempts to compute the hash and store the results into the output buffer
/// </summary>
@@ -265,11 +284,11 @@ namespace VNLib.Hashing
private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, ReadOnlySpan<byte> key = default)
{
//Alloc output buffer
- byte[] output = new byte[HashSize(alg)];
+ byte[] output = new byte[GetHashSize(alg)];
//Hash data
ERRNO result = ComputeHashInternal(alg, data, output, key);
- Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
+ Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
return output;
}
@@ -288,13 +307,13 @@ namespace VNLib.Hashing
private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<char> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default)
{
//Alloc stack buffer to store hash output
- Span<byte> hashBuffer = stackalloc byte[HashSize(alg)];
+ Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)];
//hash the buffer
ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key);
//Count should always be the same as the hash size, this should never fail
- Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
+ Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
//Convert to encoded string
return mode switch
@@ -310,13 +329,13 @@ namespace VNLib.Hashing
private static string ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, HashEncodingMode mode, ReadOnlySpan<byte> key = default)
{
//Alloc stack buffer to store hash output
- Span<byte> hashBuffer = stackalloc byte[HashSize(alg)];
+ Span<byte> hashBuffer = stackalloc byte[GetHashSize(alg)];
//hash the buffer
ERRNO count = ComputeHashInternal(alg, data, hashBuffer, key);
//Count should always be the same as the hash size, this should never fail
- Debug.Assert(count == HashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
+ Debug.Assert(count == GetHashSize(alg), $"Failed to compute hash using {alg} of size {hashBuffer.Length}");
//Convert to encoded string
return mode switch
@@ -333,29 +352,15 @@ namespace VNLib.Hashing
private static byte[] ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key = default)
{
//Alloc output buffer
- byte[] output = new byte[HashSize(alg)];
+ byte[] output = new byte[GetHashSize(alg)];
//Hash data
ERRNO result = ComputeHashInternal(alg, data, output, key);
- Debug.Assert(result == HashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
+ Debug.Assert(result == GetHashSize(alg), $"Failed to compute hash using {alg} of size {output.Length}");
return output;
}
- private static int HashSize(HashAlg alg) => alg switch
- {
- HashAlg.SHA3_512 => _3_sha512.HashSize,
- HashAlg.SHA3_384 => _3_sha384.HashSize,
- HashAlg.SHA3_256 => _3_sha256.HashSize,
- HashAlg.BlAKE2B => _blake2bAlg.HashSize,
- HashAlg.SHA512 => _sha512Alg.HashSize,
- HashAlg.SHA384 => _sha384Alg.HashSize,
- HashAlg.SHA256 => _sha256Alg.HashSize,
- HashAlg.SHA1 => _sha1Alg.HashSize,
- HashAlg.MD5 => _md5Alg.HashSize,
- _ => throw new ArgumentException("Invalid hash algorithm", nameof(alg))
- };
-
private static ERRNO ComputeHashInternal(HashAlg alg, ReadOnlySpan<byte> data, Span<byte> buffer, ReadOnlySpan<byte> key = default)
{
diff --git a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
index d53152e..eae8903 100644
--- a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
+++ b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs
@@ -150,6 +150,8 @@ namespace VNLib.Hashing
internal static int MaxHashSize => MCBlake2Module.MaxHashSize;
internal static int MaxKeySize => MCBlake2Module.MaxKeySize;
+ public static bool IsSupported => MonoCypherLibrary.CanLoadDefaultLibrary();
+
///<inheritdoc/>
public readonly int HashSize => DefaultBlake2HashSize;
@@ -180,9 +182,18 @@ namespace VNLib.Hashing
{
count = 0;
- if (output.Length > MCBlake2Module.MaxHashSize)
+ /*
+ * For this specific implementation, the ManagedHash class assumes hashing implementations
+ * have a fixed hash size. This is not the case for Blake2b, so we need to enforce one.
+ *
+ * Since the HasSize property is publicly accessible users can assume the hash size is
+ * the value returned by the property. So it should be safe to enforce the hash size here.
+ *
+ * Any data outside the default hash size is not our problem.
+ */
+ if (output.Length > HashSize)
{
- return false;
+ output = output[..HashSize];
}
//Test key size
@@ -191,17 +202,11 @@ namespace VNLib.Hashing
return false;
}
- //Compute one-shot hash
- ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output);
+ //Compute one-shot hash, a variable length result may occur
+ count = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output);
- if (result < output.Length)
- {
- count = 0;
- return false;
- }
-
- count = output.Length;
- return true;
+ //Output should always be the same size as the hash size
+ return count == output.Length;
}
}
}
diff --git a/lib/Hashing.Portable/tests/ManagedHashTests.cs b/lib/Hashing.Portable/tests/ManagedHashTests.cs
index a9fa467..3bcdb33 100644
--- a/lib/Hashing.Portable/tests/ManagedHashTests.cs
+++ b/lib/Hashing.Portable/tests/ManagedHashTests.cs
@@ -97,5 +97,86 @@ namespace VNLib.Hashing.Tests
Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2);
}
}
+
+ const string TestHashInput = "Hello World!";
+ static readonly string[] TestHashOutputHex =
+ [
+ //None
+ "",
+ //Md5
+ "ed076287532e86365e841e92bfc50d8c",
+ //Sha1
+ "2ef7bde608ce5404e97d5f042f95f89f1c232871",
+ //Sha2
+ "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069",
+ "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a",
+ "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8",
+
+ //Sha3
+ "d0e47486bbf4c16acac26f8b653592973c1362909f90262877089f9c8a4536af",
+ "f324cbd421326a2abaedf6f395d1a51e189d4a71c755f531289e519f079b224664961e385afcc37da348bd859f34fd1c",
+ "32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48",
+
+ //Blake2b (64 bytes/512 bits)
+ "54b113f499799d2f3c0711da174e3bc724737ad18f63feb286184f0597e1466436705d6c8e8c7d3d3b88f5a22e83496e0043c44a3c2b1700e0e02259f8ac468e"
+ ];
+
+ //Known hash sizes to compare against
+ static readonly int[] HashSizes =
+ [
+ 0,
+ 16,
+ 20,
+ 32,
+ 48,
+ 64,
+ 32,
+ 48,
+ 64,
+ 64
+ ];
+
+ [TestMethod()]
+ public void ComputeHexHashTest()
+ {
+ HashAlg[] algs = Enum.GetValues<HashAlg>();
+
+ Assert.AreEqual(algs.Length, TestHashOutputHex.Length);
+
+ for (int i = 0; i < algs.Length; i++)
+ {
+ if (algs[i] == HashAlg.None)
+ continue;
+
+ //Skip unsupported algorithms
+ if (!ManagedHash.IsAlgSupported(algs[i]))
+ continue;
+
+ string hash = ManagedHash.ComputeHash(TestHashInput, algs[i], HashEncodingMode.Hexadecimal);
+
+ //Make sure exact length (x2 for hex)
+ Assert.AreEqual(HashSizes[i] * 2, hash.Length);
+
+ //Make sure exact value
+ Assert.AreEqual(TestHashOutputHex[i], hash, true);
+ }
+ }
+
+ [TestMethod()]
+ public void AlgSizeTest()
+ {
+ HashAlg[] algs = Enum.GetValues<HashAlg>();
+
+ for (int i = 0; i < algs.Length; i++)
+ {
+ if (algs[i] == HashAlg.None)
+ continue;
+
+ //Make sure exact length
+ Assert.AreEqual(HashSizes[i], ManagedHash.GetHashSize(algs[i]));
+ }
+
+ Assert.ThrowsException<ArgumentException>(() => ManagedHash.GetHashSize(HashAlg.None));
+ }
}
} \ 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 a614ab8..efbee77 100644
--- a/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs
+++ b/lib/Net.Compression/VNLib.Net.Compression/CompressorManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Compression
@@ -111,7 +111,8 @@ namespace VNLib.Net.Compression
///<inheritdoc/>
public int InitCompressor(object compressorState, CompressionMethod compMethod)
{
- Compressor compressor = Unsafe.As<Compressor>(compressorState) ?? throw new ArgumentNullException(nameof(compressorState));
+ DebugThrowIfNull(compressorState, nameof(compressorState));
+ Compressor compressor = Unsafe.As<Compressor>(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");
@@ -126,7 +127,8 @@ namespace VNLib.Net.Compression
///<inheritdoc/>
public void DeinitCompressor(object compressorState)
{
- Compressor compressor = Unsafe.As<Compressor>(compressorState) ?? throw new ArgumentNullException(nameof(compressorState));
+ DebugThrowIfNull(compressorState, nameof(compressorState));
+ Compressor compressor = Unsafe.As<Compressor>(compressorState);
if(compressor.Instance == IntPtr.Zero)
{
@@ -143,7 +145,8 @@ namespace VNLib.Net.Compression
///<inheritdoc/>
public int Flush(object compressorState, Memory<byte> output)
{
- Compressor compressor = Unsafe.As<Compressor>(compressorState) ?? throw new ArgumentNullException(nameof(compressorState));
+ DebugThrowIfNull(compressorState, nameof(compressorState));
+ Compressor compressor = Unsafe.As<Compressor>(compressorState);
if (compressor.Instance == IntPtr.Zero)
{
@@ -151,14 +154,15 @@ namespace VNLib.Net.Compression
}
//Force a flush until no more data is available
- CompressionResult result = _nativeLib.CompressBlock(compressor.Instance, output, default, true);
+ CompressionResult result = _nativeLib!.CompressBlock(compressor.Instance, output, default, true);
return result.BytesWritten;
}
///<inheritdoc/>
public CompressionResult CompressBlock(object compressorState, ReadOnlyMemory<byte> input, Memory<byte> output)
{
- Compressor compressor = Unsafe.As<Compressor>(compressorState) ?? throw new ArgumentNullException(nameof(compressorState));
+ DebugThrowIfNull(compressorState, nameof(compressorState));
+ Compressor compressor = Unsafe.As<Compressor>(compressorState);
if (compressor.Instance == IntPtr.Zero)
{
@@ -166,9 +170,20 @@ namespace VNLib.Net.Compression
}
//Compress the block
- return _nativeLib.CompressBlock(compressor.Instance, output, input, false);
- }
-
+ return _nativeLib!.CompressBlock(compressor.Instance, output, input, false);
+ }
+
+ /*
+ * This compressor manager instance is designed to tbe used by a webserver instance,
+ * (or multiple) as internal calls. We can assume the library compression calls
+ * are "correct" and should never pass null objects to these function calls.
+ *
+ * Its also a managed type and if null will still raise a null ref exception
+ * if the instances are null. This is just cleaner for debugging purposes.
+ */
+
+ [Conditional("DEBUG")]
+ private static void DebugThrowIfNull<T>(T? obj, string name) => ArgumentNullException.ThrowIfNull(obj, name);
/*
* A class to contain the compressor state
diff --git a/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs b/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs
index 95c1cc1..236d39c 100644
--- a/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs
+++ b/lib/Net.Compression/VNLib.Net.CompressionTests/CompressorManagerTests.cs
@@ -26,7 +26,7 @@ namespace VNLib.Net.Compression.Tests
public void NativeLibApiTest()
{
//Load library
- using NativeCompressionLib lib = NativeCompressionLib.LoadLibrary(LIB_PATH, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories);
+ using NativeCompressionLib lib = NativeCompressionLib.LoadLibrary(LIB_PATH, DllImportSearchPath.SafeDirectories);
LibTestComp cp = new(lib, CompressionLevel.Fastest);
@@ -71,7 +71,7 @@ namespace VNLib.Net.Compression.Tests
PrintSystemInformation();
//Load native library
- using NativeCompressionLib lib = NativeCompressionLib.LoadLibrary(LIB_PATH, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories);
+ using NativeCompressionLib lib = NativeCompressionLib.LoadLibrary(LIB_PATH, DllImportSearchPath.SafeDirectories);
//Huge array of random data to compress
byte[] testData = RandomNumberGenerator.GetBytes(10 * 1024 * 1024);
@@ -102,7 +102,7 @@ namespace VNLib.Net.Compression.Tests
private static void TestSingleCompressor(LibTestComp comp, CompressionMethod method, CompressionLevel level, byte[] testData)
{
byte[] outputBlock = new byte[8 * 1024];
- long ms;
+ long nativeTicks;
Stopwatch stopwatch = new ();
{
@@ -132,11 +132,13 @@ namespace VNLib.Net.Compression.Tests
{
//Include deinit
comp.DeinitCompressor();
- stopwatch.Stop();
- ms = stopwatch.ElapsedTicks / (TimeSpan.TicksPerMillisecond / 1000);
+ stopwatch.Stop();
}
}
+ nativeTicks = stopwatch.ElapsedTicks;
+
+ //Switch to managed test
using (Stream compStream = GetEncodeStream(Stream.Null, method, level))
{
stopwatch.Restart();
@@ -151,13 +153,16 @@ namespace VNLib.Net.Compression.Tests
}
}
- long streamMicroseconds = stopwatch.ElapsedTicks / (TimeSpan.TicksPerMillisecond / 1000);
+ long streamMicroseconds = TicksToMicroseconds(stopwatch.ElapsedTicks);
+ long nativeMicroseconds = TicksToMicroseconds(nativeTicks);
- string winner = ms < streamMicroseconds ? "native" : "stream";
+ string winner = nativeMicroseconds < streamMicroseconds ? "native" : "stream";
- Debug.WriteLine($"{method}: {testData.Length} bytes, {ms}misec vs {streamMicroseconds}misec. Winner {winner}");
+ Debug.WriteLine($"{method}: {testData.Length} bytes, {nativeMicroseconds}misec vs {streamMicroseconds}misec. Winner {winner}");
}
+ static long TicksToMicroseconds(long ticks) => ticks / (TimeSpan.TicksPerMillisecond / 1000);
+
private static CompressorManager InitCompressorUnderTest()
{
CompressorManager manager = new();
@@ -255,16 +260,17 @@ namespace VNLib.Net.Compression.Tests
Debug.WriteLine($"Compressor library supports {supported}");
}
+ /*
+ * This test method initalizes a new compressor instance of the desired type
+ * creates a test data buffer, compresses it using the compressor instance
+ * then decompresses the compressed data using a managed decompressor as
+ * a reference and compares the results.
+ *
+ * The decompression must be able to recover the original data.
+ */
+
private static void TestCompressorMethod(ITestCompressor compressor, CompressionMethod method)
- {
- /*
- * This test method initalizes a new compressor instance of the desired type
- * creates a test data buffer, compresses it using the compressor instance
- * then decompresses the compressed data using a managed decompressor as
- * a reference and compares the results.
- *
- * The decompression must be able to recover the original data.
- */
+ {
//Time to initialize the compressor
int blockSize = compressor.InitCompressor(method);
@@ -311,7 +317,7 @@ namespace VNLib.Net.Compression.Tests
outputStream.Write(output.AsSpan()[0..flushed]);
}
- //Verify the data
+ //Verify the original data matches the decompressed data
byte[] decompressed = DecompressData(outputStream, method);
Assert.IsTrue(buffer.SequenceEqual(decompressed));
@@ -393,7 +399,7 @@ Page Size: {Environment.SystemPageSize}
CompressionMethod GetSupportedMethods();
}
- sealed record class ManagerTestComp(object Compressor, CompressorManager Manager) : ITestCompressor
+ sealed class ManagerTestComp(object Compressor, CompressorManager Manager) : ITestCompressor
{
public CompressionResult CompressBlock(ReadOnlyMemory<byte> input, Memory<byte> output) => Manager.CompressBlock(Compressor, input, output);
@@ -407,7 +413,7 @@ Page Size: {Environment.SystemPageSize}
}
- sealed record class LibTestComp(NativeCompressionLib Library, CompressionLevel Level) : ITestCompressor
+ sealed class LibTestComp(NativeCompressionLib Library, CompressionLevel Level) : ITestCompressor
{
private INativeCompressor? _comp;
diff --git a/lib/Net.Compression/vnlib_compress/CMakeLists.txt b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
index 17b01ca..977b9a1 100644
--- a/lib/Net.Compression/vnlib_compress/CMakeLists.txt
+++ b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
@@ -218,10 +218,16 @@ if(ENABLE_ZLIB)
check_c_compiler_flag(/arch:AVX HAS_AVX)
if (HAS_AVX)
list(APPEND Z_C_FLAGS /arch:AVX)
+ list(APPEND ZLIB_DEFINITIONS -DHAS_AVX)
endif()
- elseif(UNIX)
+ #All x64 machines have SSE2, so we can use it as
+ #and the Windows compiler will automatically use it
+ #so we only need to set the definition
+ list(APPEND Z_C_FLAGS /arch:SSE2)
+ list(APPEND ZLIB_DEFINITIONS -DHAS_SSE2 -DHAS_SSE42)
+ elseif(UNIX)
#for cloudflare intrinsic detections
check_c_compiler_flag(-march=armv8-a+crc ARM_CRC)
@@ -229,9 +235,15 @@ if(ENABLE_ZLIB)
check_c_compiler_flag(-mssse3 HAS_SSSE3)
check_c_compiler_flag(-msse4.2 HAS_SSE42)
check_c_compiler_flag(-mpclmul HAS_PCLMUL)
-
+
if(ARM_CRC)
list(APPEND Z_C_FLAGS -march=armv8-a+crc)
+
+ if(EXISTS "../third-party/zlib/adler32_simd.c")
+ list(APPEND ZLIB_DEFINITIONS -DADLER32_SIMD_NEON)
+ list(APPEND ZLIB_SOURCES ../third-party/zlib/adler32_simd.c)
+ endif()
+
else()
if(HAS_SSE2)
list(APPEND Z_C_FLAGS -msse2)
@@ -241,7 +253,7 @@ if(ENABLE_ZLIB)
if(HAS_SSSE3)
list(APPEND Z_C_FLAGS -mssse3)
- #add cloudflare intrinsic optimizations
+ #add cloudflare intrinsic optimizations, may not be present if using non-cloudflare fork
if(EXISTS "../third-party/zlib/adler32_simd.c")
list(APPEND ZLIB_DEFINITIONS -DHAS_SSSE3 -DADLER32_SIMD_SSSE3)
list(APPEND ZLIB_SOURCES ../third-party/zlib/adler32_simd.c)
@@ -255,7 +267,7 @@ if(ENABLE_ZLIB)
if(HAS_PCLMUL)
list(APPEND Z_C_FLAGS -mpclmul)
- #add cloudflare intrinsic optimizations for PCMLONGMUL
+ #add cloudflare intrinsic optimizations for PCMLONGMUL crc32, may not be present if using non-cloudflare fork
if(EXISTS "../third-party/zlib/crc32_simd.c")
list(APPEND ZLIB_DEFINITIONS -DHAS_PCLMUL)
list(APPEND ZLIB_SOURCES ../third-party/zlib/crc32_simd.c)
@@ -263,9 +275,9 @@ if(ENABLE_ZLIB)
endif()
endif()
-
-
endif()
+
+
#add zlib as a library to link later
add_library(lib_deflate STATIC ${ZLIB_SOURCES})
@@ -278,7 +290,7 @@ if(ENABLE_ZLIB)
#add the definitions to the zlib project only
target_compile_definitions(lib_deflate PRIVATE ${ZLIB_DEFINITIONS})
- #only target zlib project
+ #only target zlib project with compiler flags
target_compile_options(lib_deflate PRIVATE ${Z_C_FLAGS})
target_link_libraries(${CMAKE_PROJECT_NAME} lib_deflate)
diff --git a/lib/Net.Compression/vnlib_compress/Taskfile.yaml b/lib/Net.Compression/vnlib_compress/Taskfile.yaml
index 5271061..28b3ff0 100644
--- a/lib/Net.Compression/vnlib_compress/Taskfile.yaml
+++ b/lib/Net.Compression/vnlib_compress/Taskfile.yaml
@@ -21,7 +21,11 @@ tasks:
#make third-party dir before cloning libs
- cmd: powershell -Command "mkdir '{{.THIRD_PARTY_DIR}}' -Force"
+ platforms: [windows]
ignore_error: true
+ - cmd: mkdir -p '{{.THIRD_PARTY_DIR}}'
+ platforms: [linux, darwin]
+ ignore_error: true
- task: zlib
- task: brotli
@@ -51,7 +55,6 @@ tasks:
internal: true
status:
- cd {{.THIRD_PARTY_DIR}} && git clone https://github.com/cloudflare/zlib.git
-
cmds:
- cd {{.THIRD_PARTY_DIR}}/zlib && git pull
diff --git a/lib/Net.Compression/vnlib_compress/compression.c b/lib/Net.Compression/vnlib_compress/compression.c
index ffe280b..a414609 100644
--- a/lib/Net.Compression/vnlib_compress/compression.c
+++ b/lib/Net.Compression/vnlib_compress/compression.c
@@ -130,22 +130,21 @@ VNLIB_EXPORT void* VNLIB_CC AllocateCompressor(CompressorType type, CompressionL
switch (type)
{
+ case COMP_TYPE_BROTLI:
#ifdef VNLIB_COMPRESSOR_BROTLI_ENABLED
-
- case COMP_TYPE_BROTLI:
result = BrAllocCompressor(state);
- break;
-
#endif
-#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ break;
case COMP_TYPE_DEFLATE:
case COMP_TYPE_GZIP:
+
+#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
result = DeflateAllocCompressor(state);
- break;
#endif
+ break;
/*
* Unsupported compressor type allow error to propagate
@@ -207,30 +206,25 @@ VNLIB_EXPORT int VNLIB_CC FreeCompressor(_In_ void* compressor)
switch (comp->type)
{
-
+ case COMP_TYPE_BROTLI:
#ifdef VNLIB_COMPRESSOR_BROTLI_ENABLED
-
- case COMP_TYPE_BROTLI:
BrFreeCompressor(comp);
- break;
-
#endif
-
-#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ break;
case COMP_TYPE_DEFLATE:
- case COMP_TYPE_GZIP:
-
+ case COMP_TYPE_GZIP:
/*
* Releasing a deflate compressor will cause a deflate
* end call, which can fail, we should send the error
* to the caller and clean up as best we can.
*/
-
+#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
errorCode = DeflateFreeCompressor(comp);
+#endif
break;
-#endif
+
/*
* If compression type is none, there is nothing to do
* since its not technically an error, so just return
@@ -239,8 +233,7 @@ VNLIB_EXPORT int VNLIB_CC FreeCompressor(_In_ void* compressor)
case COMP_TYPE_NONE:
case COMP_TYPE_LZ4:
default:
- break;
-
+ break;
}
/*
@@ -267,26 +260,25 @@ VNLIB_EXPORT int64_t VNLIB_CC GetCompressedSize(_In_ const void* compressor, uin
}
comp = (CompressorState*)compressor;
+ result = ERR_COMP_TYPE_NOT_SUPPORTED;
switch (comp->type)
{
-#ifdef VNLIB_COMPRESSOR_BROTLI_ENABLED
-
case COMP_TYPE_BROTLI:
- result = BrGetCompressedSize(comp, inputLength);
- break;
+#ifdef VNLIB_COMPRESSOR_BROTLI_ENABLED
+ result = BrGetCompressedSize(comp, inputLength, flush);
#endif
-
-#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ break;
case COMP_TYPE_DEFLATE:
case COMP_TYPE_GZIP:
- result = DeflateGetCompressedSize(comp, inputLength, flush);
- break;
+#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ result = DeflateGetCompressedSize(comp, inputLength, flush);
#endif
+ break;
/*
* Set the result as an error code, since the compressor
@@ -294,8 +286,6 @@ VNLIB_EXPORT int64_t VNLIB_CC GetCompressedSize(_In_ const void* compressor, uin
*/
case COMP_TYPE_NONE:
case COMP_TYPE_LZ4:
- default:
- result = ERR_COMP_TYPE_NOT_SUPPORTED;
break;
}
@@ -350,31 +340,30 @@ VNLIB_EXPORT int VNLIB_CC CompressBlock(_In_ const void* compressor, Compression
* compression function
*/
+ result = ERR_COMP_TYPE_NOT_SUPPORTED;
+
switch (comp->type)
{
+ /* Brolti support */
+ case COMP_TYPE_BROTLI:
- /* Brolti support */
#ifdef VNLIB_COMPRESSOR_BROTLI_ENABLED
-
- case COMP_TYPE_BROTLI:
result = BrCompressBlock(comp, operation);
- break;
#endif
+ break;
- /* Deflate support */
-#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ /* Deflate support */
case COMP_TYPE_DEFLATE:
case COMP_TYPE_GZIP:
- result = DeflateCompressBlock(comp, operation);
- break;
+#ifdef VNLIB_COMPRESSOR_ZLIB_ENABLED
+ result = DeflateCompressBlock(comp, operation);
#endif
+ break;
case COMP_TYPE_NONE:
case COMP_TYPE_LZ4:
- default:
- result = ERR_COMP_TYPE_NOT_SUPPORTED;
break;
}
diff --git a/lib/Net.Compression/vnlib_compress/feature_brotli.c b/lib/Net.Compression/vnlib_compress/feature_brotli.c
index 3a3e330..924f1af 100644
--- a/lib/Net.Compression/vnlib_compress/feature_brotli.c
+++ b/lib/Net.Compression/vnlib_compress/feature_brotli.c
@@ -220,8 +220,9 @@ int BrCompressBlock(const CompressorState* state, CompressionOperation* operatio
}
-int64_t BrGetCompressedSize(const CompressorState* state, uint64_t length)
+int64_t BrGetCompressedSize(const CompressorState* state, uint64_t length, int32_t flush)
{
+ (void)flush;
/*
* When the flush flag is set, the caller is requesting the
* entire size of the compressed data, which can include metadata
diff --git a/lib/Net.Compression/vnlib_compress/feature_brotli.h b/lib/Net.Compression/vnlib_compress/feature_brotli.h
index b3cb35f..1f2090b 100644
--- a/lib/Net.Compression/vnlib_compress/feature_brotli.h
+++ b/lib/Net.Compression/vnlib_compress/feature_brotli.h
@@ -41,6 +41,6 @@ void BrFreeCompressor(CompressorState* state);
int BrCompressBlock(const CompressorState* state, CompressionOperation* operation);
-int64_t BrGetCompressedSize(const CompressorState* state, uint64_t length);
+int64_t BrGetCompressedSize(const CompressorState* state, uint64_t length, int32_t flush);
#endif /* !BROTLI_STUB_H_ */ \ No newline at end of file
diff --git a/lib/Net.Compression/vnlib_compress/util.h b/lib/Net.Compression/vnlib_compress/util.h
index 344a28b..8930da7 100644
--- a/lib/Net.Compression/vnlib_compress/util.h
+++ b/lib/Net.Compression/vnlib_compress/util.h
@@ -30,6 +30,14 @@
* Stub missing types and constants for GCC
*/
+/*
+* If a custom allocator is enabled, use the native heap api
+* header and assume linking is enabled
+*/
+#ifdef VNLIB_CUSTOM_MALLOC_ENABLE
+#include <NativeHeapApi.h>
+#endif
+
#if defined(_MSC_VER) || defined(WIN32) || defined(_WIN32)
#define IS_WINDOWS
#endif
@@ -100,56 +108,48 @@
#define ERR_INVALID_PTR -1
#define ERR_OUT_OF_MEMORY -2
-#if defined(VNLIB_CUSTOM_MALLOC_ENABLE)
+#ifdef NATIVE_HEAP_API /* Defined in the NativeHeapApi */
+ /*
+ * Add overrides for malloc, calloc, and free that use
+ * the nativeheap api to allocate memory
+ */
-/*
-* Add rpmalloc overrides
-*/
-
-#include <NativeHeapApi.h>
+ static inline void* vnmalloc(size_t num, size_t size)
+ {
+ return heapAlloc(heapGetSharedHeapHandle(), num, size, FALSE);
+ }
+ static inline void* vncalloc(size_t num, size_t size)
+ {
+ return heapAlloc(heapGetSharedHeapHandle(), num, size, TRUE);
+ }
-/*
-* Add overrides for malloc, calloc, and free that use
-* the nativeheap api to allocate memory
-*/
+ static inline void vnfree(void* ptr)
+ {
+ ERRNO result;
+ result = heapFree(heapGetSharedHeapHandle(), ptr);
-static inline void* vnmalloc(size_t num, size_t size)
-{
- return heapAlloc(heapGetSharedHeapHandle(), num, size, FALSE);
-}
-
-static inline void* vncalloc(size_t num, size_t size)
-{
- return heapAlloc(heapGetSharedHeapHandle(), num , size, TRUE);
-}
-
-static inline void vnfree(void* ptr)
-{
- ERRNO result;
- result = heapFree(heapGetSharedHeapHandle(), ptr);
-
- //track failed free results
- assert(result > 0);
-}
+ //track failed free results
+ assert(result > 0);
+ }
#else
-/*
-* Stub method for malloc. All calls to vnmalloc should be freed with vnfree.
-*/
-#define vnmalloc(num, size) malloc(num * size)
+ /*
+ * Stub method for malloc. All calls to vnmalloc should be freed with vnfree.
+ */
+ #define vnmalloc(num, size) malloc(num * size)
-/*
-* Stub method for free
-*/
-#define vnfree(ptr) free(ptr)
+ /*
+ * Stub method for free
+ */
+ #define vnfree(ptr) free(ptr)
-/*
-* Stub method for calloc. All calls to vncalloc should be freed with vnfree.
-*/
-#define vncalloc(num, size) calloc(num, size)
+ /*
+ * Stub method for calloc. All calls to vncalloc should be freed with vnfree.
+ */
+ #define vncalloc(num, size) calloc(num, size)
-#endif
+#endif // NATIVE_HEAP_API
#endif /* !UTIL_H_ */ \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
index 827af56..90bdd8c 100644
--- a/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
+++ b/lib/Net.Http/src/Core/Buffering/ContextLockedBufferManager.cs
@@ -137,15 +137,13 @@ namespace VNLib.Net.Http.Core.Buffering
}
///<inheritdoc/>
- public void ZeroAll()
+ public void FreeAll(bool zero)
{
- //Zero the buffer completely
- MemoryUtil.InitializeBlock(_handle!.Memory);
- }
+ if (zero)
+ {
+ MemoryUtil.InitializeBlock(_handle!.Memory);
+ }
- ///<inheritdoc/>
- public void FreeAll()
- {
//Clear buffer memory structs to allow gc
_requestHeaderBuffer.FreeBuffer();
_responseHeaderBuffer.FreeBuffer();
diff --git a/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs b/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs
index c7020ff..9dc067a 100644
--- a/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs
+++ b/lib/Net.Http/src/Core/Buffering/IHttpBufferManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -79,13 +79,9 @@ namespace VNLib.Net.Http.Core.Buffering
void AllocateBuffer(IHttpMemoryPool allocator);
/// <summary>
- /// Zeros all internal buffers
- /// </summary>
- void ZeroAll();
-
- /// <summary>
/// Frees all internal buffers
/// </summary>
- void FreeAll();
+ /// <param name="zeroAll">A value that indicates if the buffer should be zeored before it's returned to the pool</param>
+ void FreeAll(bool zeroAll);
}
}
diff --git a/lib/Net.Http/src/Core/Compression/ManagedHttpCompressor.cs b/lib/Net.Http/src/Core/Compression/ManagedHttpCompressor.cs
index fbb17c2..bfd848a 100644
--- a/lib/Net.Http/src/Core/Compression/ManagedHttpCompressor.cs
+++ b/lib/Net.Http/src/Core/Compression/ManagedHttpCompressor.cs
@@ -27,17 +27,8 @@ using System.Diagnostics;
namespace VNLib.Net.Http.Core.Compression
{
- internal sealed class ManagedHttpCompressor : IResponseCompressor
+ internal sealed class ManagedHttpCompressor(IHttpCompressorManager manager) : IResponseCompressor
{
- //Store the compressor
- private readonly IHttpCompressorManager _provider;
-
- public ManagedHttpCompressor(IHttpCompressorManager provider)
- {
- Debug.Assert(provider != null, "Expected non-null provider");
- _provider = provider;
- }
-
/*
* The compressor alloc is deferd until the first call to Init()
* This is because user-code should not be called during the constructor
@@ -59,7 +50,7 @@ namespace VNLib.Net.Http.Core.Compression
Debug.Assert(!output.IsEmpty, "Expected non-zero output buffer");
//Compress the block
- return _provider.CompressBlock(_compressor!, input, output);
+ return manager.CompressBlock(_compressor!, input, output);
}
///<inheritdoc/>
@@ -69,19 +60,19 @@ namespace VNLib.Net.Http.Core.Compression
Debug.Assert(_compressor != null);
Debug.Assert(!output.IsEmpty, "Expected non-zero output buffer");
- return _provider.Flush(_compressor!, output);
+ return manager.Flush(_compressor!, output);
}
///<inheritdoc/>
public void Init(CompressionMethod compMethod)
{
//Defer alloc the compressor
- _compressor ??= _provider.AllocCompressor();
+ _compressor ??= manager.AllocCompressor();
Debug.Assert(_compressor != null);
//Init the compressor and get the block size
- BlockSize = _provider.InitCompressor(_compressor, compMethod);
+ BlockSize = manager.InitCompressor(_compressor, compMethod);
initialized = true;
}
@@ -94,7 +85,7 @@ namespace VNLib.Net.Http.Core.Compression
{
Debug.Assert(_compressor != null, "Compressor was initialized, exepcted a non null instance");
- _provider.DeinitCompressor(_compressor);
+ manager.DeinitCompressor(_compressor);
initialized = false;
}
}
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index 3216a9b..8cecf30 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -25,6 +25,7 @@
using System;
using System.IO;
using System.Text;
+using System.Diagnostics;
using System.Threading.Tasks;
using VNLib.Utils;
@@ -82,7 +83,7 @@ namespace VNLib.Net.Http.Core
private readonly ManagedHttpCompressor? _compressor;
private ITransportContext? _ctx;
- public HttpContext(HttpServer server)
+ public HttpContext(HttpServer server, CompressionMethod supportedMethods)
{
ParentServer = server;
@@ -91,11 +92,17 @@ namespace VNLib.Net.Http.Core
/*
* We can alloc a new compressor if the server supports compression.
* If no compression is supported, the compressor will never be accessed
+ * and never needs to be allocated
*/
- _compressor = server.SupportedCompressionMethods == CompressionMethod.None ?
- null :
- new ManagedHttpCompressor(server.Config.CompressorManager!);
-
+ if (supportedMethods != CompressionMethod.None)
+ {
+ Debug.Assert(server.Config.CompressorManager != null, "Expected non-null provider");
+ _compressor = new ManagedHttpCompressor(server.Config.CompressorManager);
+ }
+ else
+ {
+ _compressor = null;
+ }
//Init buffer manager, if compression is supported, we need to alloc a buffer for the compressor
Buffers = new(server.Config.BufferConfig, _compressor != null);
@@ -190,12 +197,9 @@ namespace VNLib.Net.Http.Core
//Release response/requqests
Response.OnRelease();
-
- //Zero before returning to pool
- Buffers.ZeroAll();
-
+
//Free buffers
- Buffers.FreeAll();
+ Buffers.FreeAll(true);
return true;
}
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs
index a73867f..f5f3563 100644
--- a/lib/Net.Http/src/Core/HttpServerBase.cs
+++ b/lib/Net.Http/src/Core/HttpServerBase.cs
@@ -41,7 +41,6 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Generic;
-using System.Security.Authentication;
using VNLib.Utils.Logging;
using VNLib.Utils.Memory.Caching;
@@ -141,9 +140,7 @@ namespace VNLib.Net.Http
//Configure roots and their directories
ServerRoots = sites.ToFrozenDictionary(static r => r.Hostname, static tv => tv, StringComparer.OrdinalIgnoreCase);
//Compile and store the timeout keepalive header
- KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}";
- //Create a new context store
- ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this));
+ KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}";
//Setup config copy with the internal http pool
Transport = transport;
//Cache supported compression methods, or none if compressor is null
@@ -151,6 +148,9 @@ namespace VNLib.Net.Http
CompressionMethod.None :
config.CompressorManager.GetSupportedMethods();
+ //Create a new context store
+ ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this, SupportedCompressionMethods));
+
//Cache wildcard root
_wildcardRoot = ServerRoots.GetValueOrDefault(WILDCARD_KEY);
@@ -270,20 +270,6 @@ namespace VNLib.Net.Http
}
/*
- * An SslStream may throw a win32 exception with HRESULT 0x80090327
- * when processing a client certificate (I believe anyway) only
- * an issue on some clients (browsers)
- */
-
- private const int UKNOWN_CERT_AUTH_HRESULT = unchecked((int)0x80090327);
-
- /// <summary>
- /// An invlaid frame size may happen if data is recieved on an open socket
- /// but does not contain valid SSL handshake data
- /// </summary>
- private const int INVALID_FRAME_HRESULT = unchecked((int)0x80131620);
-
- /*
* A worker task that listens for connections from the transport
*/
private async Task ListenWorkerDoWork()
@@ -301,7 +287,7 @@ namespace VNLib.Net.Http
//Listen for new connection
ITransportContext ctx = await Transport.AcceptAsync(StopToken!.Token);
- //Try to dispatch the recieved event
+ //Try to dispatch the received event
_ = DataReceivedAsync(ctx).ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -309,14 +295,6 @@ namespace VNLib.Net.Http
//Closing, exit loop
break;
}
- catch(AuthenticationException ae) when(ae.HResult == INVALID_FRAME_HRESULT)
- {
- _config.ServerLog.Debug("A TLS connection attempt was made but an invalid TLS frame was received");
- }
- catch (AuthenticationException ae)
- {
- _config.ServerLog.Error(ae);
- }
catch (Exception ex)
{
_config.ServerLog.Error(ex);
diff --git a/lib/Net.Http/src/Core/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
index 7c010a3..e36d1e4 100644
--- a/lib/Net.Http/src/Core/Request/HttpInputStream.cs
+++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -38,18 +38,14 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Specialized stream to allow reading a request entity body with a fixed content length.
/// </summary>
- internal sealed class HttpInputStream : Stream
+ internal sealed class HttpInputStream(IHttpContextInformation ContextInfo) : Stream
{
- private readonly IHttpContextInformation ContextInfo;
-
private long ContentLength;
private Stream? InputStream;
private long _position;
- private InitDataBuffer? _initalData;
-
- public HttpInputStream(IHttpContextInformation contextInfo) => ContextInfo = contextInfo;
+ private InitDataBuffer? _initalData;
private long Remaining => Math.Max(ContentLength - _position, 0);
@@ -170,17 +166,16 @@ namespace VNLib.Net.Http.Core
//Return number of bytes written to the buffer
return writer.Written;
- }
-
-
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
- }
+ }
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
- //Calculate the amount of data that can be read into the buffer
+ /*
+ * Iniitally I'm calculating the amount of data that can be read into
+ * the buffer, up to the maxium input data size. This value will clamp
+ * the buffer in the writer below, so it cannot read more than is
+ * available from the transport.
+ */
int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
if (bytesToRead == 0)
@@ -256,7 +251,7 @@ namespace VNLib.Net.Http.Core
read = await ReadAsync(HttpServer.WriteOnlyScratchBuffer, CancellationToken.None)
.ConfigureAwait(true);
- } while (read != 0);
+ } while (read > 0);
}
/// <summary>
diff --git a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
index cacce4c..31493ba 100644
--- a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
+++ b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
@@ -50,31 +50,14 @@ namespace VNLib.Net.Http.Core.Response
* Must always leave enough room for trailing crlf at the end of
* the buffer
*/
- private readonly int TotalMaxBufferSize => Buffer.Size - (int)Context.CrlfSegment.Length;
-
- /// <summary>
- /// Complets and returns the memory segment containing the chunk data to send
- /// to the client. This also resets the accumulator.
- /// </summary>
- /// <returns></returns>
- public readonly Memory<byte> GetChunkData(int accumulatedSize)
- {
- //Update the chunk size
- int reservedOffset = UpdateChunkSize(Buffer, Context, accumulatedSize);
- int endPtr = GetPointerToEndOfUsedBuffer(accumulatedSize);
-
- //Write trailing chunk delimiter
- endPtr += Context.CrlfSegment.DangerousCopyTo(Buffer, endPtr);
-
- return Buffer.GetMemory()[reservedOffset..endPtr];
- }
+ private readonly int TotalMaxBufferSize => Buffer.Size - Context.CrlfSegment.Length;
/// <summary>
/// Complets and returns the memory segment containing the chunk data to send
/// to the client.
/// </summary>
/// <returns></returns>
- public readonly Memory<byte> GetFinalChunkData(int accumulatedSize)
+ public readonly Memory<byte> GetChunkData(int accumulatedSize, bool isFinalChunk)
{
//Update the chunk size
int reservedOffset = UpdateChunkSize(Buffer, Context, accumulatedSize);
@@ -83,8 +66,11 @@ namespace VNLib.Net.Http.Core.Response
//Write trailing chunk delimiter
endPtr += Context.CrlfSegment.DangerousCopyTo(Buffer, endPtr);
- //Write final chunk to the end of the accumulator
- endPtr += Context.FinalChunkSegment.DangerousCopyTo(Buffer, endPtr);
+ if (isFinalChunk)
+ {
+ //Write final chunk to the end of the accumulator
+ endPtr += Context.FinalChunkSegment.DangerousCopyTo(Buffer, endPtr);
+ }
return Buffer.GetMemory()[reservedOffset..endPtr];
}
@@ -107,22 +93,6 @@ namespace VNLib.Net.Http.Core.Response
=> TotalMaxBufferSize - GetPointerToEndOfUsedBuffer(accumulatedSize);
- /*
- * Completed chunk is the segment of the buffer that contains the size segment
- * followed by the accumulated chunk data, and the trailing crlf.
- *
- * The accumulated data position is the number of chunk bytes accumulated
- * in the data segment. This does not include the number of reserved bytes
- * are before it.
- *
- * We can get the value that points to the end of the used buffer
- * and use the memory range operator to get the segment from the reserved
- * segment, to the actual end of the data segment.
- */
- private readonly Memory<byte> GetCompleteChunk(int reservedOffset, int accumulatedSize)
- => Buffer.GetMemory()[reservedOffset..accumulatedSize];
-
-
private static int GetPointerToEndOfUsedBuffer(int accumulatedSize) => accumulatedSize + ReservedSize;
/*
@@ -181,14 +151,10 @@ namespace VNLib.Net.Http.Core.Response
int reservedOffset = ReservedSize - totalChunkBufferBytes;
//Copy encoded chunk size to the reserved segment
- ref byte reservedSegRef = ref buffer.DangerousGetBinRef(reservedOffset);
- ref byte chunkSizeBufRef = ref MemoryMarshal.GetReference(chunkSizeBinBuffer);
-
- //We know the block is super small
MemoryUtil.SmallMemmove(
- ref chunkSizeBufRef,
+ in MemoryMarshal.GetReference(chunkSizeBinBuffer),
0,
- ref reservedSegRef,
+ ref buffer.DangerousGetBinRef(reservedOffset),
0,
(ushort)totalChunkBufferBytes
);
diff --git a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
index 918ffe1..dcd0553 100644
--- a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
+++ b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
@@ -45,22 +45,14 @@ namespace VNLib.Net.Http.Core
ValueTask discardTask = Request.InputStream.DiscardRemainingAsync();
- //See if response data needs to be written
+ //See if response data needs to be written, if so we can parallel discard and write
if (ResponseBody.HasData)
{
//Parallel the write and discard
Task response = WriteResponseInternalAsync();
- if (discardTask.IsCompletedSuccessfully)
- {
- //If discard is already complete, await the response
- await response;
- }
- else
- {
- //If discard is not complete, await both, avoid wait-all method because it will allocate
- await Task.WhenAll(discardTask.AsTask(), response);
- }
+ //in .NET 8.0 WhenAll is now allocation free, so no biggie
+ await Task.WhenAll(discardTask.AsTask(), response);
}
else
{
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
index 3f2ae56..ec9879b 100644
--- a/lib/Net.Http/src/Core/Response/HttpResponse.cs
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -39,16 +39,15 @@ using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core.Response
{
- internal sealed class HttpResponse : IHttpLifeCycle
+ internal sealed class HttpResponse(IHttpContextInformation ContextInfo, IHttpBufferManager manager) : IHttpLifeCycle
#if DEBUG
, IStringSerializeable
#endif
{
- private readonly IHttpContextInformation ContextInfo;
- private readonly HashSet<HttpCookie> Cookies;
- private readonly DirectStream ReusableDirectStream;
- private readonly ChunkedStream ReusableChunkedStream;
- private readonly HeaderDataAccumulator Writer;
+ private readonly HashSet<HttpCookie> Cookies = [];
+ private readonly DirectStream ReusableDirectStream = new();
+ private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ContextInfo);
+ private readonly HeaderDataAccumulator Writer = new(manager.ResponseHeaderBuffer, ContextInfo);
private int _headerWriterPosition;
@@ -60,29 +59,13 @@ namespace VNLib.Net.Http.Core.Response
/// <summary>
/// Response header collection
/// </summary>
- public VnWebHeaderCollection Headers { get; }
+ public VnWebHeaderCollection Headers { get; } = [];
/// <summary>
/// The current http status code value
/// </summary>
internal HttpStatusCode StatusCode => _code;
- public HttpResponse(IHttpContextInformation ctx, IHttpBufferManager manager)
- {
- ContextInfo = ctx;
-
- //Initialize a new header collection and a cookie jar
- Headers = new();
- Cookies = new();
-
- //Init header accumulator
- Writer = new(manager.ResponseHeaderBuffer, ContextInfo);
-
- //Create a new chunked stream
- ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ContextInfo);
- ReusableDirectStream = new();
- }
-
/// <summary>
/// Sets the status code of the response
/// </summary>
@@ -365,9 +348,9 @@ namespace VNLib.Net.Http.Core.Response
/// <summary>
/// Writes chunked HTTP message bodies to an underlying streamwriter
/// </summary>
- private sealed class ChunkedStream : ReusableResponseStream, IResponseDataWriter
+ private sealed class ChunkedStream(IChunkAccumulatorBuffer buffer, IHttpContextInformation context) : ReusableResponseStream, IResponseDataWriter
{
- private readonly ChunkDataAccumulator _chunkAccumulator;
+ private readonly ChunkDataAccumulator _chunkAccumulator = new(buffer, context);
/*
* Tracks the number of bytes accumulated in the
@@ -375,9 +358,6 @@ namespace VNLib.Net.Http.Core.Response
*/
private int _accumulatedBytes;
- public ChunkedStream(IChunkAccumulatorBuffer buffer, IHttpContextInformation context)
- => _chunkAccumulator = new(buffer, context);
-
#region Hooks
///<inheritdoc/>
@@ -402,11 +382,9 @@ namespace VNLib.Net.Http.Core.Response
* write the final termination sequence to the transport.
*/
- Memory<byte> chunkData = isFinal ?
- _chunkAccumulator.GetFinalChunkData(_accumulatedBytes) :
- _chunkAccumulator.GetChunkData(_accumulatedBytes);
+ Memory<byte> chunkData = _chunkAccumulator.GetChunkData(_accumulatedBytes, isFinal);
- //Reset accumulator
+ //Reset accumulator now that we captured the final chunk
_accumulatedBytes = 0;
//Write remaining data to stream
diff --git a/lib/Net.Http/src/Core/Response/HttpStreamResponse.cs b/lib/Net.Http/src/Core/Response/HttpStreamResponse.cs
new file mode 100644
index 0000000..b08d2ab
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HttpStreamResponse.cs
@@ -0,0 +1,50 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpStreamResponse.cs
+*
+* HttpStreamResponse.cs is part of VNLib.Net.Http which is part
+* of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This file handles response entity processing. It handles in-memory response
+ * 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.
+ */
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core.Response
+{
+ internal readonly struct HttpStreamResponse(Stream stream) : IHttpStreamResponse
+ {
+ ///<inheritdoc/>
+ public readonly void Dispose() => stream.Dispose();
+
+ ///<inheritdoc/>
+ public readonly ValueTask DisposeAsync() => stream.DisposeAsync();
+
+ ///<inheritdoc/>
+ public readonly ValueTask<int> ReadAsync(Memory<byte> buffer) => stream!.ReadAsync(buffer, CancellationToken.None);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/HttpstreamResponse.cs b/lib/Net.Http/src/Core/Response/HttpstreamResponse.cs
new file mode 100644
index 0000000..b08d2ab
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HttpstreamResponse.cs
@@ -0,0 +1,50 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpStreamResponse.cs
+*
+* HttpStreamResponse.cs is part of VNLib.Net.Http which is part
+* of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This file handles response entity processing. It handles in-memory response
+ * 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.
+ */
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core.Response
+{
+ internal readonly struct HttpStreamResponse(Stream stream) : IHttpStreamResponse
+ {
+ ///<inheritdoc/>
+ public readonly void Dispose() => stream.Dispose();
+
+ ///<inheritdoc/>
+ public readonly ValueTask DisposeAsync() => stream.DisposeAsync();
+
+ ///<inheritdoc/>
+ public readonly ValueTask<int> ReadAsync(Memory<byte> buffer) => stream!.ReadAsync(buffer, CancellationToken.None);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs b/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs
new file mode 100644
index 0000000..797d490
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/ResponsBodyDataState.cs
@@ -0,0 +1,100 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ResponseWriter.cs
+*
+* ResponseWriter.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This file handles response entity processing. It handles in-memory response
+ * 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.
+ */
+
+using System.IO;
+
+namespace VNLib.Net.Http.Core.Response
+{
+ internal readonly struct ResponsBodyDataState
+ {
+ /// <summary>
+ /// A value that inidcates if the response entity has been set
+ /// </summary>
+ public readonly bool IsSet;
+ /// <summary>
+ /// A value that indicates if the response entity requires buffering
+ /// </summary>
+ public readonly bool BufferRequired;
+ /// <summary>
+ /// The length (in bytes) of the response entity
+ /// </summary>
+ public readonly long Legnth;
+
+ public readonly IHttpStreamResponse? Stream;
+ public readonly IMemoryResponseReader? MemResponse;
+ public readonly Stream? RawStream;
+
+ private ResponsBodyDataState(IHttpStreamResponse stream, long length)
+ {
+ Legnth = length;
+ Stream = stream;
+ MemResponse = null;
+ RawStream = null;
+ IsSet = true;
+ BufferRequired = true;
+ }
+
+ private ResponsBodyDataState(IMemoryResponseReader reader)
+ {
+ Legnth = reader.Remaining;
+ MemResponse = reader;
+ Stream = null;
+ RawStream = null;
+ IsSet = true;
+ BufferRequired = false;
+ }
+
+ private ResponsBodyDataState(Stream stream, long length)
+ {
+ Legnth = length;
+ Stream = null;
+ MemResponse = null;
+ RawStream = stream;
+ IsSet = true;
+ BufferRequired = true;
+ }
+
+ internal readonly HttpStreamResponse GetRawStreamResponse() => new(RawStream!);
+
+ internal readonly void Dispose()
+ {
+ Stream?.Dispose();
+ MemResponse?.Close();
+ RawStream?.Dispose();
+ }
+
+ public static ResponsBodyDataState FromMemory(IMemoryResponseReader stream) => new(stream);
+
+ public static ResponsBodyDataState FromStream(IHttpStreamResponse stream, long length) => new(stream, length);
+
+ public static ResponsBodyDataState FromRawStream(Stream stream, long length) => new(stream, length);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/ResponseWriter.cs b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
index b5a167c..b60537d 100644
--- a/lib/Net.Http/src/Core/Response/ResponseWriter.cs
+++ b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
@@ -32,7 +32,6 @@
using System;
using System.IO;
using System.Diagnostics;
-using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
@@ -41,15 +40,13 @@ using VNLib.Net.Http.Core.Compression;
namespace VNLib.Net.Http.Core.Response
{
-
internal sealed class ResponseWriter : IHttpResponseBody
{
private ResponsBodyDataState _userState;
///<inheritdoc/>
public bool HasData => _userState.IsSet;
-
- //Buffering is required when a stream is set
+
///<inheritdoc/>
public bool BufferRequired => _userState.BufferRequired;
@@ -72,7 +69,7 @@ namespace VNLib.Net.Http.Core.Response
Debug.Assert(response != null, "Stream value is null, illegal operation");
Debug.Assert(length > -1, "explicit length passed a negative value, illegal operation");
- _userState = new(response, length);
+ _userState = ResponsBodyDataState.FromStream(response, length);
return true;
}
@@ -91,7 +88,7 @@ namespace VNLib.Net.Http.Core.Response
Debug.Assert(response != null, "Memory response argument was null and expected a value");
//Assign user-state
- _userState = new(response);
+ _userState = ResponsBodyDataState.FromMemory(response);
return true;
}
@@ -112,147 +109,144 @@ namespace VNLib.Net.Http.Core.Response
Debug.Assert(length > -1, "explicit length passed a negative value, illegal operation");
//Assign user-state
- _userState = new(rawStream, length);
+ _userState = ResponsBodyDataState.FromRawStream(rawStream, length);
return true;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void OnComplete()
+ {
+ //Clear response containers
+ _userState.Dispose();
+ _userState = default;
+
+ _readSegment = default;
+ }
+
+
private ReadOnlyMemory<byte> _readSegment;
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
///<inheritdoc/>
- public async Task WriteEntityAsync(IDirectResponsWriter dest, Memory<byte> buffer)
+ public Task WriteEntityAsync(IDirectResponsWriter dest, Memory<byte> buffer) => WriteEntityAsync(dest, buffer, 0);
+
+ ///<inheritdoc/>
+ public async Task WriteEntityAsync<TComp>(TComp compressor, IResponseDataWriter writer, Memory<byte> buffer)
+ where TComp : IResponseCompressor
{
- //Write a sliding window response
- if (_userState.MemResponse != null)
- {
- //Write response body from memory
- while (_userState.MemResponse.Remaining > 0)
- {
- //Get remaining segment
- _readSegment = _userState.MemResponse.GetMemory();
+ //Create a chunked response writer struct to pass to write async function
+ ChunkedResponseWriter<TComp> output = new(writer, compressor);
- //Write segment to output stream
- await dest.WriteAsync(_readSegment);
+ await WriteEntityAsync(output, buffer, compressor.BlockSize);
- //Advance by the written amount
- _userState.MemResponse.Advance(_readSegment.Length);
- }
+ /*
+ * Once there is no more response data avialable to compress
+ * we need to flush the compressor, then flush the writer
+ * to publish all accumulated data to the client
+ */
- //Disposing of memory response can be deferred until the end of the request since its always syncrhonous
- }
- else if(_userState.RawStream != null)
+ do
{
- Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
+ //Flush the compressor output
+ int written = compressor.Flush(writer.GetMemory());
- await ProcessStreamDataAsync(_userState.GetRawStreamResponse(), dest, buffer);
- }
- else
- {
- Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
+ //No more data to buffer
+ if (written == 0)
+ {
+ //final flush and exit
+ await writer.FlushAsync(true);
+ break;
+ }
- Debug.Assert(_userState.Stream != null, "Stream value is null, illegal operation");
+ if (writer.Advance(written) == 0)
+ {
+ //Flush because accumulator is full
+ await writer.FlushAsync(false);
+ }
- await ProcessStreamDataAsync(_userState.Stream!, dest, buffer);
- }
+ } while (true);
}
- ///<inheritdoc/>
- public async Task WriteEntityAsync<TComp>(TComp comp, IResponseDataWriter writer, Memory<byte> buffer) where TComp : IResponseCompressor
+ private async Task WriteEntityAsync<TResWriter>(TResWriter dest, Memory<byte> buffer, int blockSize)
+ where TResWriter : IDirectResponsWriter
{
//try to clamp the buffer size to the compressor block size
- int maxBufferSize = Math.Min(buffer.Length, comp.BlockSize);
- if(maxBufferSize > 0)
+ if (blockSize > 0)
{
- buffer = buffer[..maxBufferSize];
+ buffer = buffer[..Math.Min(buffer.Length, blockSize)];
}
- ChunkedResponseWriter<TComp> output = new(writer, comp);
-
//Write a sliding window response
if (_userState.MemResponse != null)
{
- while (_userState.MemResponse.Remaining > 0)
+ if (blockSize > 0)
+ {
+ while (_userState.MemResponse.Remaining > 0)
+ {
+ //Get next segment clamped to the block size
+ _readSegment = _userState.MemResponse.GetRemainingConstrained(blockSize);
+
+ //Commit output bytes
+ await dest.WriteAsync(_readSegment);
+
+ //Advance by the written amount
+ _userState.MemResponse.Advance(_readSegment.Length);
+ }
+ }
+ else
{
- //Get next segment
- _readSegment = comp.BlockSize > 0 ?
- _userState.MemResponse.GetRemainingConstrained(comp.BlockSize)
- : _userState.MemResponse.GetMemory();
+ //Write response body from memory
+ while (_userState.MemResponse.Remaining > 0)
+ {
+ //Get remaining segment
+ _readSegment = _userState.MemResponse.GetMemory();
- //Commit output bytes
- await output.WriteAsync(_readSegment);
+ //Write segment to output stream
+ await dest.WriteAsync(_readSegment);
- //Advance by the written amount
- _userState.MemResponse.Advance(_readSegment.Length);
+ //Advance by the written amount
+ _userState.MemResponse.Advance(_readSegment.Length);
+ }
}
//Disposing of memory response can be deferred until the end of the request since its always syncrhonous
}
- else if(_userState.RawStream != null)
+ else if (_userState.RawStream != null)
{
Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
- //Configure a raw stream response
- await ProcessStreamDataAsync(_userState.GetRawStreamResponse(), output, buffer);
+ await ProcessStreamDataAsync(_userState.GetRawStreamResponse(), dest, buffer, _userState.Legnth);
}
else
{
Debug.Assert(!buffer.IsEmpty, "Transfer buffer is required for streaming operations");
+ Debug.Assert(_userState.Stream != null, "Stream value is null, illegal state");
- Debug.Assert(_userState.Stream != null, "Stream value is null, illegal operation");
- await ProcessStreamDataAsync(_userState.Stream, output, buffer);
+ await ProcessStreamDataAsync(_userState.Stream, dest, buffer, _userState.Legnth);
}
-
-
- /*
- * Once there is no more response data avialable to compress
- * we need to flush the compressor, then flush the writer
- * to publish all accumulated data to the client
- */
-
- do
- {
- //Flush the compressor output
- int written = comp.Flush(writer.GetMemory());
-
- //No more data to buffer
- if (written == 0)
- {
- //final flush and exit
- await writer.FlushAsync(true);
- break;
- }
-
- if (writer.Advance(written) == 0)
- {
- //Flush because accumulator is full
- await writer.FlushAsync(false);
- }
-
- } while (true);
}
- private async Task ProcessStreamDataAsync<TStream, TWriter>(TStream stream, TWriter dest, Memory<byte> buffer)
- where TStream: IHttpStreamResponse
- where TWriter: IDirectResponsWriter
+ private static async Task ProcessStreamDataAsync<TStream, TWriter>(TStream stream, TWriter dest, Memory<byte> buffer, long length)
+ where TStream : IHttpStreamResponse
+ where TWriter : IDirectResponsWriter
{
/*
* When streams are used, callers will submit an explict length value
* which must be respected. This allows the stream size to differ from
* the actual content length. This is useful for when the stream is
- * non-seekable, or does not have a known length
+ * non-seekable, or does not have a known length. Also used for
+ * content-range responses, that are shorter than the whole stream.
*/
- long total = 0;
- while (total < Length)
+ long sentBytes = 0;
+ do
{
- //get offset wrapper of the total buffer or remaining count
- Memory<byte> offset = buffer[..(int)Math.Min(buffer.Length, Length - total)];
+ Memory<byte> offset = ClampCopyBuffer(buffer, length, sentBytes);
- //read
+ //read only the amount of data that is required
int read = await stream.ReadAsync(offset);
- //Guard
if (read == 0)
{
break;
@@ -261,110 +255,31 @@ namespace VNLib.Net.Http.Core.Response
//write only the data that was read (slice)
await dest.WriteAsync(offset[..read]);
- //Update total
- total += read;
- }
+ sentBytes += read;
+
+ } while (sentBytes < length);
//Try to dispose the response stream asyncrhonously since we are done with it
await stream.DisposeAsync();
}
-
- private static bool CompressNextSegment<TComp>(ref ForwardOnlyMemoryReader<byte> reader, TComp comp, IResponseDataWriter writer)
- where TComp: IResponseCompressor
+
+ private static Memory<byte> ClampCopyBuffer(Memory<byte> buffer, long contentLength, long sentBytes)
{
- //Get output buffer
- Memory<byte> output = writer.GetMemory();
-
- //Compress the trimmed block
- CompressionResult res = comp.CompressBlock(reader.Window, output);
- ValidateCompressionResult(in res);
-
- //Commit input bytes
- reader.Advance(res.BytesRead);
-
- return writer.Advance(res.BytesWritten) == 0;
+ //get offset wrapper of the total buffer or remaining count
+ int bufferSize = (int)Math.Min(buffer.Length, contentLength - sentBytes);
+ return buffer[..bufferSize];
}
[Conditional("DEBUG")]
- private static void ValidateCompressionResult(in CompressionResult result)
- {
+ private static void ValidateCompressionResult(in CompressionResult result, int segmentLen)
+ {
Debug.Assert(result.BytesRead > -1, "Compression result returned a negative bytes read value");
Debug.Assert(result.BytesWritten > -1, "Compression result returned a negative bytes written value");
+ Debug.Assert(result.BytesWritten <= segmentLen, "Compression result wrote more bytes than the input segment length");
}
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void OnComplete()
- {
- //Clear rseponse containers
- _userState.Dispose();
- _userState = default;
-
- _readSegment = default;
- }
-
-
- private readonly struct ResponsBodyDataState
- {
- public readonly bool IsSet;
- public readonly bool BufferRequired;
- public readonly long Legnth;
- public readonly IHttpStreamResponse? Stream;
- public readonly IMemoryResponseReader? MemResponse;
- public readonly Stream? RawStream;
-
- public ResponsBodyDataState(IHttpStreamResponse stream, long length)
- {
- Legnth = length;
- Stream = stream;
- MemResponse = null;
- RawStream = null;
- IsSet = true;
- BufferRequired = true;
- }
-
- public ResponsBodyDataState(IMemoryResponseReader reader)
- {
- Legnth = reader.Remaining;
- MemResponse = reader;
- Stream = null;
- RawStream = null;
- IsSet = true;
- BufferRequired = false;
- }
-
- public ResponsBodyDataState(Stream stream, long length)
- {
- Legnth = length;
- Stream = null;
- MemResponse = null;
- RawStream = stream;
- IsSet = true;
- BufferRequired = true;
- }
-
- public readonly HttpstreamResponse GetRawStreamResponse() => new(RawStream!);
-
- public readonly void Dispose()
- {
- Stream?.Dispose();
- MemResponse?.Close();
- RawStream?.Dispose();
- }
- }
-
- private readonly struct HttpstreamResponse(Stream stream) : IHttpStreamResponse
- {
- ///<inheritdoc/>
- public readonly void Dispose() => stream.Dispose();
-
- ///<inheritdoc/>
- public readonly ValueTask DisposeAsync() => stream.DisposeAsync();
-
- ///<inheritdoc/>
- public readonly ValueTask<int> ReadAsync(Memory<byte> buffer) => stream!.ReadAsync(buffer, CancellationToken.None);
- }
+
private readonly struct ChunkedResponseWriter<TComp>(IResponseDataWriter writer, TComp comp) : IDirectResponsWriter
where TComp : IResponseCompressor
@@ -378,7 +293,7 @@ namespace VNLib.Net.Http.Core.Response
do
{
//Compress the buffered data and flush if required
- if (CompressNextSegment(ref streamReader, comp, writer))
+ if (CompressNextSegment(ref streamReader))
{
//Time to flush
await writer.FlushAsync(false);
@@ -386,6 +301,21 @@ namespace VNLib.Net.Http.Core.Response
} while (streamReader.WindowSize > 0);
}
+
+ private readonly bool CompressNextSegment(ref ForwardOnlyMemoryReader<byte> reader)
+ {
+ //Get output buffer
+ Memory<byte> output = writer.GetMemory();
+
+ //Compress the trimmed block
+ CompressionResult res = comp.CompressBlock(reader.Window, output);
+ ValidateCompressionResult(in res, output.Length);
+
+ //Commit input bytes
+ reader.Advance(res.BytesRead);
+
+ return writer.Advance(res.BytesWritten) == 0;
+ }
}
}
} \ No newline at end of file
diff --git a/lib/Plugins.Essentials/src/Accounts/UserCreationRequest.cs b/lib/Plugins.Essentials/src/Accounts/UserCreationRequest.cs
index e346af1..2f2ba91 100644
--- a/lib/Plugins.Essentials/src/Accounts/UserCreationRequest.cs
+++ b/lib/Plugins.Essentials/src/Accounts/UserCreationRequest.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -22,6 +22,8 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
+
using VNLib.Utils.Memory;
using VNLib.Plugins.Essentials.Users;
@@ -40,7 +42,17 @@ namespace VNLib.Plugins.Essentials.Accounts
public ulong Privileges { get; init; } = AccountUtil.MINIMUM_LEVEL;
///<inheritdoc/>
- public string EmailAddress { get; init; } = string.Empty;
+ public string Username { get; init; } = string.Empty;
+
+ /// <summary>
+ /// Obsolete: Use the Username property instead
+ /// </summary>
+ [Obsolete("Use the Username property instead")]
+ public string EmailAddress
+ {
+ get => Username;
+ init => Username = value;
+ }
///<inheritdoc/>
public bool UseRawPassword { get; init; }
diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs
index 61a9b64..f052c56 100644
--- a/lib/Plugins.Essentials/src/EventProcessor.cs
+++ b/lib/Plugins.Essentials/src/EventProcessor.cs
@@ -188,8 +188,7 @@ namespace VNLib.Plugins.Essentials
//If the processor had an error recovering the session, return the result to the processor
if (entity.EventSessionHandle.EntityStatus != FileProcessArgs.Continue)
{
- ProcessFile(httpEvent, entity.EventSessionHandle.EntityStatus);
- return;
+ goto ProcessRoutine;
}
//Attach the new session to the entity
@@ -209,7 +208,7 @@ namespace VNLib.Plugins.Essentials
//Loop through nodes
while(mwNode != null)
{
- //Process
+ //Invoke mw handler on our event
entity.EventArgs = await mwNode.ValueRef.ProcessAsync(entity);
switch (entity.EventArgs.Routine)
@@ -237,7 +236,7 @@ namespace VNLib.Plugins.Essentials
//Process a virtual file
GetArgsFromVirtualReturn(entity, rt, out entity.EventArgs);
- //If the entity was processed, exit
+ //If the entity was processed by the handler, exit
if (entity.EventArgs != FileProcessArgs.Continue)
{
goto RespondAndExit;
@@ -275,8 +274,10 @@ namespace VNLib.Plugins.Essentials
}
}
+ ProcessRoutine:
+
//Finally process the file
- ProcessFile(httpEvent, in entity.EventArgs);
+ ProcessRoutine(httpEvent, in entity.EventArgs);
}
catch (ContentTypeUnacceptableException)
{
@@ -329,7 +330,7 @@ namespace VNLib.Plugins.Essentials
/// </summary>
/// <param name="entity">The entity to process the file for</param>
/// <param name="args">The selected <see cref="FileProcessArgs"/> to determine what file to process</param>
- protected virtual void ProcessFile(IHttpEvent entity, ref readonly FileProcessArgs args)
+ protected virtual void ProcessRoutine(IHttpEvent entity, ref readonly FileProcessArgs args)
{
try
{
@@ -413,8 +414,7 @@ namespace VNLib.Plugins.Essentials
//Get the content type of he file
ContentType fileType = HttpHelpers.GetContentTypeFromFile(filename);
-
- //Make sure the client accepts the content type
+
if (!entity.Server.Accepts(fileType))
{
//Unacceptable
@@ -729,7 +729,7 @@ namespace VNLib.Plugins.Essentials
WeakReference<object?> wr = Volatile.Read(ref _objects[tableIndex]);
//Try to get the object instance
- wr.TryGetTarget(out object? value);
+ _ = wr.TryGetTarget(out object? value);
instance = (T?)value;
}
diff --git a/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs b/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs
index 92fae08..a99b1ab 100644
--- a/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs
+++ b/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs
@@ -354,6 +354,7 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <param name="domain"></param>
/// <param name="path"></param>
/// <param name="sameSite"></param>
+ /// <param name="expires"></param>
/// <param name="httpOnly"></param>
/// <param name="secure"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
index f49af32..5f59346 100644
--- a/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
+++ b/lib/Plugins.Essentials/src/Extensions/EssentialHttpEventExtensions.cs
@@ -163,6 +163,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseJson(this IHttpEvent ev, HttpStatusCode code, JsonDocument data)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if(data == null)
{
ev.CloseResponse(code);
@@ -195,7 +197,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse<T>(this IHttpEvent ev, T webm) where T:WebMessage
{
- if (webm == null)
+ ArgumentNullException.ThrowIfNull(ev);
+
+ if (webm is null)
{
ev.CloseResponse(HttpStatusCode.OK);
}
@@ -220,6 +224,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseAttachment(this IHttpEvent ev, HttpStatusCode code, FileInfo file)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(file);
+
//Close with file
ev.CloseResponse(code, file);
//Set content dispostion as attachment (only if successfull)
@@ -236,6 +243,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseAttachment(this IHttpEvent ev, HttpStatusCode code, FileStream file)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(file);
+
//Close with file
ev.CloseResponse(code, file);
//Set content dispostion as attachment (only if successfull)
@@ -255,6 +265,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponseAttachment(this IHttpEvent ev, HttpStatusCode code, ContentType ct, Stream data, string fileName, long length)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(data);
+
//Close with file
ev.CloseResponse(code, ct, data, length);
//Set content dispostion as attachment (only if successfull)
@@ -275,6 +288,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, FileInfo file)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(file);
+
//Open filestream for file
FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
try
@@ -302,13 +318,16 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, FileStream file)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(file);
+
//Get content type from filename
ContentType ct = HttpHelpers.GetContentTypeFromFile(file.Name);
//Set the input as a stream
ev.CloseResponse(code, ct, file, file.Length);
}
-
+
/// <summary>
/// Close a response to a connection with a character buffer using the server wide
/// <see cref="ConnectionInfo.Encoding"/> encoding
@@ -321,12 +340,10 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ContentTypeUnacceptableException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, ContentType type, ReadOnlySpan<char> data)
- {
+ public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, ContentType type, ReadOnlySpan<char> data) =>
//Get a memory stream using server built-in encoding
CloseResponse(ev, code, type, data, ev.Server.Encoding);
- }
-
+
/// <summary>
/// Close a response to a connection with a character buffer using the specified encoding type
/// </summary>
@@ -339,6 +356,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, ContentType type, ReadOnlySpan<char> data, Encoding encoding)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (data.IsEmpty)
{
ev.CloseResponse(code);
@@ -367,6 +386,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CloseResponse(this IHttpEvent ev, HttpStatusCode code, ContentType type, ReadOnlySpan<byte> data)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (data.IsEmpty)
{
ev.CloseResponse(code);
@@ -391,6 +412,9 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CloseWithRelativeFile(this HttpEntity entity, HttpStatusCode code, string filePath)
{
+ ArgumentNullException.ThrowIfNull(entity);
+ ArgumentNullException.ThrowIfNull(filePath);
+
//See if file exists and is within the root's directory
if (entity.RequestedRoot.FindResourceInRoot(filePath, out string realPath))
{
@@ -412,11 +436,9 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <remarks>Sets required headers for redirection, disables cache control, and returns the status code to the client</remarks>
/// <exception cref="UriFormatException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Redirect(this IHttpEvent ev, RedirectType type, string location)
- {
- Redirect(ev, type, new Uri(location, UriKind.RelativeOrAbsolute));
- }
-
+ public static void Redirect(this IHttpEvent ev, RedirectType type, string location)
+ => Redirect(ev, type, new Uri(location, UriKind.RelativeOrAbsolute));
+
/// <summary>
/// Redirects a client using the specified <see cref="RedirectType"/>
/// </summary>
@@ -427,6 +449,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Redirect(this IHttpEvent ev, RedirectType type, Uri location)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(location);
if(type == RedirectType.None)
{
@@ -472,6 +496,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetJsonFromArg<T>(this IHttpEvent ev, string key, JsonSerializerOptions options, out T? obj)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
//Check for key in argument
if (ev.RequestArgs.TryGetNonEmptyValue(key, out string? value))
{
@@ -502,6 +528,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsonDocument? GetJsonFromArg(this IHttpEvent ev, string key, in JsonDocumentOptions options = default)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
try
{
//Check for key in argument
@@ -528,6 +556,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T? GetJsonFromFile<T>(this IHttpEvent ev, JsonSerializerOptions? options = null, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (ev.Files.Count <= uploadIndex)
{
return default;
@@ -563,6 +593,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsonDocument? GetJsonFromFile(this IHttpEvent ev, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (ev.Files.Count <= uploadIndex)
{
return default;
@@ -599,6 +631,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueTask<T?> GetJsonFromFileAsync<T>(this HttpEntity ev, JsonSerializerOptions? options = null, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (ev.Files.Count <= uploadIndex)
{
return ValueTask.FromResult<T?>(default);
@@ -640,6 +674,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<JsonDocument?> GetJsonFromFileAsync(this HttpEntity ev, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+
if (ev.Files.Count <= uploadIndex)
{
return DocTaskDefault;
@@ -677,6 +713,9 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="IndexOutOfRangeException"></exception>
public static Task<T?> ParseFileAsAsync<T>(this IHttpEvent ev, Func<Stream, Task<T?>> parser, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(parser);
+
if (ev.Files.Count <= uploadIndex)
{
return Task.FromResult<T?>(default);
@@ -698,6 +737,9 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="IndexOutOfRangeException"></exception>
public static Task<T?> ParseFileAsAsync<T>(this IHttpEvent ev, Func<Stream, string, Task<T?>> parser, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(parser);
+
if (ev.Files.Count <= uploadIndex)
{
return Task.FromResult<T?>(default);
@@ -720,6 +762,9 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="IndexOutOfRangeException"></exception>
public static ValueTask<T?> ParseFileAsAsync<T>(this IHttpEvent ev, Func<Stream, ValueTask<T?>> parser, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(parser);
+
if (ev.Files.Count <= uploadIndex)
{
return ValueTask.FromResult<T?>(default);
@@ -741,6 +786,9 @@ namespace VNLib.Plugins.Essentials.Extensions
/// <exception cref="IndexOutOfRangeException"></exception>
public static ValueTask<T?> ParseFileAsAsync<T>(this IHttpEvent ev, Func<Stream, string, ValueTask<T?>> parser, int uploadIndex = 0)
{
+ ArgumentNullException.ThrowIfNull(ev);
+ ArgumentNullException.ThrowIfNull(parser);
+
if (ev.Files.Count <= uploadIndex)
{
return ValueTask.FromResult<T?>(default);
@@ -760,6 +808,8 @@ namespace VNLib.Plugins.Essentials.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasAuthorization(this IConnectionInfo ci, [NotNullWhen(true)] out string? token)
{
+ ArgumentNullException.ThrowIfNull(ci);
+
//Get auth header value
string? authorization = ci.Headers[HttpRequestHeader.Authorization];
//Check if its set
@@ -767,7 +817,7 @@ namespace VNLib.Plugins.Essentials.Extensions
{
int bearerIndex = authorization.IndexOf(BEARER_STRING, StringComparison.OrdinalIgnoreCase);
//Calc token offset, get token, and trim any whitespace
- token = authorization[(bearerIndex + BEARER_LEN)..].Trim();
+ token = authorization.AsSpan(bearerIndex + BEARER_LEN).Trim().ToString();
return true;
}
token = null;
@@ -819,11 +869,9 @@ namespace VNLib.Plugins.Essentials.Extensions
)
{
//Must define an accept callback
- _ = socketOpenedCallback ?? throw new ArgumentNullException(nameof(socketOpenedCallback));
-
- bool success = PrepWebSocket(entity, subProtocol);
+ ArgumentNullException.ThrowIfNull(socketOpenedCallback);
- if (success)
+ if (PrepWebSocket(entity, subProtocol))
{
//Set a default keep alive if none was specified
if (keepAlive == default)
@@ -841,8 +889,11 @@ namespace VNLib.Plugins.Essentials.Extensions
//Setup a new websocket session with a new session id
entity.DangerousChangeProtocol(ws);
+
+ return true;
}
- return success;
+
+ return false;
}
/// <summary>
@@ -858,11 +909,10 @@ namespace VNLib.Plugins.Essentials.Extensions
public static bool AcceptWebSocket(this IHttpEvent entity, WebSocketAcceptedCallback socketOpenedCallback, string? subProtocol = null, TimeSpan keepAlive = default)
{
//Must define an accept callback
- _ = socketOpenedCallback ?? throw new ArgumentNullException(nameof(socketOpenedCallback));
+ ArgumentNullException.ThrowIfNull(entity);
+ ArgumentNullException.ThrowIfNull(socketOpenedCallback);
- bool success = PrepWebSocket(entity, subProtocol);
-
- if(success)
+ if(PrepWebSocket(entity, subProtocol))
{
//Set a default keep alive if none was specified
if (keepAlive == default)
@@ -879,15 +929,19 @@ namespace VNLib.Plugins.Essentials.Extensions
//Setup a new websocket session with a new session id
entity.DangerousChangeProtocol(ws);
+
+ return true;
}
- return success;
+ return false;
}
private static string GetNewSocketId() => Guid.NewGuid().ToString("N");
private static bool PrepWebSocket(this IHttpEvent entity, string? subProtocol = null)
{
+ ArgumentNullException.ThrowIfNull(entity);
+
//Make sure this is a websocket request
if (!entity.Server.IsWebSocketRequest)
{
diff --git a/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs b/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs
index a235b13..e1706f4 100644
--- a/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs
+++ b/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs
@@ -28,6 +28,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using VNLib.Net.Http;
using VNLib.Plugins.Essentials.Endpoints;
@@ -65,6 +66,7 @@ namespace VNLib.Plugins.Essentials
new Dictionary<string, IVirtualEndpoint<HttpEntity>>(StringComparer.OrdinalIgnoreCase)
.ToFrozenDictionary();
+ private bool _isEmpty = true;
/*
* A lock that is held by callers that intend to
@@ -73,14 +75,14 @@ namespace VNLib.Plugins.Essentials
private readonly object VeUpdateLock = new();
///<inheritdoc/>
- public bool IsEmpty => VirtualEndpoints.Count == 0;
+ public bool IsEmpty => _isEmpty;
///<inheritdoc/>
public void AddEndpoint(params IEndpoint[] endpoints)
{
//Check
- _ = endpoints ?? throw new ArgumentNullException(nameof(endpoints));
+ ArgumentNullException.ThrowIfNull(endpoints);
//Make sure all endpoints specify a path
if (endpoints.Any(static e => string.IsNullOrWhiteSpace(e?.Path)))
{
@@ -105,6 +107,7 @@ namespace VNLib.Plugins.Essentials
//Uinion endpoints by their paths to combine them
IEnumerable<IVirtualEndpoint<HttpEntity>> allEndpoints = eps.UnionBy(evs, static s => s.Path);
+ //Only allow 1 thread at a time to mutate the table
lock (VeUpdateLock)
{
//Clone the current dictonary
@@ -115,10 +118,11 @@ namespace VNLib.Plugins.Essentials
newTable.Add(ep.Path, ep);
}
- FrozenDictionary<string, IVirtualEndpoint<HttpEntity>> newTableFrozen = newTable.ToFrozenDictionary();
+ //Update is-empty flag
+ _isEmpty = newTable.Count == 0;
- //Store the new table
- _ = Interlocked.Exchange(ref VirtualEndpoints, newTableFrozen);
+ //Create the new table and store the entire table
+ _ = Interlocked.Exchange(ref VirtualEndpoints, newTable.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase));
}
}
@@ -157,24 +161,27 @@ namespace VNLib.Plugins.Essentials
_ = newTable.Remove(eps);
}
- FrozenDictionary<string, IVirtualEndpoint<HttpEntity>> newTableFrozen = newTable.ToFrozenDictionary();
+ //Update is-empty flag
+ _isEmpty = newTable.Count == 0;
//Store the new table
- _ = Interlocked.Exchange(ref VirtualEndpoints, newTableFrozen);
+ _ = Interlocked.Exchange(ref VirtualEndpoints, newTable.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase));
}
}
///<inheritdoc/>
- public bool TryGetEndpoint(string path, out IVirtualEndpoint<HttpEntity>? endpoint) => VirtualEndpoints.TryGetValue(path, out endpoint);
+ public bool TryGetEndpoint(string path, [NotNullWhen(true)] out IVirtualEndpoint<HttpEntity>? endpoint)
+ => VirtualEndpoints.TryGetValue(path, out endpoint);
/*
* Wrapper class for converting IHttpEvent endpoints to
* httpEntityEndpoints
*/
- private sealed record class EvEndpointWrapper(IVirtualEndpoint<IHttpEvent> Wrapped) : IVirtualEndpoint<HttpEntity>
+ private sealed class EvEndpointWrapper(IVirtualEndpoint<IHttpEvent> Wrapped) : IVirtualEndpoint<HttpEntity>
{
string IEndpoint.Path => Wrapped.Path;
+
ValueTask<VfReturnType> IVirtualEndpoint<HttpEntity>.Process(HttpEntity entity) => Wrapped.Process(entity);
}
}
diff --git a/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs b/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
index d3fc475..05d6712 100644
--- a/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
+++ b/lib/Plugins.Essentials/src/Sessions/ISessionExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -49,7 +49,7 @@ namespace VNLib.Plugins.Essentials.Sessions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetOrigin(this ISession session) => session[ORIGIN_ENTRY];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Uri GetOriginUri(this ISession session) => Uri.TryCreate(session[ORIGIN_ENTRY], UriKind.Absolute, out Uri origin) ? origin : null;
+ public static Uri? GetOriginUri(this ISession session) => Uri.TryCreate(session[ORIGIN_ENTRY], UriKind.Absolute, out Uri? origin) ? origin : null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetOrigin(this ISession session, string origin) => session[ORIGIN_ENTRY] = origin;
diff --git a/lib/Plugins.Essentials/src/Users/IUserCreationRequest.cs b/lib/Plugins.Essentials/src/Users/IUserCreationRequest.cs
index a5b9a30..bd89be1 100644
--- a/lib/Plugins.Essentials/src/Users/IUserCreationRequest.cs
+++ b/lib/Plugins.Essentials/src/Users/IUserCreationRequest.cs
@@ -44,9 +44,9 @@ namespace VNLib.Plugins.Essentials.Users
ulong Privileges { get; }
/// <summary>
- /// The user's email address
+ /// The user's unique username (may also be an email address
/// </summary>
- string EmailAddress { get; }
+ string Username { get; }
/// <summary>
/// Should the password be stored as-is in the database?
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index ed8ed5a..ada2c64 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -88,6 +88,7 @@ namespace VNLib.Utils.IO
/// <returns>The readonly stream</returns>
public static VnMemoryStream CreateReadonly(VnMemoryStream stream)
{
+ ArgumentNullException.ThrowIfNull(stream);
//Set the readonly flag
stream._isReadonly = true;
//Return the stream
@@ -528,10 +529,19 @@ namespace VNLib.Utils.IO
/// <exception cref="OutOfMemoryException"></exception>
public byte[] ToArray()
{
- //Alloc a new array of the size of the internal buffer, may be 64 bit large block
- byte[] data = new byte[_length];
-
- //Copy the internal buffer to the new array
+ byte[] data;
+
+ if (_length < Int32.MaxValue)
+ {
+ //Alloc uninialized, since were going to overwite it anyway
+ data = GC.AllocateUninitializedArray<byte>((int)_length, false);
+ }
+ else
+ {
+ //Use new opperator if larger than 32bit
+ data = new byte[_length];
+ }
+
MemoryUtil.CopyArray(_buffer, 0, data, 0, (nuint)_length);
return data;
diff --git a/lib/Utils/src/Memory/Caching/IReusable.cs b/lib/Utils/src/Memory/Caching/IReusable.cs
index 4472ad3..cf73fa0 100644
--- a/lib/Utils/src/Memory/Caching/IReusable.cs
+++ b/lib/Utils/src/Memory/Caching/IReusable.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -25,7 +25,7 @@
namespace VNLib.Utils.Memory.Caching
{
/// <summary>
- /// Allows for use within a <see cref="ObjectRental{T}"/>, this object is intended to be reused heavily
+ /// An interface that exposes event hooks for use within an <see cref="ObjectRental{T}"/>, managment.
/// </summary>
public interface IReusable
{
diff --git a/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs b/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
index bcc5be9..f196597 100644
--- a/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
@@ -74,7 +74,7 @@ namespace VNLib.Utils.Memory
public static bool RequiresPinning(nuint byteSize, bool forceAcceleration)
{
/*
- * Pinnin is required, if reflected memmove is not supported on the platform
+ * Pinning is required, if reflected memmove is not supported on the platform
* AND the size of the data to copy is larger than 32 bit width.
*
* Otherwise if accleration is forced, pinning will always be required.
@@ -94,9 +94,9 @@ namespace VNLib.Utils.Memory
}
/*
- * Why does this function exist. For centralized memmove operations primarily.
+ * Why does this function exist? For centralized memmove operations primarily.
*
- * When the block is known to be small, all of the brances in memmove can be
+ * When the block is known to be small, all of the branches in memmove can be
* alot of overhead including the possability of Avx2 being used for really
* small blocks if they are aligned. If the block is known to be small, we
* can just skip all of that and use the fastest method for small blocks,
@@ -118,7 +118,6 @@ namespace VNLib.Utils.Memory
Debug.Assert(!Unsafe.IsNullRef(in dstByte), "Null destination reference passed to MemmoveByRef");
_fallbackMemmove.Memmove(in srcByte, ref dstByte, byteCount);
- return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 774aca3..ba25104 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -32,7 +32,6 @@ using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics.X86;
-using VNLib.Utils.Resources;
using VNLib.Utils.Memory.Diagnostics;
namespace VNLib.Utils.Memory
@@ -86,6 +85,11 @@ namespace VNLib.Utils.Memory
/// that will use the array pool before falling back to the <see cref="Shared"/>.
/// heap.
/// </summary>
+ /// <remarks>
+ /// This value is chosen to be just under the size the CLR will promote an array to the
+ /// LOH, we can assume any heap impl will have long-term performance than the LOH for
+ /// large allocations.
+ /// </remarks>
public const int MAX_UNSAFE_POOL_SIZE = 80 * 1024;
//Cache the system page size
@@ -314,7 +318,11 @@ namespace VNLib.Utils.Memory
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InitializeBlock<T>(T[] array) where T : struct => InitializeBlock(array, (uint)array.Length);
+ public static void InitializeBlock<T>(T[] array) where T : struct
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ InitializeBlock(array, (uint)array.Length);
+ }
/// <summary>
/// Initializes the array with zeros up to the specified count
@@ -327,7 +335,7 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitializeBlock<T>(T[] array, uint count) where T: struct
{
- ArgumentNullException.ThrowIfNull(array, nameof(array));
+ ArgumentNullException.ThrowIfNull(array);
//Check bounds
CheckBounds(array, 0, count);
@@ -348,7 +356,7 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void InitializeBlock<T>(ref T block, int itemCount) where T : struct
{
- ThrowIfNullRef(ref block, nameof(block));
+ ThrowIfNullRef(in block, nameof(block));
if (itemCount <= 0)
{
@@ -406,8 +414,7 @@ namespace VNLib.Utils.Memory
public static void ZeroStruct<T>(T* structPtr) where T : unmanaged
{
ArgumentNullException.ThrowIfNull(structPtr);
-
- ZeroStruct(ref *structPtr);
+ ZeroStruct(ref Unsafe.AsRef<T>(structPtr));
}
/// <summary>
@@ -431,14 +438,6 @@ namespace VNLib.Utils.Memory
#region Copy
- /*
- * Dirty little trick to access internal Buffer.Memmove method for
- * large references. May not always be supported, so optional safe
- * guards are in place.
- */
- private delegate void BigMemmove(ref byte dest, ref readonly byte src, nuint len);
- private static readonly BigMemmove? _clrMemmove = ManagedLibrary.TryGetStaticMethod<BigMemmove>(typeof(Buffer), "Memmove", System.Reflection.BindingFlags.NonPublic);
-
/// <summary>
/// Copies structure data from a source byte reference that points to a sequence of
/// of data to the target structure reference.
@@ -626,8 +625,11 @@ namespace VNLib.Utils.Memory
{
ArgumentNullException.ThrowIfNull(source);
ArgumentOutOfRangeException.ThrowIfLessThan(target.Length, sizeof(T), nameof(target));
-
- CopyStruct(ref *source, target);
+
+ CopyStruct(
+ ref Unsafe.AsRef<T>(source),
+ target
+ );
}
/// <summary>
@@ -677,7 +679,7 @@ namespace VNLib.Utils.Memory
Unsafe.CopyBlockUnaligned(
ref Refs.AsByte(ref target, 0),
- ref Refs.AsByteR(in source, 0),
+ in Refs.AsByteR(in source, 0),
(uint)sizeof(T)
);
}
@@ -1354,7 +1356,7 @@ namespace VNLib.Utils.Memory
/// <param name="size">The size of the span (the size of the block)</param>
/// <returns>A span over the block of memory pointed to by the handle of the specified size</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span<T> GetSpan<T>(ref MemoryHandle handle, int size) => new(handle.Pointer, size);
+ public static Span<T> GetSpan<T>(ref readonly MemoryHandle handle, int size) => new(handle.Pointer, size);
/// <summary>
/// Gets a <see cref="Span{T}"/> over the block of memory pointed to by the supplied handle.
diff --git a/lib/Utils/src/Memory/ProcessHeap.cs b/lib/Utils/src/Memory/ProcessHeap.cs
index 35a2a71..f13181b 100644
--- a/lib/Utils/src/Memory/ProcessHeap.cs
+++ b/lib/Utils/src/Memory/ProcessHeap.cs
@@ -54,10 +54,7 @@ namespace VNLib.Utils.Memory
/// <summary>
/// Initalizes a new global (cross platform) process heap
/// </summary>
- public ProcessHeap()
- {
- Trace.WriteLine($"Default heap instnace created {GetHashCode():x}");
- }
+ public ProcessHeap() => Trace.WriteLine($"Default heap instnace created {GetHashCode():x}");
///<inheritdoc/>
///<exception cref="OverflowException"></exception>
@@ -78,13 +75,10 @@ namespace VNLib.Utils.Memory
return true;
}
-
+
///<inheritdoc/>
- protected override void Free()
- {
- Trace.WriteLine($"Default heap instnace disposed {GetHashCode():x}");
- }
-
+ protected override void Free() => Trace.WriteLine($"Default heap instnace disposed {GetHashCode():x}");
+
///<inheritdoc/>
///<exception cref="OverflowException"></exception>
///<exception cref="OutOfMemoryException"></exception>
diff --git a/lib/Utils/src/Native/SafeLibraryHandle.cs b/lib/Utils/src/Native/SafeLibraryHandle.cs
index 6b1af00..ed9dd16 100644
--- a/lib/Utils/src/Native/SafeLibraryHandle.cs
+++ b/lib/Utils/src/Native/SafeLibraryHandle.cs
@@ -150,11 +150,19 @@ namespace VNLib.Utils.Native
private static string? GetLibraryFile(string dirPath, string libPath, SearchOption search)
{
- //slice the lib to its file name
- libPath = Path.GetFileName(libPath);
- libPath = Path.ChangeExtension(libPath, OperatingSystem.IsWindows() ? ".dll" : ".so");
- //Select the first file that matches the name
- return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
+ //If the library path already has an extension, just search for the file
+ if (Path.HasExtension(libPath))
+ {
+ return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
+ }
+ else
+ {
+ //slice the lib to its file name
+ libPath = Path.GetFileName(libPath);
+ libPath = Path.ChangeExtension(libPath, OperatingSystem.IsWindows() ? ".dll" : ".so");
+ //Select the first file that matches the name
+ return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault();
+ }
}
/// <summary>
@@ -173,7 +181,7 @@ namespace VNLib.Utils.Native
bool success = false;
DangerousAddRef(ref success);
- ObjectDisposedException.ThrowIf(success == false, "The libary has been released!");
+ ObjectDisposedException.ThrowIf(success == false, this);
try
{