aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Taskfile.yaml3
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2Context.cs (renamed from lib/Hashing.Portable/src/Argon2/Argon2_Context.cs)20
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2CostParams.cs53
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs130
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2Type.cs33
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2Version.cs33
-rw-r--r--lib/Hashing.Portable/src/Argon2/IArgon2Library.cs44
-rw-r--r--lib/Hashing.Portable/src/Argon2/SafeArgon2Library.cs85
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs326
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs2
-rw-r--r--lib/Plugins.Essentials/src/Accounts/AccountUtils.cs24
-rw-r--r--lib/Plugins.Essentials/src/Accounts/Argon2ConfigParams.cs66
-rw-r--r--lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs164
-rw-r--r--lib/Plugins.Essentials/src/Sessions/ISession.cs14
-rw-r--r--lib/Plugins.Essentials/src/Sessions/SessionBase.cs5
-rw-r--r--lib/Plugins.Essentials/src/Sessions/SessionInfo.cs14
-rw-r--r--lib/Plugins.Essentials/src/Users/IUser.cs13
-rw-r--r--lib/Plugins.Essentials/src/Users/IUserManager.cs4
-rw-r--r--lib/Plugins.PluginBase/src/PluginBase.cs14
-rw-r--r--lib/Utils/src/ArgumentList.cs101
-rw-r--r--lib/Utils/src/Async/AsyncUpdatableResource.cs41
-rw-r--r--lib/Utils/src/Async/IAsyncExclusiveResource.cs6
-rw-r--r--lib/Utils/src/Async/IAsyncResourceStateHandler.cs53
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs5
-rw-r--r--lib/Utils/src/Memory/Caching/LRUCache.cs14
-rw-r--r--lib/Utils/src/Memory/Caching/LRUDataStore.cs22
-rw-r--r--lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs19
-rw-r--r--lib/Utils/src/Resources/BackedResourceBase.cs38
-rw-r--r--lib/Utils/src/Resources/IResourceStateHandler.cs47
-rw-r--r--lib/Utils/src/Resources/UpdatableResource.cs46
-rw-r--r--lib/Utils/src/VnEncoding.cs8
-rw-r--r--lib/Utils/tests/.runsettings7
32 files changed, 1023 insertions, 431 deletions
diff --git a/Taskfile.yaml b/Taskfile.yaml
index b16b082..2f12d1f 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -68,8 +68,7 @@ tasks:
#Remove the output dirs on clean
clean:
dir: '{{.USER_WORKING_DIR}}'
+ ignore_error: true
cmds:
- cmd: powershell Remove-Item -Recurse './bin'
- ignore_error: true
- cmd: powershell Remove-Item -Recurse './obj'
- ignore_error: true
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2_Context.cs b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs
index 78d0f13..4631654 100644
--- a/lib/Hashing.Portable/src/Argon2/Argon2_Context.cs
+++ b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs
@@ -1,11 +1,11 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Hashing.Portable
-* File: Argon2_Context.cs
+* File: Argon2Context.cs
*
-* Argon2_Context.cs is part of VNLib.Hashing.Portable which is part of the larger
+* Argon2Context.cs is part of VNLib.Hashing.Portable which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
@@ -22,16 +22,6 @@
* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
*/
-/*
- * VnArgon2.cs
- * Author: Vaughhn Nugent
- * Date: July 17, 2021
- *
- * Dependencies Argon2.
- * https://github.com/P-H-C/phc-winner-argon2
- *
- */
-
using System;
using System.Runtime.InteropServices;
@@ -41,7 +31,7 @@ namespace VNLib.Hashing
public static unsafe partial class VnArgon2
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- private ref struct Argon2_Context
+ private ref struct Argon2Context
{
public void* outptr; /* output array */
public UInt32 outlen; /* digest length */
@@ -63,7 +53,7 @@ namespace VNLib.Hashing
public UInt32 lanes; /* number of lanes */
public UInt32 threads; /* maximum number of threads */
- public Argon2_version version; /* version number */
+ public Argon2Version version; /* version number */
public void* allocate_cbk; /* pointer to memory allocator */
public void* free_cbk; /* pointer to memory deallocator */
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2CostParams.cs b/lib/Hashing.Portable/src/Argon2/Argon2CostParams.cs
new file mode 100644
index 0000000..d016f1f
--- /dev/null
+++ b/lib/Hashing.Portable/src/Argon2/Argon2CostParams.cs
@@ -0,0 +1,53 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: Argon2CostParams.cs
+*
+* Argon2CostParams.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Hashing
+{
+ /// <summary>
+ /// Stores Argon2 hashing cost parameters
+ /// </summary>
+ public readonly ref struct Argon2CostParams
+ {
+ /// <summary>
+ /// Initializes a new structure of <see cref="Argon2CostParams"/> with the specified parameters
+ /// </summary>
+ public Argon2CostParams()
+ { }
+
+ /// <summary>
+ /// Argon2 hash time cost parameter
+ /// </summary>
+ public readonly uint TimeCost { get; init; } = 2;
+
+ /// <summary>
+ /// Argon2 hash memory cost parameter
+ /// </summary>
+ public readonly uint MemoryCost { get; init; } = 65535;
+
+ /// <summary>
+ /// Argon2 hash parallelism parameter
+ /// </summary>
+ public readonly uint Parallelism { get; init; } = 4;
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs
index b45cceb..f99d0dc 100644
--- a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs
+++ b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Hashing.Portable
@@ -30,78 +30,82 @@ using VNLib.Utils.Extensions;
namespace VNLib.Hashing
{
- public static unsafe partial class VnArgon2
+ internal readonly ref struct Argon2PasswordEntry
{
- private readonly ref struct Argon2PasswordEntry
+ private readonly ReadOnlySpan<char> _window;
+
+ public readonly Argon2Version Version;
+ public readonly ReadOnlySpan<char> Salt;
+ public readonly ReadOnlySpan<char> Hash;
+
+ private static Argon2Version ParseVersion(ReadOnlySpan<char> window)
{
- public readonly uint TimeCost;
- public readonly uint MemoryCost;
- public readonly Argon2_version Version;
- public readonly uint Parallelism;
- public readonly ReadOnlySpan<char> Salt;
- public readonly ReadOnlySpan<char> Hash;
+ //Version comes after the v= prefix
+ ReadOnlySpan<char> v = window.SliceAfterParam("v=");
+ v = v.SliceBeforeParam(',');
+ //Parse the version as an enum value
+ return Enum.Parse<Argon2Version>(v);
+ }
- private static Argon2_version ParseVersion(ReadOnlySpan<char> window)
- {
- //Version comes after the v= prefix
- ReadOnlySpan<char> v = window.SliceAfterParam("v=");
- v = v.SliceBeforeParam(',');
- //Parse the version as an enum value
- return Enum.Parse<Argon2_version>(v);
- }
+ private static uint ParseTimeCost(ReadOnlySpan<char> window)
+ {
+ //TimeCost comes after the t= prefix
+ ReadOnlySpan<char> t = window.SliceAfterParam("t=");
+ t = t.SliceBeforeParam(',');
+ //Parse the time cost as an unsigned integer
+ return uint.Parse(t, NumberStyles.Integer, CultureInfo.InvariantCulture);
+ }
- private static uint ParseTimeCost(ReadOnlySpan<char> window)
- {
- //TimeCost comes after the t= prefix
- ReadOnlySpan<char> t = window.SliceAfterParam("t=");
- t = t.SliceBeforeParam(',');
- //Parse the time cost as an unsigned integer
- return uint.Parse(t, NumberStyles.Integer, CultureInfo.InvariantCulture);
- }
+ private static uint ParseMemoryCost(ReadOnlySpan<char> window)
+ {
+ //MemoryCost comes after the m= prefix
+ ReadOnlySpan<char> m = window.SliceAfterParam("m=");
+ m = m.SliceBeforeParam(',');
+ //Parse the memory cost as an unsigned integer
+ return uint.Parse(m, NumberStyles.Integer, CultureInfo.InvariantCulture);
+ }
- private static uint ParseMemoryCost(ReadOnlySpan<char> window)
- {
- //MemoryCost comes after the m= prefix
- ReadOnlySpan<char> m = window.SliceAfterParam("m=");
- m = m.SliceBeforeParam(',');
- //Parse the memory cost as an unsigned integer
- return uint.Parse(m, NumberStyles.Integer, CultureInfo.InvariantCulture);
- }
+ private static uint ParseParallelism(ReadOnlySpan<char> window)
+ {
+ //Parallelism comes after the p= prefix
+ ReadOnlySpan<char> p = window.SliceAfterParam("p=");
+ p = p.SliceBeforeParam(',');
+ //Parse the parallelism as an unsigned integer
+ return uint.Parse(p, NumberStyles.Integer, CultureInfo.InvariantCulture);
+ }
- private static uint ParseParallelism(ReadOnlySpan<char> window)
- {
- //Parallelism comes after the p= prefix
- ReadOnlySpan<char> p = window.SliceAfterParam("p=");
- p = p.SliceBeforeParam(',');
- //Parse the parallelism as an unsigned integer
- return uint.Parse(p, NumberStyles.Integer, CultureInfo.InvariantCulture);
- }
+ private static ReadOnlySpan<char> ParseSalt(ReadOnlySpan<char> window)
+ {
+ //Salt comes after the s= prefix
+ ReadOnlySpan<char> s = window.SliceAfterParam("s=");
+ s = s.SliceBeforeParam('$');
+ //Parse the salt as a string
+ return s;
+ }
- private static ReadOnlySpan<char> ParseSalt(ReadOnlySpan<char> window)
- {
- //Salt comes after the s= prefix
- ReadOnlySpan<char> s = window.SliceAfterParam("s=");
- s = s.SliceBeforeParam('$');
- //Parse the salt as a string
- return s;
- }
+ private static ReadOnlySpan<char> ParseHash(ReadOnlySpan<char> window)
+ {
+ //Get last index of dollar sign for the start of the password hash
+ int start = window.LastIndexOf('$');
+ return window[(start + 1)..];
+ }
- private static ReadOnlySpan<char> ParseHash(ReadOnlySpan<char> window)
- {
- //Get last index of dollar sign for the start of the password hash
- int start = window.LastIndexOf('$');
- return window[(start + 1)..];
- }
+ public Argon2PasswordEntry(ReadOnlySpan<char> str)
+ {
+ _window = str;
+ Version = ParseVersion(str);
+ Salt = ParseSalt(str);
+ Hash = ParseHash(str);
+ }
- public Argon2PasswordEntry(ReadOnlySpan<char> str)
+ public readonly Argon2CostParams GetCostParams()
+ {
+ return new()
{
- Version = ParseVersion(str);
- TimeCost = ParseTimeCost(str);
- MemoryCost = ParseMemoryCost(str);
- Parallelism = ParseParallelism(str);
- Salt = ParseSalt(str);
- Hash = ParseHash(str);
- }
+ MemoryCost = ParseMemoryCost(_window),
+ TimeCost = ParseTimeCost(_window),
+ Parallelism = ParseParallelism(_window)
+ };
}
}
} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2Type.cs b/lib/Hashing.Portable/src/Argon2/Argon2Type.cs
new file mode 100644
index 0000000..4faf469
--- /dev/null
+++ b/lib/Hashing.Portable/src/Argon2/Argon2Type.cs
@@ -0,0 +1,33 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: Argon2Type.cs
+*
+* Argon2Type.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Hashing
+{
+ internal enum Argon2Type
+ {
+ Argon2d,
+ Argon2i,
+ Argon2id
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/Argon2Version.cs b/lib/Hashing.Portable/src/Argon2/Argon2Version.cs
new file mode 100644
index 0000000..fa7f524
--- /dev/null
+++ b/lib/Hashing.Portable/src/Argon2/Argon2Version.cs
@@ -0,0 +1,33 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: VnArgon2.cs
+*
+* VnArgon2.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Hashing
+{
+ internal enum Argon2Version
+ {
+ Version10 = 0x10,
+ Version13 = 0x13,
+ Argon2DefaultVersion = Version13
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/IArgon2Library.cs b/lib/Hashing.Portable/src/Argon2/IArgon2Library.cs
new file mode 100644
index 0000000..eea4806
--- /dev/null
+++ b/lib/Hashing.Portable/src/Argon2/IArgon2Library.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: IArgon2Library.cs
+*
+* IArgon2Library.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+
+using System;
+
+
+namespace VNLib.Hashing
+{
+ /// <summary>
+ /// Represents a native Argon2 library that can be used to hash an argon2 context.
+ /// </summary>
+ public interface IArgon2Library
+ {
+ /// <summary>
+ /// Hashes the data in the <paramref name="context"/> and returns the result
+ /// of the operation as an Argon2 error code.
+ /// </summary>
+ /// <param name="context">A pointer to a valid argon2 hash context</param>
+ /// <returns>The argon2 status code result</returns>
+ int Argon2Hash(IntPtr context);
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/SafeArgon2Library.cs b/lib/Hashing.Portable/src/Argon2/SafeArgon2Library.cs
new file mode 100644
index 0000000..0ce2fa7
--- /dev/null
+++ b/lib/Hashing.Portable/src/Argon2/SafeArgon2Library.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Hashing.Portable
+* File: SafeArgon2Library.cs
+*
+* SafeArgon2Library.cs is part of VNLib.Hashing.Portable which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Hashing.Portable is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Native;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Hashing
+{
+ /// <summary>
+ /// Represents a handle to a <see cref="SafeLibraryHandle"/>'s
+ /// native method for hashing data with Argon2
+ /// </summary>
+ public class SafeArgon2Library : IArgon2Library, IDisposable
+ {
+ /*
+ * The native library method delegate type
+ */
+ [SafeMethodName("argon2id_ctx")]
+ delegate int Argon2InvokeHash(IntPtr context);
+
+ private readonly SafeMethodHandle<Argon2InvokeHash> methodHandle;
+
+ /// <summary>
+ /// The safe library handle to the native library
+ /// </summary>
+ public SafeLibraryHandle LibHandle { get; }
+
+ internal SafeArgon2Library(SafeLibraryHandle lib)
+ {
+ LibHandle = lib;
+ //Get the native method
+ methodHandle = lib.GetMethod<Argon2InvokeHash>();
+ }
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public int Argon2Hash(IntPtr context)
+ {
+ LibHandle.ThrowIfClosed();
+ return methodHandle.Method!.Invoke(context);
+ }
+
+ /// <summary>
+ /// Disposes the library handle and method handle
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///<inheritdoc/>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ methodHandle.Dispose();
+ LibHandle.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
index 4542d80..9650128 100644
--- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
+++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs
@@ -22,13 +22,14 @@
* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/.
*/
-
using System;
using System.Text;
using System.Buffers;
using System.Threading;
+using System.Diagnostics;
using System.Buffers.Text;
using System.Security.Cryptography;
+using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Native;
@@ -47,94 +48,80 @@ namespace VNLib.Hashing
public const uint HASH_SIZE = 128;
public const int MAX_SALT_SIZE = 100;
public const string ID_MODE = "argon2id";
- public const string ARGON2_CTX_SAFE_METHOD_NAME = "argon2id_ctx";
- public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH";
public const string ARGON2_DEFUALT_LIB_NAME = "Argon2";
+ public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH";
private static readonly Encoding LocEncoding = Encoding.Unicode;
private static readonly Lazy<IUnmangedHeap> _heap = new (MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly);
- private static readonly Lazy<Argon2NativeLibary> _nativeLibrary = new(LoadNativeLib, LazyThreadSafetyMode.PublicationOnly);
-
+ private static readonly Lazy<SafeArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly);
+
//Private heap initialized to 10k size, and all allocated buffers will be zeroed when allocated
private static IUnmangedHeap PwHeap => _heap.Value;
- /* Argon2 primitive type */
- private enum Argon2_type
- {
- Argon2_d,
- Argon2_i,
- Argon2_id
- }
-
- /* Version of the algorithm */
- private enum Argon2_version
- {
- VERSION_10 = 0x10,
- VERSION_13 = 0x13,
- ARGON2_VERSION_NUMBER = VERSION_13
- }
-
/*
* The native library delegate method
*/
- delegate int Argon2InvokeHash(Argon2_Context* context);
-
- /*
- * Wrapper class that manages lifetime of the native library
- * This should basically never get finalized, but if it does
- * it will free the native lib
- */
- private sealed class Argon2NativeLibary
- {
- private readonly SafeMethodHandle<Argon2InvokeHash> _argon2id_ctx;
+ [SafeMethodName("argon2id_ctx")]
+ delegate int Argon2InvokeHash(IntPtr context);
- public Argon2NativeLibary(SafeMethodHandle<Argon2InvokeHash> method) => _argon2id_ctx = method;
+ private static SafeArgon2Library LoadSharedLibInternal()
+ {
+ //Get the path to the argon2 library
+ string? argon2EnvPath = Environment.GetEnvironmentVariable(ARGON2_LIB_ENVIRONMENT_VAR_NAME);
+ //Default to the default library name
+ argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME;
- public int Argon2Hash(Argon2_Context* context) => _argon2id_ctx.Method!.Invoke(context);
+ Trace.WriteLine("Attempting to load global native Argon2 library from: " + argon2EnvPath, "VnArgon2");
- ~Argon2NativeLibary()
- {
- //Dispose method handle which will release the native library
- _argon2id_ctx.Dispose();
- }
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories);
+ return new SafeArgon2Library(lib);
}
+
/// <summary>
- /// Loads the native Argon2 libray into the process with env variable library path
+ /// Gets the sahred native library instance for the current process.
/// </summary>
- /// <returns></returns>
- private static Argon2NativeLibary LoadNativeLib()
+ /// <returns>The shared library instance</returns>
+ /// <exception cref="DllNotFoundException"></exception>
+ public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Value;
+
+ /// <summary>
+ /// Loads a native Argon2 shared library from the specified path
+ /// and returns a <see cref="IArgon2Library"/> instance. wrapper
+ /// </summary>
+ /// <param name="dllPath">The path to the native shared library to load</param>
+ /// <param name="searchPath">The dll search path</param>
+ /// <returns>A handle for the library</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="DllNotFoundException"></exception>
+ public static SafeArgon2Library LoadCustomLibrary(string dllPath, DllImportSearchPath searchPath)
{
- //Get the path to the argon2 library
- string? argon2EnvPath = Environment.GetEnvironmentVariable(ARGON2_LIB_ENVIRONMENT_VAR_NAME);
- //Default to the default library name
- argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME;
-
//Try to load the libary and always dispose it so the native method handle will unload the library
- using SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath);
-
- //Get safe native method
- SafeMethodHandle<Argon2InvokeHash> method = lib.GetMethod<Argon2InvokeHash>(ARGON2_CTX_SAFE_METHOD_NAME);
-
- return new Argon2NativeLibary(method);
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(dllPath, searchPath);
+ return new SafeArgon2Library(lib);
}
/// <summary>
/// Hashes a password with a salt and specified arguments
/// </summary>
+ /// <param name="lib"></param>
/// <param name="password">Span of characters containing the password to be hashed</param>
/// <param name="salt">Span of characters contating the salt to include in the hashing</param>
/// <param name="secret">Optional secret to include in hash</param>
/// <param name="hashLen">Size of the hash in bytes</param>
- /// <param name="memCost">Memory cost</param>
- /// <param name="parallelism">Degree of parallelism</param>
- /// <param name="timeCost">Time cost of operation</param>
+ /// <param name="costParams">Argon2 cost parameters</param>
/// <exception cref="VnArgon2Exception"></exception>
/// <exception cref="InsufficientMemoryException"></exception>
/// <returns>A <see cref="Encoding.Unicode"/> <see cref="string"/> containg the ready-to-store hash</returns>
- public static string Hash2id(ReadOnlySpan<char> password, ReadOnlySpan<char> salt, ReadOnlySpan<byte> secret,
- uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE)
+ public static string Hash2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<char> password,
+ ReadOnlySpan<char> salt,
+ ReadOnlySpan<byte> secret,
+ in Argon2CostParams costParams,
+ uint hashLen = HASH_SIZE
+ )
{
//Get bytes count
int saltbytes = LocEncoding.GetByteCount(salt);
@@ -155,25 +142,29 @@ namespace VNLib.Hashing
_ = LocEncoding.GetBytes(password, passBuffer);
//Hash
- return Hash2id(passBuffer, saltBuffer, secret, timeCost, memCost, parallelism, hashLen);
+ return Hash2id(lib, passBuffer, saltBuffer, secret, in costParams, hashLen);
}
/// <summary>
/// Hashes a password with a salt and specified arguments
/// </summary>
+ /// <param name="lib"></param>
/// <param name="password">Span of characters containing the password to be hashed</param>
/// <param name="salt">Span of characters contating the salt to include in the hashing</param>
/// <param name="secret">Optional secret to include in hash</param>
/// <param name="hashLen">Size of the hash in bytes</param>
- /// <param name="memCost">Memory cost</param>
- /// <param name="parallelism">Degree of parallelism</param>
- /// <param name="timeCost">Time cost of operation</param>
- /// <exception cref="FormatException"></exception>
+ /// <param name="costParams">Argon2 cost parameters</param>
/// <exception cref="VnArgon2Exception"></exception>
/// <exception cref="InsufficientMemoryException"></exception>
/// <returns>A <see cref="Encoding.Unicode"/> <see cref="string"/> containg the ready-to-store hash</returns>
- public static string Hash2id(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> secret,
- uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE)
+ public static string Hash2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<char> password,
+ ReadOnlySpan<byte> salt,
+ ReadOnlySpan<byte> secret,
+ in Argon2CostParams costParams,
+ uint hashLen = HASH_SIZE
+ )
{
//Get bytes count
int passBytes = LocEncoding.GetByteCount(password);
@@ -185,31 +176,36 @@ namespace VNLib.Hashing
_ = LocEncoding.GetBytes(password, pwdHandle.Span);
//Hash
- return Hash2id(pwdHandle.Span, salt, secret, timeCost, memCost, parallelism, hashLen);
+ return Hash2id(lib, pwdHandle.Span, salt, secret, in costParams, hashLen);
}
-
+
/// <summary>
/// Hashes a password with a salt and specified arguments
/// </summary>
+ /// <param name="lib"></param>
/// <param name="password">Span of characters containing the password to be hashed</param>
/// <param name="salt">Span of characters contating the salt to include in the hashing</param>
/// <param name="secret">Optional secret to include in hash</param>
/// <param name="hashLen">Size of the hash in bytes</param>
- /// <param name="memCost">Memory cost</param>
- /// <param name="parallelism">Degree of parallelism</param>
- /// <param name="timeCost">Time cost of operation</param>
+ /// <param name="costParams">Argon2 cost parameters</param>
/// <exception cref="VnArgon2Exception"></exception>
/// <exception cref="OutOfMemoryException"></exception>
/// <returns>A <see cref="Encoding.Unicode"/> <see cref="string"/>containg the ready-to-store hash</returns>
- public static string Hash2id(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> secret,
- uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE)
+ public static string Hash2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ ReadOnlySpan<byte> secret,
+ in Argon2CostParams costParams,
+ uint hashLen = HASH_SIZE
+ )
{
string hash, salts;
//Alloc data for hash output
using IMemoryHandle<byte> hashHandle = PwHeap.Alloc<byte>(hashLen, true);
//hash the password
- Hash2id(password, salt, secret, hashHandle.Span, timeCost, memCost, parallelism);
+ Hash2id(lib, password, salt, secret, hashHandle.Span, in costParams);
//Encode hash
hash = Convert.ToBase64String(hashHandle.Span);
@@ -218,125 +214,69 @@ namespace VNLib.Hashing
salts = Convert.ToBase64String(salt);
//Encode salt in base64
- return $"${ID_MODE}$v={(int)Argon2_version.VERSION_13},m={memCost},t={timeCost},p={parallelism},s={salts}${hash}";
+ return $"${ID_MODE}$v={(int)Argon2Version.Version13},m={costParams.MemoryCost},t={costParams.TimeCost},p={costParams.Parallelism},s={salts}${hash}";
}
-
+
/// <summary>
/// Exposes the raw Argon2-ID hashing api to C#, using spans (pins memory references)
/// </summary>
+ /// <param name="lib"></param>
/// <param name="password">Span of characters containing the password to be hashed</param>
/// <param name="rawHashOutput">The output buffer to store the raw hash output</param>
/// <param name="salt">Span of characters contating the salt to include in the hashing</param>
/// <param name="secret">Optional secret to include in hash</param>
- /// <param name="memCost">Memory cost</param>
- /// <param name="parallelism">Degree of parallelism</param>
- /// <param name="timeCost">Time cost of operation</param>
+ /// <param name="costParams">Argon2 cost parameters</param>>
/// <exception cref="VnArgon2Exception"></exception>
- public static void Hash2id(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> secret, Span<byte> rawHashOutput,
- uint timeCost = 2, uint memCost = 65535, uint parallelism = 4)
+ public static void Hash2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ ReadOnlySpan<byte> secret,
+ Span<byte> rawHashOutput,
+ in Argon2CostParams costParams
+ )
{
fixed (byte* pwd = password, slptr = salt, secretptr = secret, outPtr = rawHashOutput)
{
//Setup context
- Argon2_Context ctx;
+ Argon2Context ctx;
//Pointer
- Argon2_Context* context = &ctx;
- context->version = Argon2_version.VERSION_13;
- context->t_cost = timeCost;
- context->m_cost = memCost;
- context->threads = parallelism;
- context->lanes = parallelism;
+ Argon2Context* context = &ctx;
+ context->version = Argon2Version.Argon2DefaultVersion;
+ context->t_cost = costParams.TimeCost;
+ context->m_cost = costParams.MemoryCost;
+ context->threads = costParams.Parallelism;
+ context->lanes = costParams.Parallelism;
//Default flags
context->flags = ARGON2_DEFAULT_FLAGS;
context->allocate_cbk = null;
context->free_cbk = null;
//Password
context->pwd = pwd;
- context->pwdlen = (UInt32)password.Length;
+ context->pwdlen = (uint)password.Length;
//Salt
context->salt = slptr;
- context->saltlen = (UInt32)salt.Length;
+ context->saltlen = (uint)salt.Length;
//Secret
context->secret = secretptr;
- context->secretlen = (UInt32)secret.Length;
+ context->secretlen = (uint)secret.Length;
//Output
context->outptr = outPtr;
- context->outlen = (UInt32)rawHashOutput.Length;
+ context->outlen = (uint)rawHashOutput.Length;
//Hash
- Argon2_ErrorCodes result = (Argon2_ErrorCodes)_nativeLibrary.Value.Argon2Hash(&ctx);
+ Argon2_ErrorCodes result = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context);
//Throw exceptions if error
ThrowOnArgonErr(result);
}
}
- /// <summary>
- /// Compares a raw password, with a salt to a raw hash
- /// </summary>
- /// <param name="rawPass">Password bytes</param>
- /// <param name="salt">Salt bytes</param>
- /// <param name="secret">Optional secret that was included in hash</param>
- /// <param name="hashBytes">Raw hash bytes</param>
- /// <param name="timeCost">Time cost</param>
- /// <param name="memCost">Memory cost</param>
- /// <param name="parallelism">Degree of parallelism</param>
- /// <exception cref="OverflowException"></exception>
- /// <exception cref="FormatException"></exception>
- /// <exception cref="VnArgon2Exception"></exception>
- /// <exception cref="InsufficientMemoryException"></exception>
- /// <exception cref="VnArgon2PasswordFormatException"></exception>
- /// <returns>True if hashes match</returns>
- public static bool Verify2id(ReadOnlySpan<byte> rawPass, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> secret, ReadOnlySpan<byte> hashBytes,
- uint timeCost = 2, uint memCost = 65535, uint parallelism = 4)
- {
- //Alloc data for hash output
- using IMemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length, true);
-
- //Pin to get the base pointer
- using MemoryHandle outputPtr = outputHandle.Pin(0);
-
- //Get pointers
- fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt)
- {
- //Setup context
- Argon2_Context ctx;
- //Pointer
- Argon2_Context* context = &ctx;
- context->version = Argon2_version.VERSION_13;
- context->m_cost = memCost;
- context->t_cost = timeCost;
- context->threads = parallelism;
- context->lanes = parallelism;
- //Default flags
- context->flags = ARGON2_DEFAULT_FLAGS;
- //Use default memory allocator
- context->allocate_cbk = null;
- context->free_cbk = null;
- //Password
- context->pwd = pwd;
- context->pwdlen = (uint)rawPass.Length;
- //Salt
- context->salt = slptr;
- context->saltlen = (uint)salt.Length;
- //Secret
- context->secret = secretptr;
- context->secretlen = (uint)secret.Length;
- //Output
- context->outptr = outputPtr.Pointer;
- context->outlen = (uint)outputHandle.Length;
- //Hash
- Argon2_ErrorCodes result = (Argon2_ErrorCodes)_nativeLibrary.Value.Argon2Hash(&ctx);
- //Throw an excpetion if an error ocurred
- ThrowOnArgonErr(result);
- }
- //Return the comparison
- return CryptographicOperations.FixedTimeEquals(outputHandle.Span, hashBytes);
- }
/// <summary>
/// Compares a password to a previously hashed password from this library
/// </summary>
+ /// <param name="lib"></param>
/// <param name="rawPass">Password data</param>
/// <param name="secret">Optional secret that was included in hash</param>
/// <param name="hash">Full hash span</param>
@@ -346,7 +286,12 @@ namespace VNLib.Hashing
/// <exception cref="InsufficientMemoryException"></exception>
/// <exception cref="VnArgon2PasswordFormatException"></exception>
/// <returns>True if the password matches the hash</returns>
- public static bool Verify2id(ReadOnlySpan<char> rawPass, ReadOnlySpan<char> hash, ReadOnlySpan<byte> secret)
+ public static bool Verify2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<char> rawPass,
+ ReadOnlySpan<char> hash,
+ ReadOnlySpan<byte> secret
+ )
{
if (!hash.Contains(ID_MODE, StringComparison.Ordinal))
{
@@ -354,10 +299,12 @@ namespace VNLib.Hashing
}
Argon2PasswordEntry entry;
+ Argon2CostParams costParams;
try
{
//Init password breakout struct
entry = new(hash);
+ costParams = entry.GetCostParams();
}
catch (Exception ex)
{
@@ -376,6 +323,7 @@ namespace VNLib.Hashing
Span<byte> saltBuf = rawBufferHandle.Span[..saltBase64BufSize];
Span<byte> passBuf = rawBufferHandle.AsSpan(saltBase64BufSize, passBase64BufSize);
Span<byte> rawPassBuf = rawBufferHandle.AsSpan(saltBase64BufSize + passBase64BufSize, rawPassLen);
+
{
//Decode salt
if (!Convert.TryFromBase64Chars(entry.Hash, passBuf, out int actualHashLen))
@@ -399,7 +347,75 @@ namespace VNLib.Hashing
//encode password bytes
rawPassLen = LocEncoding.GetBytes(rawPass, rawPassBuf);
//Verify password
- return Verify2id(rawPassBuf[..rawPassLen], saltBuf, secret, passBuf, entry.TimeCost, entry.MemoryCost, entry.Parallelism);
+ return Verify2id(lib, rawPassBuf[..rawPassLen], saltBuf, secret, passBuf, in costParams);
+ }
+
+ /// <summary>
+ /// Compares a raw password, with a salt to a raw hash
+ /// </summary>
+ /// <param name="lib"></param>
+ /// <param name="rawPass">Password bytes</param>
+ /// <param name="salt">Salt bytes</param>
+ /// <param name="secret">Optional secret that was included in hash</param>
+ /// <param name="hashBytes">Raw hash bytes</param>
+ /// <param name="costParams">Argon2 cost parameters</param>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="FormatException"></exception>
+ /// <exception cref="VnArgon2Exception"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="VnArgon2PasswordFormatException"></exception>
+ /// <returns>True if hashes match</returns>
+ public static bool Verify2id(
+ this IArgon2Library lib,
+ ReadOnlySpan<byte> rawPass,
+ ReadOnlySpan<byte> salt,
+ ReadOnlySpan<byte> secret,
+ ReadOnlySpan<byte> hashBytes,
+ in Argon2CostParams costParams
+ )
+ {
+ //Alloc data for hash output
+ using IMemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length, true);
+
+ //Pin to get the base pointer
+ using MemoryHandle outputPtr = outputHandle.Pin(0);
+
+ //Get pointers
+ fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt)
+ {
+ //Setup context
+ Argon2Context ctx;
+ //Pointer
+ Argon2Context* context = &ctx;
+ context->version = Argon2Version.Argon2DefaultVersion;
+ context->t_cost = costParams.TimeCost;
+ context->m_cost = costParams.MemoryCost;
+ context->threads = costParams.Parallelism;
+ context->lanes = costParams.Parallelism;
+ //Default flags
+ context->flags = ARGON2_DEFAULT_FLAGS;
+ //Use default memory allocator
+ context->allocate_cbk = null;
+ context->free_cbk = null;
+ //Password
+ context->pwd = pwd;
+ context->pwdlen = (uint)rawPass.Length;
+ //Salt
+ context->salt = slptr;
+ context->saltlen = (uint)salt.Length;
+ //Secret
+ context->secret = secretptr;
+ context->secretlen = (uint)secret.Length;
+ //Output
+ context->outptr = outputPtr.Pointer;
+ context->outlen = (uint)outputHandle.Length;
+ //Hash
+ Argon2_ErrorCodes result = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context);
+ //Throw an excpetion if an error ocurred
+ ThrowOnArgonErr(result);
+ }
+ //Return the comparison
+ return CryptographicOperations.FixedTimeEquals(outputHandle.Span, hashBytes);
}
private static void ThrowOnArgonErr(Argon2_ErrorCodes result)
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index b17498d..3e25e70 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -46,10 +46,12 @@ namespace VNLib.Net.Http.Core
/// The reusable http request container
/// </summary>
public readonly HttpRequest Request;
+
/// <summary>
/// The reusable response controler
/// </summary>
public readonly HttpResponse Response;
+
/// <summary>
/// The http server that this context is bound to
/// </summary>
diff --git a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
index 1b30934..e819e6c 100644
--- a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
+++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs
@@ -122,6 +122,28 @@ namespace VNLib.Plugins.Essentials.Accounts
return await manager.UpdatePassAsync(user, passHash);
}
+ /// <summary>
+ /// Creates a new user with a random user id and the specified email address and password.
+ /// If privileges are left null, the minimum privileges will be set.
+ /// </summary>
+ /// <param name="manager"></param>
+ /// <param name="emailAddress">The user's email address or secondary id</param>
+ /// <param name="password">The user's password</param>
+ /// <param name="privileges">Optional user privilage level</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that resolves the new user</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="UserExistsException"></exception>
+ /// <exception cref="UserCreationFailedException"></exception>
+ public static Task<IUser> CreateUserAsync(this IUserManager manager, string emailAddress, PrivateString password, ulong? privileges, CancellationToken cancellation = default)
+ {
+ _ = manager ?? throw new ArgumentNullException(nameof(manager));
+ //Create a random user id
+ string randomId = GetRandomUserId();
+ //Set the default/minimum privileges
+ privileges ??= MINIMUM_LEVEL;
+ return manager.CreateUserAsync(randomId, emailAddress, privileges.Value, password, cancellation);
+ }
/// <summary>
/// Checks to see if the current user account was created
@@ -312,7 +334,7 @@ namespace VNLib.Plugins.Essentials.Accounts
//Store variables
entity.Session.UserID = user.UserID;
- entity.Session.Privilages = user.Privilages;
+ entity.Session.Privilages = user.Privileges;
//Store client id for later use
entity.Session[BROWSER_ID_ENTRY] = secInfo.ClientId;
diff --git a/lib/Plugins.Essentials/src/Accounts/Argon2ConfigParams.cs b/lib/Plugins.Essentials/src/Accounts/Argon2ConfigParams.cs
new file mode 100644
index 0000000..c75ba93
--- /dev/null
+++ b/lib/Plugins.Essentials/src/Accounts/Argon2ConfigParams.cs
@@ -0,0 +1,66 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials
+* File: Argon2ConfigParams.cs
+*
+* Argon2ConfigParams.cs is part of VNLib.Plugins.Essentials which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials 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.Plugins.Essentials 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/.
+*/
+
+using System;
+
+namespace VNLib.Plugins.Essentials.Accounts
+{
+ /// <summary>
+ /// The configuration parameters for the Argon2 hashing algorithm
+ /// </summary>
+ public readonly record struct Argon2ConfigParams
+ {
+
+ /// <summary>
+ /// Initializes a new <see cref="Argon2ConfigParams"/> instance with the default values
+ /// </summary>
+ public Argon2ConfigParams()
+ { }
+
+ /// <summary>
+ /// The length of the random salt to use in bytes (defaults to 32)
+ /// </summary>
+ public int SaltLen { get; init; } = 32;
+
+ /// <summary>
+ /// The Argon2 time cost parameter (defaults to 4)
+ /// </summary>
+ public uint TimeCost { get; init; } = 4;
+
+ /// <summary>
+ /// The Argon2 memory cost parameter (defaults to UInt16.MaxValue)
+ /// </summary>
+ public uint MemoryCost { get; init; } = UInt16.MaxValue;
+
+ /// <summary>
+ /// The Argon2 default hash length parameter (defaults to 128)
+ /// </summary>
+ public uint HashLen { get; init; } = 128;
+
+ /// <summary>
+ /// The Argon2 parallelism parameter (defaults to the number of logical processors)
+ /// </summary>
+ public uint Parallelism { get; init; } = (uint)Environment.ProcessorCount;
+ }
+} \ No newline at end of file
diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs
index 32e04e3..b9fde20 100644
--- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs
+++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs
@@ -38,44 +38,49 @@ namespace VNLib.Plugins.Essentials.Accounts
/// </summary>
public sealed class PasswordHashing : IPasswordHashingProvider
{
- private const int STACK_MAX_BUFF_SIZE = 64;
+ private const int STACK_MAX_BUFF_SIZE = 128;
private readonly ISecretProvider _secret;
+ private readonly IArgon2Library _argon2;
+ private readonly Argon2ConfigParams _config;
+
+ private PasswordHashing(IArgon2Library library, ISecretProvider secret, in Argon2ConfigParams setup)
+ {
+ //Store getter
+ _secret = secret ?? throw new ArgumentNullException(nameof(secret));
+ _argon2 = library ?? throw new ArgumentNullException(nameof(library));
+ _config = setup;
+ }
- private readonly uint TimeCost;
- private readonly uint MemoryCost;
- private readonly uint HashLen;
- private readonly int SaltLen;
- private readonly uint Parallelism;
+ /// <summary>
+ /// Creates a new <see cref="PasswordHashing"/> instance using the specified library.
+ /// </summary>
+ /// <param name="library">The library instance to use</param>
+ /// <param name="secret">The password secret provider</param>
+ /// <param name="setup">The configuration setup arguments</param>
+ /// <returns>The instance of the library to use</returns>
+ public static PasswordHashing Create(IArgon2Library library, ISecretProvider secret, in Argon2ConfigParams setup) => new (library, secret, setup);
/// <summary>
- /// Initalizes the <see cref="PasswordHashing"/> class
+ /// Creates a new <see cref="PasswordHashing"/> instance using the default
+ /// <see cref="VnArgon2"/> library.
/// </summary>
/// <param name="secret">The password secret provider</param>
- /// <param name="saltLen">A positive integer for the size of the random salt used during the hashing proccess</param>
- /// <param name="timeCost">The Argon2 time cost parameter</param>
- /// <param name="memoryCost">The Argon2 memory cost parameter</param>
- /// <param name="hashLen">The size of the hash to produce during hashing operations</param>
- /// <param name="parallism">
- /// The Argon2 parallelism parameter (the number of threads to use for hasing)
- /// (default = 0 - defaults to the number of logical processors)
- /// </param>
- /// <exception cref="ArgumentNullException"></exception>
- public PasswordHashing(ISecretProvider secret, int saltLen = 32, uint timeCost = 4, uint memoryCost = UInt16.MaxValue, uint parallism = 0, uint hashLen = 128)
+ /// <param name="setup">The configuration setup arguments</param>
+ /// <returns>The instance of the library to use</returns>
+ /// <exception cref="DllNotFoundException"></exception>
+ public static PasswordHashing Create(ISecretProvider secret, in Argon2ConfigParams setup) => Create(VnArgon2.GetOrLoadSharedLib(), secret, in setup);
+
+ private Argon2CostParams GetCostParams()
{
- //Store getter
- _secret = secret ?? throw new ArgumentNullException(nameof(secret));
-
- //Store parameters
- HashLen = hashLen;
- //Store maginitude as a unit
- MemoryCost = memoryCost;
- TimeCost = timeCost;
- SaltLen = saltLen;
- Parallelism = parallism < 1 ? (uint)Environment.ProcessorCount : parallism;
+ return new Argon2CostParams
+ {
+ MemoryCost = _config.MemoryCost,
+ TimeCost = _config.TimeCost,
+ Parallelism = _config.Parallelism
+ };
}
-
///<inheritdoc/>
///<exception cref="VnArgon2Exception"></exception>
///<exception cref="VnArgon2PasswordFormatException"></exception>
@@ -109,7 +114,7 @@ namespace VNLib.Plugins.Essentials.Accounts
//Get the secret from the callback
ERRNO count = _secret.GetSecret(secretBuffer);
//Verify
- return VnArgon2.Verify2id(password, passHash, secretBuffer[..(int)count]);
+ return _argon2.Verify2id(password, passHash, secretBuffer[..(int)count]);
}
finally
{
@@ -129,12 +134,27 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <remarks>Uses fixed time comparison from <see cref="CryptographicOperations"/> class</remarks>
public bool Verify(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> password)
{
- //Alloc a buffer with the same size as the hash
- using UnsafeMemoryHandle<byte> hashBuf = MemoryUtil.UnsafeAlloc(hash.Length, true);
- //Hash the password with the current config
- Hash(password, salt, hashBuf.Span);
- //Compare the hashed password to the specified hash and return results
- return CryptographicOperations.FixedTimeEquals(hash, hashBuf.Span);
+ if (hash.Length < STACK_MAX_BUFF_SIZE)
+ {
+ //Alloc stack buffer
+ Span<byte> hashBuf = stackalloc byte[hash.Length];
+
+ //Hash the password with the current config
+ Hash(password, salt, hashBuf);
+
+ //Compare the hashed password to the specified hash and return results
+ return CryptographicOperations.FixedTimeEquals(hash, hashBuf);
+ }
+ else
+ {
+ using UnsafeMemoryHandle<byte> hashBuf = MemoryUtil.UnsafeAlloc(hash.Length, true);
+
+ //Hash the password with the current config
+ Hash(password, salt, hashBuf.Span);
+
+ //Compare the hashed password to the specified hash and return results
+ return CryptographicOperations.FixedTimeEquals(hash, hashBuf.Span);
+ }
}
/// <inheritdoc/>
@@ -142,22 +162,25 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <returns>A <see cref="PrivateString"/> of the hashed and encoded password</returns>
public PrivateString Hash(ReadOnlySpan<char> password)
{
+ Argon2CostParams costParams = GetCostParams();
+
//Alloc shared buffer for the salt and secret buffer
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(SaltLen + _secret.BufferSize, true);
+ using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(_config.SaltLen + _secret.BufferSize, true);
+
+ //Split buffers
+ Span<byte> saltBuf = buffer.Span[.._config.SaltLen];
+ Span<byte> secretBuf = buffer.Span[_config.SaltLen..];
+
+ //Fill the buffer with random bytes
+ RandomHash.GetRandomBytes(saltBuf);
+
try
{
- //Split buffers
- Span<byte> saltBuf = buffer.Span[..SaltLen];
- Span<byte> secretBuf = buffer.Span[SaltLen..];
-
- //Fill the buffer with random bytes
- RandomHash.GetRandomBytes(saltBuf);
-
//recover the secret
ERRNO count = _secret.GetSecret(secretBuf);
//Hashes a password, with the current parameters
- return (PrivateString)VnArgon2.Hash2id(password, saltBuf, secretBuf[..(int)count], TimeCost, MemoryCost, Parallelism, HashLen);
+ return (PrivateString)_argon2.Hash2id(password, saltBuf, secretBuf[..(int)count], in costParams, _config.HashLen);
}
finally
{
@@ -170,21 +193,24 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <returns>A <see cref="PrivateString"/> of the hashed and encoded password</returns>
public PrivateString Hash(ReadOnlySpan<byte> password)
{
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(SaltLen + _secret.BufferSize, true);
- try
- {
- //Split buffers
- Span<byte> saltBuf = buffer.Span[..SaltLen];
- Span<byte> secretBuf = buffer.Span[SaltLen..];
+ Argon2CostParams costParams = GetCostParams();
+
+ using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(_config.SaltLen + _secret.BufferSize, true);
- //Fill the buffer with random bytes
- RandomHash.GetRandomBytes(saltBuf);
+ //Split buffers
+ Span<byte> saltBuf = buffer.Span[.._config.SaltLen];
+ Span<byte> secretBuf = buffer.Span[_config.SaltLen..];
+ //Fill the buffer with random bytes
+ RandomHash.GetRandomBytes(saltBuf);
+
+ try
+ {
//recover the secret
ERRNO count = _secret.GetSecret(secretBuf);
//Hashes a password, with the current parameters
- return (PrivateString)VnArgon2.Hash2id(password, saltBuf, secretBuf[..(int)count], TimeCost, MemoryCost, Parallelism, HashLen);
+ return (PrivateString)_argon2.Hash2id(password, saltBuf, secretBuf[..(int)count], in costParams, _config.HashLen);
}
finally
{
@@ -202,14 +228,16 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <exception cref="VnArgon2Exception"></exception>
public void Hash(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, Span<byte> hashOutput)
{
+ Argon2CostParams costParams = GetCostParams();
+
//alloc secret buffer
- using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAlloc(_secret.BufferSize, true);
+ using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAllocNearestPage(_secret.BufferSize, true);
try
{
//Get the secret from the callback
ERRNO count = _secret.GetSecret(secretBuffer.Span);
//Hashes a password, with the current parameters
- VnArgon2.Hash2id(password, salt, secretBuffer.Span[..(int)count], hashOutput, TimeCost, MemoryCost, Parallelism);
+ _argon2.Hash2id(password, salt, secretBuffer.Span[..(int)count], hashOutput, in costParams);
}
finally
{
@@ -225,36 +253,36 @@ namespace VNLib.Plugins.Essentials.Accounts
/// <param name="passHash"></param>
/// <param name="password"></param>
/// <exception cref="NotSupportedException"></exception>
- public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password)
- {
- throw new NotSupportedException();
- }
+ public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password) => throw new NotSupportedException();
///<inheritdoc/>
///<exception cref="VnArgon2Exception"></exception>
public ERRNO Hash(ReadOnlySpan<byte> password, Span<byte> hashOutput)
{
//Calc the min buffer size
- int minBufferSize = SaltLen + _secret.BufferSize + (int)HashLen;
+ int minBufferSize = _config.SaltLen + _secret.BufferSize + (int)_config.HashLen;
+
+ Argon2CostParams costParams = GetCostParams();
//Alloc heap buffer
using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage<byte>(minBufferSize, true);
- try
- {
- //Segment the buffer
- HashBufferSegments segments = new(buffer.Span, _secret.BufferSize, SaltLen, (int)HashLen);
- //Fill the buffer with random bytes
- RandomHash.GetRandomBytes(segments.SaltBuffer);
+ //Segment the buffer
+ HashBufferSegments segments = new(buffer.Span, _secret.BufferSize, _config.SaltLen, (int)_config.HashLen);
+
+ //Fill the buffer with random bytes
+ RandomHash.GetRandomBytes(segments.SaltBuffer);
+ try
+ {
//recover the secret
ERRNO count = _secret.GetSecret(segments.SecretBuffer);
//Hash the password in binary and write the secret to the binary buffer
- VnArgon2.Hash2id(password, segments.SaltBuffer, segments.SecretBuffer[..(int)count], segments.HashBuffer, TimeCost, MemoryCost, Parallelism);
+ _argon2.Hash2id(password, segments.SaltBuffer, segments.SecretBuffer[..(int)count], segments.HashBuffer, in costParams);
//Hash size is the desired hash size
- return new((int)HashLen);
+ return new((int)_config.HashLen);
}
finally
{
diff --git a/lib/Plugins.Essentials/src/Sessions/ISession.cs b/lib/Plugins.Essentials/src/Sessions/ISession.cs
index e15c6e2..d2e0ee1 100644
--- a/lib/Plugins.Essentials/src/Sessions/ISession.cs
+++ b/lib/Plugins.Essentials/src/Sessions/ISession.cs
@@ -53,34 +53,42 @@ namespace VNLib.Plugins.Essentials.Sessions
/// A value specifying the type of the loaded session
/// </summary>
SessionType SessionType { get; }
+
/// <summary>
/// UTC time in when the session was created
/// </summary>
DateTimeOffset Created { get; }
+
/// <summary>
/// Privilages associated with user specified during login
/// </summary>
ulong Privilages { get; set; }
+
/// <summary>
/// Key that identifies the current session. (Identical to cookie::sessionid)
/// </summary>
string SessionID { get; }
+
/// <summary>
/// User ID associated with session
/// </summary>
string UserID { get; set; }
+
/// <summary>
/// Marks the session as invalid
/// </summary>
void Invalidate(bool all = false);
+
/// <summary>
/// Gets or sets the session's authorization token
/// </summary>
string Token { get; set; }
+
/// <summary>
/// The IP address belonging to the client
/// </summary>
IPAddress UserIP { get; }
+
/// <summary>
/// Sets the session ID to be regenerated if applicable
/// </summary>
@@ -90,5 +98,11 @@ namespace VNLib.Plugins.Essentials.Sessions
/// A value that indicates this session was newly created
/// </summary>
bool IsNew { get; }
+
+ /// <summary>
+ /// This is a special function that requests the session to be detached from the current http connection
+ /// but allow it to remain available.
+ /// </summary>
+ void Detach();
}
} \ No newline at end of file
diff --git a/lib/Plugins.Essentials/src/Sessions/SessionBase.cs b/lib/Plugins.Essentials/src/Sessions/SessionBase.cs
index 33a0dbd..038fd2c 100644
--- a/lib/Plugins.Essentials/src/Sessions/SessionBase.cs
+++ b/lib/Plugins.Essentials/src/Sessions/SessionBase.cs
@@ -40,6 +40,7 @@ namespace VNLib.Plugins.Essentials.Sessions
protected const ulong IS_NEW_MSK = 0b0000000000000010UL;
protected const ulong REGEN_ID_MSK = 0b0000000000000100UL;
protected const ulong INVALID_MSK = 0b0000000000001000UL;
+ protected const ulong DETACHED_MSK = 0b0000000000010000UL;
protected const ulong ALL_INVALID_MSK = 0b0000000000100000UL;
protected const string USER_ID_ENTRY = "__.i.uid";
@@ -145,6 +146,10 @@ namespace VNLib.Plugins.Essentials.Sessions
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void RegenID() => Flags.Set(REGEN_ID_MSK);
+
+ /// <inheritdoc/>
+ public virtual void Detach() => Flags.Set(DETACHED_MSK);
+
/// <summary>
/// Invoked when the indexer is is called to
/// </summary>
diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
index a5d7c4d..d6d5456 100644
--- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
+++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
@@ -224,21 +224,27 @@ namespace VNLib.Plugins.Essentials.Sessions
/// Flags the session as invalid. IMPORTANT: the user's session data is no longer valid, no data
/// will be saved to the session store when the session closes
/// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invalidate(bool all = false) => UserSession.Invalidate(all);
+
/// <summary>
/// Marks the session ID to be regenerated during closing event
/// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegenID() => UserSession.RegenID();
+ /// <summary>
+ /// Marks the session to be detached from the current connection.
+ /// </summary>
+ public void Detach() => UserSession.Detach();
+
+
#nullable disable
+
///<inheritdoc/>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetObject<T>(string key) => JsonSerializer.Deserialize<T>(this[key], SR_OPTIONS);
+
///<inheritdoc/>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetObject<T>(string key, T obj) => this[key] = obj == null ? null: JsonSerializer.Serialize(obj, SR_OPTIONS);
+
#nullable enable
/// <summary>
diff --git a/lib/Plugins.Essentials/src/Users/IUser.cs b/lib/Plugins.Essentials/src/Users/IUser.cs
index 28c5305..635f5e4 100644
--- a/lib/Plugins.Essentials/src/Users/IUser.cs
+++ b/lib/Plugins.Essentials/src/Users/IUser.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
@@ -39,33 +39,40 @@ namespace VNLib.Plugins.Essentials.Users
public interface IUser : IAsyncExclusiveResource, IDisposable, IObjectStorage, IEnumerable<KeyValuePair<string, string>>, IIndexable<string, string>
{
/// <summary>
- /// The user's privilage level
+ /// The user's privilege level
/// </summary>
- ulong Privilages { get; }
+ ulong Privileges { get; set; }
+
/// <summary>
/// The user's ID
/// </summary>
string UserID { get; }
+
/// <summary>
/// Date the user's account was created
/// </summary>
DateTimeOffset Created { get; }
+
/// <summary>
/// The user's password hash if retreived from the backing store, otherwise null
/// </summary>
PrivateString? PassHash { get; }
+
/// <summary>
/// Status of account
/// </summary>
UserStatus Status { get; set; }
+
/// <summary>
/// Is the account only usable from local network?
/// </summary>
bool LocalOnly { get; set; }
+
/// <summary>
/// The user's email address
/// </summary>
string EmailAddress { get; set; }
+
/// <summary>
/// Marks the user for deletion on release
/// </summary>
diff --git a/lib/Plugins.Essentials/src/Users/IUserManager.cs b/lib/Plugins.Essentials/src/Users/IUserManager.cs
index dd521e4..42d482e 100644
--- a/lib/Plugins.Essentials/src/Users/IUserManager.cs
+++ b/lib/Plugins.Essentials/src/Users/IUserManager.cs
@@ -74,7 +74,7 @@ namespace VNLib.Plugins.Essentials.Users
/// Creates a new user in the current user's table and if successful returns the new user object (without password)
/// </summary>
/// <param name="userid">The user id</param>
- /// <param name="privilages">A number representing the privilage level of the account</param>
+ /// <param name="privileges">A number representing the privilage level of the account</param>
/// <param name="passHash">Value to store in the password field</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <param name="emailAddress">The account email address</param>
@@ -82,7 +82,7 @@ namespace VNLib.Plugins.Essentials.Users
/// <exception cref="UserExistsException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="UserCreationFailedException"></exception>
- Task<IUser> CreateUserAsync(string userid, string emailAddress, ulong privilages, PrivateString passHash, CancellationToken cancellation = default);
+ Task<IUser> CreateUserAsync(string userid, string emailAddress, ulong privileges, PrivateString passHash, CancellationToken cancellation = default);
/// <summary>
/// Updates a password associated with the specified user. If the update fails, the transaction
/// is rolled back.
diff --git a/lib/Plugins.PluginBase/src/PluginBase.cs b/lib/Plugins.PluginBase/src/PluginBase.cs
index d2564a6..258a923 100644
--- a/lib/Plugins.PluginBase/src/PluginBase.cs
+++ b/lib/Plugins.PluginBase/src/PluginBase.cs
@@ -32,6 +32,7 @@ using System.Collections.Generic;
using Serilog;
+using VNLib.Utils;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Attributes;
@@ -126,17 +127,16 @@ namespace VNLib.Plugins
[LogInitializer]
public virtual void InitLog(string[] cmdArgs)
{
- //Get the procce's command ling args to check for logging verbosity
- List<string> args = new(cmdArgs);
+ ArgumentList args = new(cmdArgs);
//Open new logger config
LoggerConfiguration logConfig = new();
//Check for verbose
- if (args.Contains("-v"))
+ if (args.HasArgument("-v"))
{
logConfig.MinimumLevel.Verbose();
}
//Check for debug mode
- else if (args.Contains("-d"))
+ else if (args.HasArgument("-d"))
{
logConfig.MinimumLevel.Debug();
}
@@ -147,7 +147,7 @@ namespace VNLib.Plugins
}
//Init console log
- InitConsoleLog(cmdArgs, logConfig);
+ InitConsoleLog(args, logConfig);
//Init file log
InitFileLog(logConfig);
@@ -156,10 +156,10 @@ namespace VNLib.Plugins
Log = new VLogProvider(logConfig);
}
- private void InitConsoleLog(string[] args, LoggerConfiguration logConfig)
+ private void InitConsoleLog(ArgumentList args, LoggerConfiguration logConfig)
{
//If silent arg is not specified, open log to console
- if (!(args.Contains("--silent") || args.Contains("-s")))
+ if (!(args.HasArgument("--silent") || args.HasArgument("-s")))
{
_ = logConfig.WriteTo.Console(outputTemplate: LogTemplate, formatProvider:null);
}
diff --git a/lib/Utils/src/ArgumentList.cs b/lib/Utils/src/ArgumentList.cs
new file mode 100644
index 0000000..ae45e36
--- /dev/null
+++ b/lib/Utils/src/ArgumentList.cs
@@ -0,0 +1,101 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: ArgumentList.cs
+*
+* ArgumentList.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace VNLib.Utils
+{
+ /// <summary>
+ /// Provides methods for parsing an argument list
+ /// </summary>
+ public class ArgumentList : IIndexable<int, string>
+ {
+ private readonly List<string> _args;
+
+ /// <summary>
+ /// Initalzies a the argument parser by copying the given argument array
+ /// </summary>
+ /// <param name="args">The array of arguments to clone</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public ArgumentList(string[] args)
+ {
+ _ = args ?? throw new ArgumentNullException(nameof(args));
+ _args = args.ToList();
+ }
+
+ /// <summary>
+ /// Initalizes the argument parser by copying the given argument list
+ /// </summary>
+ /// <param name="args">The argument list to clone</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public ArgumentList(IReadOnlyList<string> args)
+ {
+ _ = args ?? throw new ArgumentNullException(nameof(args));
+ _args = args.ToList();
+ }
+
+ /// <summary>
+ /// Gets the number of arguments in the list
+ /// </summary>
+ public int Count => _args.Count;
+
+ ///<inheritdoc/>
+ public string this[int key]
+ {
+ get => _args[key];
+ set => _args[key] = value;
+ }
+
+ /// <summary>
+ /// Determines of the given argument is present in the argument list
+ /// </summary>
+ /// <param name="arg"></param>
+ /// <returns>A value that indicates if the argument is present in the list</returns>
+ public bool HasArgument(string arg) => _args.Contains(arg);
+
+ /// <summary>
+ /// Determines if the argument is present in the argument list and
+ /// has a non-null value following it.
+ /// </summary>
+ /// <param name="arg">The argument name to test</param>
+ /// <returns>A value that indicates if a non-null argument is present in the list</returns>
+ public bool HasArgumentValue(string arg) => GetArgument(arg) != null;
+
+ /// <summary>
+ /// Gets the value following the specified argument, or
+ /// null no value follows the specified argument
+ /// </summary>
+ /// <param name="arg">The argument to get following value of</param>
+ /// <returns>The argument value if found</returns>
+ public string? GetArgument(string arg)
+ {
+ int index = _args.IndexOf(arg);
+ return index == -1 || index + 1 >= _args.Count ? null : this[index + 1];
+ }
+
+
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/Async/AsyncUpdatableResource.cs b/lib/Utils/src/Async/AsyncUpdatableResource.cs
index b4ce519..6b8ec62 100644
--- a/lib/Utils/src/Async/AsyncUpdatableResource.cs
+++ b/lib/Utils/src/Async/AsyncUpdatableResource.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -23,28 +23,13 @@
*/
using System;
-using System.IO;
-using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
-using VNLib.Utils.IO;
using VNLib.Utils.Resources;
namespace VNLib.Utils.Async
{
- /// <summary>
- /// A callback delegate used for updating a <see cref="AsyncUpdatableResource"/>
- /// </summary>
- /// <param name="source">The <see cref="AsyncUpdatableResource"/> to be updated</param>
- /// <param name="data">The serialized data to be stored/updated</param>
- /// <exception cref="ResourceUpdateFailedException"></exception>
- public delegate Task AsyncUpdateCallback(object source, Stream data);
- /// <summary>
- /// A callback delegate invoked when a <see cref="AsyncUpdatableResource"/> delete is requested
- /// </summary>
- /// <param name="source">The <see cref="AsyncUpdatableResource"/> to be deleted</param>
- /// <exception cref="ResourceDeleteFailedException"></exception>
- public delegate Task AsyncDeleteCallback(object source);
/// <summary>
/// Implemented by a resource that is backed by an external data store, that when modified or deleted will
@@ -52,8 +37,10 @@ namespace VNLib.Utils.Async
/// </summary>
public abstract class AsyncUpdatableResource : BackedResourceBase, IAsyncExclusiveResource
{
- protected abstract AsyncUpdateCallback UpdateCb { get; }
- protected abstract AsyncDeleteCallback DeleteCb { get; }
+ /// <summary>
+ /// The resource update handler that will be invoked when the resource is modified
+ /// </summary>
+ protected abstract IAsyncResourceStateHandler AsyncHandler { get; }
/// <summary>
/// Releases the resource and flushes pending changes to its backing store.
@@ -62,7 +49,7 @@ namespace VNLib.Utils.Async
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ResourceDeleteFailedException"></exception>
/// <exception cref="ResourceUpdateFailedException"></exception>
- public virtual async ValueTask ReleaseAsync()
+ public virtual async ValueTask ReleaseAsync(CancellationToken cancellation = default)
{
//If resource has already been realeased, return
if (IsReleased)
@@ -72,12 +59,12 @@ namespace VNLib.Utils.Async
//If deleted flag is set, invoke the delete callback
if (Deleted)
{
- await DeleteCb(this).ConfigureAwait(true);
+ await AsyncHandler.DeleteAsync(this, cancellation).ConfigureAwait(true);
}
//If the state has been modifed, flush changes to the store
else if (Modified)
{
- await FlushPendingChangesAsync().ConfigureAwait(true);
+ await FlushPendingChangesAsync(cancellation).ConfigureAwait(true);
}
//Set the released value
IsReleased = true;
@@ -92,18 +79,12 @@ namespace VNLib.Utils.Async
/// Only call this method if your store supports multiple state updates
/// </para>
/// </summary>
- protected virtual async Task FlushPendingChangesAsync()
+ protected virtual async Task FlushPendingChangesAsync(CancellationToken cancellation = default)
{
//Get the resource
object resource = GetResource();
- //Open a memory stream to store data in
- using VnMemoryStream data = new();
- //Serialize and write to stream
- VnEncoding.JSONSerializeToBinary(resource, data, resource.GetType(), base.JSO);
- //Reset stream to begining
- _ = data.Seek(0, SeekOrigin.Begin);
//Invoke update callback
- await UpdateCb(this, data).ConfigureAwait(true);
+ await AsyncHandler.UpdateAsync(this, resource, cancellation).ConfigureAwait(true);
//Clear modified flag
Modified = false;
}
diff --git a/lib/Utils/src/Async/IAsyncExclusiveResource.cs b/lib/Utils/src/Async/IAsyncExclusiveResource.cs
index 93157ce..79657c2 100644
--- a/lib/Utils/src/Async/IAsyncExclusiveResource.cs
+++ b/lib/Utils/src/Async/IAsyncExclusiveResource.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -22,7 +22,9 @@
* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
*/
+using System.Threading;
using System.Threading.Tasks;
+
using VNLib.Utils.Resources;
namespace VNLib.Utils.Async
@@ -35,6 +37,6 @@ namespace VNLib.Utils.Async
/// <summary>
/// Releases the resource from use. Called when a <see cref="ExclusiveResourceHandle{T}"/> is disposed
/// </summary>
- ValueTask ReleaseAsync();
+ ValueTask ReleaseAsync(CancellationToken cancellation = default);
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Async/IAsyncResourceStateHandler.cs b/lib/Utils/src/Async/IAsyncResourceStateHandler.cs
new file mode 100644
index 0000000..843af96
--- /dev/null
+++ b/lib/Utils/src/Async/IAsyncResourceStateHandler.cs
@@ -0,0 +1,53 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IAsyncResourceStateHandler.cs
+*
+* IAsyncResourceStateHandler.cs is part of VNLib.Utils which is part of
+* the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// Represents a resource update handler that processes updates asynchronously
+ /// when requested by a resource holder
+ /// </summary>
+ public interface IAsyncResourceStateHandler
+ {
+ /// <summary>
+ /// Updates the resource in it's backing store
+ /// </summary>
+ /// <param name="resource">The instance of the handler that is requesting the update</param>
+ /// <param name="state">The wrapped resource to update</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that completes when the resource data has successfully been updated</returns>
+ Task UpdateAsync(AsyncUpdatableResource resource, object state, CancellationToken cancellation);
+
+ /// <summary>
+ /// Deletes the resource from it's backing store
+ /// </summary>
+ /// <param name="resource">The instance of the source data to delete</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that completes when the resource has been deleted</returns>
+ Task DeleteAsync(AsyncUpdatableResource resource, CancellationToken cancellation);
+ }
+}
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs
index 0e898b4..32bb3d4 100644
--- a/lib/Utils/src/Extensions/MemoryExtensions.cs
+++ b/lib/Utils/src/Extensions/MemoryExtensions.cs
@@ -334,9 +334,9 @@ namespace VNLib.Utils.Extensions
/// <typeparam name="T">The unmanged data type to provide allocations from</typeparam>
/// <returns>The new <see cref="MemoryPool{T}"/> heap wrapper.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap) where T : unmanaged
+ public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged
{
- return new PrivateBuffersMemoryPool<T>(heap);
+ return new PrivateBuffersMemoryPool<T>(heap, maxBufferSize);
}
/// <summary>
@@ -388,6 +388,7 @@ namespace VNLib.Utils.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
{
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
//Minimum of one element
elements = Math.Max(elements, 1);
//Get element size
diff --git a/lib/Utils/src/Memory/Caching/LRUCache.cs b/lib/Utils/src/Memory/Caching/LRUCache.cs
index 30608af..0c8e169 100644
--- a/lib/Utils/src/Memory/Caching/LRUCache.cs
+++ b/lib/Utils/src/Memory/Caching/LRUCache.cs
@@ -35,17 +35,20 @@ namespace VNLib.Utils.Memory.Caching
public abstract class LRUCache<TKey, TValue> : LRUDataStore<TKey, TValue> where TKey : notnull
{
///<inheritdoc/>
- protected LRUCache()
- {}
+ protected LRUCache(): base()
+ { }
+
///<inheritdoc/>
protected LRUCache(int initialCapacity) : base(initialCapacity)
- {}
+ { }
+
///<inheritdoc/>
protected LRUCache(IEqualityComparer<TKey> keyComparer) : base(keyComparer)
- {}
+ { }
+
///<inheritdoc/>
protected LRUCache(int initialCapacity, IEqualityComparer<TKey> keyComparer) : base(initialCapacity, keyComparer)
- {}
+ { }
/// <summary>
/// The maximum number of items to store in LRU cache
@@ -121,6 +124,7 @@ namespace VNLib.Utils.Memory.Caching
/// </summary>
/// <param name="evicted">The record that is being evicted</param>
protected abstract void Evicted(ref KeyValuePair<TKey, TValue> evicted);
+
/// <summary>
/// Invoked when an entry was requested and was not found in cache.
/// </summary>
diff --git a/lib/Utils/src/Memory/Caching/LRUDataStore.cs b/lib/Utils/src/Memory/Caching/LRUDataStore.cs
index 89d7c12..c1eb22b 100644
--- a/lib/Utils/src/Memory/Caching/LRUDataStore.cs
+++ b/lib/Utils/src/Memory/Caching/LRUDataStore.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -42,6 +42,7 @@ namespace VNLib.Utils.Memory.Caching
/// A lookup table that provides O(1) access times for key-value lookups
/// </summary>
protected Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> LookupTable { get; }
+
/// <summary>
/// A linked list that tracks the least recently used item.
/// New items (or recently accessed items) are moved to the end of the list.
@@ -52,11 +53,8 @@ namespace VNLib.Utils.Memory.Caching
/// <summary>
/// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/>
/// </summary>
- protected LRUDataStore()
- {
- LookupTable = new();
- List = new();
- }
+ protected LRUDataStore() : this(EqualityComparer<TKey>.Default)
+ { }
/// <summary>
/// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and sets
@@ -98,6 +96,7 @@ namespace VNLib.Utils.Memory.Caching
/// <param name="key">The key identifying the value</param>
/// <returns>The value stored at the given key</returns>
/// <remarks>Items are promoted in the store when accessed</remarks>
+ /// <exception cref="KeyNotFoundException"></exception>
public virtual TValue this[TKey key]
{
get
@@ -115,7 +114,7 @@ namespace VNLib.Utils.Memory.Caching
List.Remove(oldNode);
//Reuse the node
- oldNode.ValueRef = new KeyValuePair<TKey, TValue>(key, value);
+ oldNode.Value = new KeyValuePair<TKey, TValue>(key, value);
//Move the item to the back of the list
List.AddLast(oldNode);
@@ -135,8 +134,11 @@ namespace VNLib.Utils.Memory.Caching
/// </summary>
///<exception cref="NotImplementedException"></exception>
public virtual ICollection<TValue> Values => throw new NotSupportedException("Values are not stored in an independent collection, as they are not directly mutable");
+
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => LookupTable.Keys;
+
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => List.Select(static node => node.Value);
+
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => List.Select(static node => node.Value).GetEnumerator();
/// <summary>
@@ -164,10 +166,13 @@ namespace VNLib.Utils.Memory.Caching
public bool Remove(in KeyValuePair<TKey, TValue> item) => Remove(item.Key);
///<inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator();
+
///<inheritdoc/>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => List.CopyTo(array, arrayIndex);
+
///<inheritdoc/>
public virtual bool ContainsKey(TKey key) => LookupTable.ContainsKey(key);
+
///<inheritdoc/>
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => List.GetEnumerator();
@@ -194,6 +199,7 @@ namespace VNLib.Utils.Memory.Caching
LookupTable.Clear();
List.Clear();
}
+
/// <summary>
/// Determines if the <see cref="KeyValuePair{TKey, TValue}"/> exists in the store
/// </summary>
@@ -207,6 +213,7 @@ namespace VNLib.Utils.Memory.Caching
}
return false;
}
+
///<inheritdoc/>
public virtual bool Remove(TKey key)
{
@@ -219,6 +226,7 @@ namespace VNLib.Utils.Memory.Caching
}
return false;
}
+
/// <summary>
/// Tries to get a value from the store with its key. Found items are promoted
/// </summary>
diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
index e73a26f..f2bbd51 100644
--- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
+++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -28,19 +28,22 @@ using System.Buffers;
namespace VNLib.Utils.Memory
{
/// <summary>
- /// Provides a <see cref="MemoryPool{T}"/> wrapper for using unmanged <see cref="Win32PrivateHeap"/>s
+ /// Provides a <see cref="MemoryPool{T}"/> wrapper for using an <see cref="IUnmangedHeap"/>s
/// </summary>
/// <typeparam name="T">Unamanged memory type to provide data memory instances from</typeparam>
public sealed class PrivateBuffersMemoryPool<T> : MemoryPool<T> where T : unmanaged
{
private readonly IUnmangedHeap Heap;
- internal PrivateBuffersMemoryPool(IUnmangedHeap heap):base()
+ internal PrivateBuffersMemoryPool(IUnmangedHeap heap, int maxSize)
{
- this.Heap = heap;
+ Heap = heap;
+ MaxBufferSize = maxSize;
}
+
///<inheritdoc/>
- public override int MaxBufferSize => int.MaxValue;
+ public override int MaxBufferSize { get; }
+
///<inheritdoc/>
///<exception cref="OutOfMemoryException"></exception>
///<exception cref="ObjectDisposedException"></exception>
@@ -53,10 +56,8 @@ namespace VNLib.Utils.Memory
/// <typeparam name="TDifType">The unmanaged data type to allocate for</typeparam>
/// <param name="minBufferSize">Minumum size of the buffer</param>
/// <returns>The memory owner of a different data type</returns>
- public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged
- {
- return new SysBufferMemoryManager<TDifType>(Heap, (uint)minBufferSize, false);
- }
+ public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => new SysBufferMemoryManager<TDifType>(Heap, (uint)minBufferSize, false);
+
///<inheritdoc/>
protected override void Dispose(bool disposing)
{
diff --git a/lib/Utils/src/Resources/BackedResourceBase.cs b/lib/Utils/src/Resources/BackedResourceBase.cs
index 0a2e1e3..22a965c 100644
--- a/lib/Utils/src/Resources/BackedResourceBase.cs
+++ b/lib/Utils/src/Resources/BackedResourceBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -23,7 +23,6 @@
*/
using System;
-using System.Text.Json;
using System.Runtime.CompilerServices;
namespace VNLib.Utils.Resources
@@ -34,23 +33,36 @@ namespace VNLib.Utils.Resources
/// </summary>
public abstract class BackedResourceBase : IResource
{
- ///<inheritdoc/>
- public bool IsReleased { get; protected set; }
+ const int IsReleasedFlag = 1 << 0;
+ const int IsDeletedFlag = 1 << 2;
+ const int IsModifiedFlag = 1 << 3;
- /// <summary>
- /// Optional <see cref="JsonSerializerOptions"/> to be used when serializing
- /// the resource
- /// </summary>
- internal protected virtual JsonSerializerOptions? JSO { get; }
+ private uint _flags;
+
+ ///<inheritdoc/>
+ public bool IsReleased
+ {
+ get => (_flags & IsReleasedFlag) == IsReleasedFlag;
+ protected set => _flags |= IsReleasedFlag;
+ }
/// <summary>
/// A value indicating whether the instance should be deleted when released
/// </summary>
- protected bool Deleted { get; set; }
+ protected bool Deleted
+ {
+ get => (_flags & IsDeletedFlag) == IsDeletedFlag;
+ set => _flags |= IsDeletedFlag;
+ }
+
/// <summary>
/// A value indicating whether the instance should be updated when released
/// </summary>
- protected bool Modified { get; set; }
+ protected bool Modified
+ {
+ get => (_flags & IsModifiedFlag) == IsModifiedFlag;
+ set => _flags |= IsModifiedFlag;
+ }
/// <summary>
/// Checks if the resouce has been disposed and raises an exception if it is
@@ -61,7 +73,7 @@ namespace VNLib.Utils.Resources
{
if (IsReleased)
{
- throw new ObjectDisposedException("The resource has been disposed");
+ throw new ObjectDisposedException(null, "The resource has been disposed");
}
}
@@ -74,6 +86,6 @@ namespace VNLib.Utils.Resources
/// <summary>
/// Marks the resource for deletion from backing store during closing events
/// </summary>
- public virtual void Delete() => Deleted = true;
+ public virtual void Delete() => _flags |= IsDeletedFlag;
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Resources/IResourceStateHandler.cs b/lib/Utils/src/Resources/IResourceStateHandler.cs
new file mode 100644
index 0000000..c3d0d56
--- /dev/null
+++ b/lib/Utils/src/Resources/IResourceStateHandler.cs
@@ -0,0 +1,47 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IResourceStateHandler.cs
+*
+* IResourceStateHandler.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Utils.Resources
+{
+ /// <summary>
+ /// Implemented by a resource that is backed by an external data store, that when modified or deleted will
+ /// be reflected to the backing store.
+ /// </summary>
+ public interface IResourceStateHandler
+ {
+ /// <summary>
+ /// Called when a resource update has been requested
+ /// </summary>
+ /// <param name="resource">The <see cref="UpdatableResource"/> to be updated</param>
+ /// <param name="data">The wrapped state data to update</param>
+ void Update(UpdatableResource resource, object data);
+
+ /// <summary>
+ /// Called when a resource delete has been requested
+ /// </summary>
+ /// <param name="resource">The <see cref="UpdatableResource"/> to be deleted</param>
+ /// <exception cref="ResourceDeleteFailedException"></exception>
+ void Delete(UpdatableResource resource);
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/Resources/UpdatableResource.cs b/lib/Utils/src/Resources/UpdatableResource.cs
index 16f26f2..68072f9 100644
--- a/lib/Utils/src/Resources/UpdatableResource.cs
+++ b/lib/Utils/src/Resources/UpdatableResource.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -23,25 +23,9 @@
*/
using System;
-using System.IO;
-
-using VNLib.Utils.IO;
namespace VNLib.Utils.Resources
{
- /// <summary>
- /// A callback delegate used for updating a <see cref="UpdatableResource"/>
- /// </summary>
- /// <param name="source">The <see cref="UpdatableResource"/> to be updated</param>
- /// <param name="data">The serialized data to be stored/updated</param>
- /// <exception cref="ResourceUpdateFailedException"></exception>
- public delegate void UpdateCallback(object source, Stream data);
- /// <summary>
- /// A callback delegate invoked when a <see cref="UpdatableResource"/> delete is requested
- /// </summary>
- /// <param name="source">The <see cref="UpdatableResource"/> to be deleted</param>
- /// <exception cref="ResourceDeleteFailedException"></exception>
- public delegate void DeleteCallback(object source);
/// <summary>
/// Implemented by a resource that is backed by an external data store, that when modified or deleted will
@@ -50,19 +34,11 @@ namespace VNLib.Utils.Resources
public abstract class UpdatableResource : BackedResourceBase, IExclusiveResource
{
/// <summary>
- /// The update callback method to invoke during a release operation
- /// when the resource is updated.
+ /// Gets the <see cref="IResourceStateHandler"/> that will be invoked when the resource is released
/// </summary>
- protected abstract UpdateCallback UpdateCb { get; }
- /// <summary>
- /// The callback method to invoke during a realease operation
- /// when the resource should be deleted
- /// </summary>
- protected abstract DeleteCallback DeleteCb { get; }
+ protected abstract IResourceStateHandler Handler { get; }
- /// <summary>
/// <inheritdoc/>
- /// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ResourceDeleteFailedException"></exception>
/// <exception cref="ResourceUpdateFailedException"></exception>
@@ -76,7 +52,7 @@ namespace VNLib.Utils.Resources
//If deleted flag is set, invoke the delete callback
if (Deleted)
{
- DeleteCb(this);
+ Handler.Delete(this);
}
//If the state has been modifed, flush changes to the store
else if (Modified)
@@ -88,24 +64,20 @@ namespace VNLib.Utils.Resources
}
/// <summary>
+ /// <para>
/// Writes the current state of the the resource to the backing store
/// immediatly by invoking the specified callback.
- /// <br></br>
- /// <br></br>
+ /// </para>
+ /// <para>
/// Only call this method if your store supports multiple state updates
+ /// </para>
/// </summary>
protected virtual void FlushPendingChanges()
{
//Get the resource
object resource = GetResource();
- //Open a memory stream to store data in
- using VnMemoryStream data = new();
- //Serialize and write to stream
- VnEncoding.JSONSerializeToBinary(resource, data, resource.GetType(), JSO);
- //Reset stream to begining
- _ = data.Seek(0, SeekOrigin.Begin);
//Invoke update callback
- UpdateCb(this, data);
+ Handler.Update(this, resource);
//Clear modified flag
Modified = false;
}
diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs
index 35d52a7..9a50a50 100644
--- a/lib/Utils/src/VnEncoding.cs
+++ b/lib/Utils/src/VnEncoding.cs
@@ -471,7 +471,8 @@ namespace VNLib.Utils
}
}
return value;
- }
+ }
+
/// <summary>
/// Converts the base32 character buffer to its structure representation
/// </summary>
@@ -485,7 +486,7 @@ namespace VNLib.Utils
//calc size of bin buffer
int size = base32.Length;
//Rent a bin buffer
- using UnsafeMemoryHandle<byte> binBuffer = Memory.MemoryUtil.UnsafeAlloc(size);
+ using UnsafeMemoryHandle<byte> binBuffer = MemoryUtil.UnsafeAlloc(size);
//Try to decode the data
ERRNO decoded = TryFromBase32Chars(base32, binBuffer.Span);
//Marshal back to a struct
@@ -505,7 +506,7 @@ namespace VNLib.Utils
return null;
}
//Buffer size of the base32 string will always be enough buffer space
- using UnsafeMemoryHandle<byte> tempBuffer = Memory.MemoryUtil.UnsafeAlloc(base32.Length);
+ using UnsafeMemoryHandle<byte> tempBuffer = MemoryUtil.UnsafeAlloc(base32.Length);
//Try to decode the data
ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span);
@@ -704,6 +705,7 @@ namespace VNLib.Utils
{
return Convert.TryFromBase64Chars(base64, buffer, out int bytesWritten) ? bytesWritten : ERRNO.E_FAIL;
}
+
/// <summary>
/// Tries to convert the 8-bit unsigned integers inside the specified read-only span
/// into their equivalent string representation that is encoded with base-64 digits.
diff --git a/lib/Utils/tests/.runsettings b/lib/Utils/tests/.runsettings
index a4d0377..cf85d64 100644
--- a/lib/Utils/tests/.runsettings
+++ b/lib/Utils/tests/.runsettings
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
- <EnvironmentVariables>
- <VNLIB_SHARED_HEAP_DIAGNOSTICS>1</VNLIB_SHARED_HEAP_DIAGNOSTICS>
- </EnvironmentVariables>
+ <EnvironmentVariables>
+ <VNLIB_SHARED_HEAP_FILE_PATH>F:\Programming\VNLib\core\lib\WinRpMalloc\src\x64\Debug\rpmalloc.dll</VNLIB_SHARED_HEAP_FILE_PATH>
+ <VNLIB_SHARED_HEAP_DIAGNOSTICS>1</VNLIB_SHARED_HEAP_DIAGNOSTICS>
+ </EnvironmentVariables>
</RunConfiguration>
</RunSettings> \ No newline at end of file