diff options
author | vnugent <public@vaughnnugent.com> | 2023-09-08 23:20:19 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-09-08 23:20:19 -0400 |
commit | dcf6f9dc0143eb50b702eb7dcd9e5dadd140537c (patch) | |
tree | 41efba0de085e6d49a0228b3f942824b2395383d | |
parent | c38d794d808b06240b778b5b320f506382215e29 (diff) |
Resource, argon2, accounts. sessions core updates
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 |