From dcf6f9dc0143eb50b702eb7dcd9e5dadd140537c Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 8 Sep 2023 23:20:19 -0400 Subject: Resource, argon2, accounts. sessions core updates --- Taskfile.yaml | 3 +- lib/Hashing.Portable/src/Argon2/Argon2Context.cs | 64 ++++ .../src/Argon2/Argon2CostParams.cs | 53 ++++ .../src/Argon2/Argon2PasswordEntry.cs | 130 ++++---- lib/Hashing.Portable/src/Argon2/Argon2Type.cs | 33 +++ lib/Hashing.Portable/src/Argon2/Argon2Version.cs | 33 +++ lib/Hashing.Portable/src/Argon2/Argon2_Context.cs | 74 ----- lib/Hashing.Portable/src/Argon2/IArgon2Library.cs | 44 +++ .../src/Argon2/SafeArgon2Library.cs | 85 ++++++ lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 326 +++++++++++---------- lib/Net.Http/src/Core/HttpContext.cs | 2 + .../src/Accounts/AccountUtils.cs | 24 +- .../src/Accounts/Argon2ConfigParams.cs | 66 +++++ .../src/Accounts/PasswordHashing.cs | 164 ++++++----- lib/Plugins.Essentials/src/Sessions/ISession.cs | 14 + lib/Plugins.Essentials/src/Sessions/SessionBase.cs | 5 + lib/Plugins.Essentials/src/Sessions/SessionInfo.cs | 14 +- lib/Plugins.Essentials/src/Users/IUser.cs | 13 +- lib/Plugins.Essentials/src/Users/IUserManager.cs | 4 +- lib/Plugins.PluginBase/src/PluginBase.cs | 14 +- lib/Utils/src/ArgumentList.cs | 101 +++++++ lib/Utils/src/Async/AsyncUpdatableResource.cs | 41 +-- lib/Utils/src/Async/IAsyncExclusiveResource.cs | 6 +- lib/Utils/src/Async/IAsyncResourceStateHandler.cs | 53 ++++ lib/Utils/src/Extensions/MemoryExtensions.cs | 5 +- lib/Utils/src/Memory/Caching/LRUCache.cs | 14 +- lib/Utils/src/Memory/Caching/LRUDataStore.cs | 22 +- lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs | 19 +- lib/Utils/src/Resources/BackedResourceBase.cs | 38 ++- lib/Utils/src/Resources/IResourceStateHandler.cs | 47 +++ lib/Utils/src/Resources/UpdatableResource.cs | 46 +-- lib/Utils/src/VnEncoding.cs | 8 +- lib/Utils/tests/.runsettings | 7 +- 33 files changed, 1082 insertions(+), 490 deletions(-) create mode 100644 lib/Hashing.Portable/src/Argon2/Argon2Context.cs create mode 100644 lib/Hashing.Portable/src/Argon2/Argon2CostParams.cs create mode 100644 lib/Hashing.Portable/src/Argon2/Argon2Type.cs create mode 100644 lib/Hashing.Portable/src/Argon2/Argon2Version.cs delete mode 100644 lib/Hashing.Portable/src/Argon2/Argon2_Context.cs create mode 100644 lib/Hashing.Portable/src/Argon2/IArgon2Library.cs create mode 100644 lib/Hashing.Portable/src/Argon2/SafeArgon2Library.cs create mode 100644 lib/Plugins.Essentials/src/Accounts/Argon2ConfigParams.cs create mode 100644 lib/Utils/src/ArgumentList.cs create mode 100644 lib/Utils/src/Async/IAsyncResourceStateHandler.cs create mode 100644 lib/Utils/src/Resources/IResourceStateHandler.cs 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/Argon2Context.cs b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs new file mode 100644 index 0000000..4631654 --- /dev/null +++ b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: Argon2Context.cs +* +* 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 +* 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 System.Runtime.InteropServices; + +namespace VNLib.Hashing +{ + + public static unsafe partial class VnArgon2 + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private ref struct Argon2Context + { + public void* outptr; /* output array */ + public UInt32 outlen; /* digest length */ + + public void* pwd; /* password array */ + public UInt32 pwdlen; /* password length */ + + public void* salt; /* salt array */ + public UInt32 saltlen; /* salt length */ + + public void* secret; /* key array */ + public UInt32 secretlen; /* key length */ + + public void* ad; /* associated data array */ + public UInt32 adlen; /* associated data length */ + + public UInt32 t_cost; /* number of passes */ + public UInt32 m_cost; /* amount of memory requested (KB) */ + public UInt32 lanes; /* number of lanes */ + public UInt32 threads; /* maximum number of threads */ + + public Argon2Version version; /* version number */ + + public void* allocate_cbk; /* pointer to memory allocator */ + public void* free_cbk; /* pointer to memory deallocator */ + + public UInt32 flags; /* array of bool options */ + } + } +} \ No newline at end of file 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 +{ + /// + /// Stores Argon2 hashing cost parameters + /// + public readonly ref struct Argon2CostParams + { + /// + /// Initializes a new structure of with the specified parameters + /// + public Argon2CostParams() + { } + + /// + /// Argon2 hash time cost parameter + /// + public readonly uint TimeCost { get; init; } = 2; + + /// + /// Argon2 hash memory cost parameter + /// + public readonly uint MemoryCost { get; init; } = 65535; + + /// + /// Argon2 hash parallelism parameter + /// + 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 _window; + + public readonly Argon2Version Version; + public readonly ReadOnlySpan Salt; + public readonly ReadOnlySpan Hash; + + private static Argon2Version ParseVersion(ReadOnlySpan window) { - public readonly uint TimeCost; - public readonly uint MemoryCost; - public readonly Argon2_version Version; - public readonly uint Parallelism; - public readonly ReadOnlySpan Salt; - public readonly ReadOnlySpan Hash; + //Version comes after the v= prefix + ReadOnlySpan v = window.SliceAfterParam("v="); + v = v.SliceBeforeParam(','); + //Parse the version as an enum value + return Enum.Parse(v); + } - private static Argon2_version ParseVersion(ReadOnlySpan window) - { - //Version comes after the v= prefix - ReadOnlySpan v = window.SliceAfterParam("v="); - v = v.SliceBeforeParam(','); - //Parse the version as an enum value - return Enum.Parse(v); - } + private static uint ParseTimeCost(ReadOnlySpan window) + { + //TimeCost comes after the t= prefix + ReadOnlySpan 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 window) - { - //TimeCost comes after the t= prefix - ReadOnlySpan 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 window) + { + //MemoryCost comes after the m= prefix + ReadOnlySpan 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 window) - { - //MemoryCost comes after the m= prefix - ReadOnlySpan 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 window) + { + //Parallelism comes after the p= prefix + ReadOnlySpan 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 window) - { - //Parallelism comes after the p= prefix - ReadOnlySpan 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 ParseSalt(ReadOnlySpan window) + { + //Salt comes after the s= prefix + ReadOnlySpan s = window.SliceAfterParam("s="); + s = s.SliceBeforeParam('$'); + //Parse the salt as a string + return s; + } - private static ReadOnlySpan ParseSalt(ReadOnlySpan window) - { - //Salt comes after the s= prefix - ReadOnlySpan s = window.SliceAfterParam("s="); - s = s.SliceBeforeParam('$'); - //Parse the salt as a string - return s; - } + private static ReadOnlySpan ParseHash(ReadOnlySpan 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 ParseHash(ReadOnlySpan 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 str) + { + _window = str; + Version = ParseVersion(str); + Salt = ParseSalt(str); + Hash = ParseHash(str); + } - public Argon2PasswordEntry(ReadOnlySpan 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/Argon2_Context.cs b/lib/Hashing.Portable/src/Argon2/Argon2_Context.cs deleted file mode 100644 index 78d0f13..0000000 --- a/lib/Hashing.Portable/src/Argon2/Argon2_Context.cs +++ /dev/null @@ -1,74 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Hashing.Portable -* File: Argon2_Context.cs -* -* Argon2_Context.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/. -*/ - -/* - * 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; - -namespace VNLib.Hashing -{ - - public static unsafe partial class VnArgon2 - { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private ref struct Argon2_Context - { - public void* outptr; /* output array */ - public UInt32 outlen; /* digest length */ - - public void* pwd; /* password array */ - public UInt32 pwdlen; /* password length */ - - public void* salt; /* salt array */ - public UInt32 saltlen; /* salt length */ - - public void* secret; /* key array */ - public UInt32 secretlen; /* key length */ - - public void* ad; /* associated data array */ - public UInt32 adlen; /* associated data length */ - - public UInt32 t_cost; /* number of passes */ - public UInt32 m_cost; /* amount of memory requested (KB) */ - public UInt32 lanes; /* number of lanes */ - public UInt32 threads; /* maximum number of threads */ - - public Argon2_version version; /* version number */ - - public void* allocate_cbk; /* pointer to memory allocator */ - public void* free_cbk; /* pointer to memory deallocator */ - - public UInt32 flags; /* array of bool options */ - } - } -} \ 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 +{ + /// + /// Represents a native Argon2 library that can be used to hash an argon2 context. + /// + public interface IArgon2Library + { + /// + /// Hashes the data in the and returns the result + /// of the operation as an Argon2 error code. + /// + /// A pointer to a valid argon2 hash context + /// The argon2 status code result + 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 +{ + /// + /// Represents a handle to a 's + /// native method for hashing data with Argon2 + /// + public class SafeArgon2Library : IArgon2Library, IDisposable + { + /* + * The native library method delegate type + */ + [SafeMethodName("argon2id_ctx")] + delegate int Argon2InvokeHash(IntPtr context); + + private readonly SafeMethodHandle methodHandle; + + /// + /// The safe library handle to the native library + /// + public SafeLibraryHandle LibHandle { get; } + + internal SafeArgon2Library(SafeLibraryHandle lib) + { + LibHandle = lib; + //Get the native method + methodHandle = lib.GetMethod(); + } + + /// + /// + public int Argon2Hash(IntPtr context) + { + LibHandle.ThrowIfClosed(); + return methodHandle.Method!.Invoke(context); + } + + /// + /// Disposes the library handle and method handle + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + 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 _heap = new (MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); - private static readonly Lazy _nativeLibrary = new(LoadNativeLib, LazyThreadSafetyMode.PublicationOnly); - + private static readonly Lazy _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 _argon2id_ctx; + [SafeMethodName("argon2id_ctx")] + delegate int Argon2InvokeHash(IntPtr context); - public Argon2NativeLibary(SafeMethodHandle 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); } + /// - /// Loads the native Argon2 libray into the process with env variable library path + /// Gets the sahred native library instance for the current process. /// - /// - private static Argon2NativeLibary LoadNativeLib() + /// The shared library instance + /// + public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Value; + + /// + /// Loads a native Argon2 shared library from the specified path + /// and returns a instance. wrapper + /// + /// The path to the native shared library to load + /// The dll search path + /// A handle for the library + /// + /// + 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 method = lib.GetMethod(ARGON2_CTX_SAFE_METHOD_NAME); - - return new Argon2NativeLibary(method); + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(dllPath, searchPath); + return new SafeArgon2Library(lib); } /// /// Hashes a password with a salt and specified arguments /// + /// /// Span of characters containing the password to be hashed /// Span of characters contating the salt to include in the hashing /// Optional secret to include in hash /// Size of the hash in bytes - /// Memory cost - /// Degree of parallelism - /// Time cost of operation + /// Argon2 cost parameters /// /// /// A containg the ready-to-store hash - public static string Hash2id(ReadOnlySpan password, ReadOnlySpan salt, ReadOnlySpan secret, - uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE) + public static string Hash2id( + this IArgon2Library lib, + ReadOnlySpan password, + ReadOnlySpan salt, + ReadOnlySpan 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); } /// /// Hashes a password with a salt and specified arguments /// + /// /// Span of characters containing the password to be hashed /// Span of characters contating the salt to include in the hashing /// Optional secret to include in hash /// Size of the hash in bytes - /// Memory cost - /// Degree of parallelism - /// Time cost of operation - /// + /// Argon2 cost parameters /// /// /// A containg the ready-to-store hash - public static string Hash2id(ReadOnlySpan password, ReadOnlySpan salt, ReadOnlySpan secret, - uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE) + public static string Hash2id( + this IArgon2Library lib, + ReadOnlySpan password, + ReadOnlySpan salt, + ReadOnlySpan 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); } - + /// /// Hashes a password with a salt and specified arguments /// + /// /// Span of characters containing the password to be hashed /// Span of characters contating the salt to include in the hashing /// Optional secret to include in hash /// Size of the hash in bytes - /// Memory cost - /// Degree of parallelism - /// Time cost of operation + /// Argon2 cost parameters /// /// /// A containg the ready-to-store hash - public static string Hash2id(ReadOnlySpan password, ReadOnlySpan salt, ReadOnlySpan secret, - uint timeCost = 2, uint memCost = 65535, uint parallelism = 4, uint hashLen = HASH_SIZE) + public static string Hash2id( + this IArgon2Library lib, + ReadOnlySpan password, + ReadOnlySpan salt, + ReadOnlySpan secret, + in Argon2CostParams costParams, + uint hashLen = HASH_SIZE + ) { string hash, salts; //Alloc data for hash output using IMemoryHandle hashHandle = PwHeap.Alloc(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}"; } - + /// /// Exposes the raw Argon2-ID hashing api to C#, using spans (pins memory references) /// + /// /// Span of characters containing the password to be hashed /// The output buffer to store the raw hash output /// Span of characters contating the salt to include in the hashing /// Optional secret to include in hash - /// Memory cost - /// Degree of parallelism - /// Time cost of operation + /// Argon2 cost parameters> /// - public static void Hash2id(ReadOnlySpan password, ReadOnlySpan salt, ReadOnlySpan secret, Span rawHashOutput, - uint timeCost = 2, uint memCost = 65535, uint parallelism = 4) + public static void Hash2id( + this IArgon2Library lib, + ReadOnlySpan password, + ReadOnlySpan salt, + ReadOnlySpan secret, + Span 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); } } - /// - /// Compares a raw password, with a salt to a raw hash - /// - /// Password bytes - /// Salt bytes - /// Optional secret that was included in hash - /// Raw hash bytes - /// Time cost - /// Memory cost - /// Degree of parallelism - /// - /// - /// - /// - /// - /// True if hashes match - public static bool Verify2id(ReadOnlySpan rawPass, ReadOnlySpan salt, ReadOnlySpan secret, ReadOnlySpan hashBytes, - uint timeCost = 2, uint memCost = 65535, uint parallelism = 4) - { - //Alloc data for hash output - using IMemoryHandle outputHandle = PwHeap.Alloc(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); - } /// /// Compares a password to a previously hashed password from this library /// + /// /// Password data /// Optional secret that was included in hash /// Full hash span @@ -346,7 +286,12 @@ namespace VNLib.Hashing /// /// /// True if the password matches the hash - public static bool Verify2id(ReadOnlySpan rawPass, ReadOnlySpan hash, ReadOnlySpan secret) + public static bool Verify2id( + this IArgon2Library lib, + ReadOnlySpan rawPass, + ReadOnlySpan hash, + ReadOnlySpan 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 saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; Span passBuf = rawBufferHandle.AsSpan(saltBase64BufSize, passBase64BufSize); Span 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); + } + + /// + /// Compares a raw password, with a salt to a raw hash + /// + /// + /// Password bytes + /// Salt bytes + /// Optional secret that was included in hash + /// Raw hash bytes + /// Argon2 cost parameters + /// + /// + /// + /// + /// + /// True if hashes match + public static bool Verify2id( + this IArgon2Library lib, + ReadOnlySpan rawPass, + ReadOnlySpan salt, + ReadOnlySpan secret, + ReadOnlySpan hashBytes, + in Argon2CostParams costParams + ) + { + //Alloc data for hash output + using IMemoryHandle outputHandle = PwHeap.Alloc(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 /// public readonly HttpRequest Request; + /// /// The reusable response controler /// public readonly HttpResponse Response; + /// /// The http server that this context is bound to /// 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); } + /// + /// 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. + /// + /// + /// The user's email address or secondary id + /// The user's password + /// Optional user privilage level + /// A token to cancel the operation + /// A task that resolves the new user + /// + /// + /// + public static Task 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); + } /// /// 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 +{ + /// + /// The configuration parameters for the Argon2 hashing algorithm + /// + public readonly record struct Argon2ConfigParams + { + + /// + /// Initializes a new instance with the default values + /// + public Argon2ConfigParams() + { } + + /// + /// The length of the random salt to use in bytes (defaults to 32) + /// + public int SaltLen { get; init; } = 32; + + /// + /// The Argon2 time cost parameter (defaults to 4) + /// + public uint TimeCost { get; init; } = 4; + + /// + /// The Argon2 memory cost parameter (defaults to UInt16.MaxValue) + /// + public uint MemoryCost { get; init; } = UInt16.MaxValue; + + /// + /// The Argon2 default hash length parameter (defaults to 128) + /// + public uint HashLen { get; init; } = 128; + + /// + /// The Argon2 parallelism parameter (defaults to the number of logical processors) + /// + 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 /// 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; + /// + /// Creates a new instance using the specified library. + /// + /// The library instance to use + /// The password secret provider + /// The configuration setup arguments + /// The instance of the library to use + public static PasswordHashing Create(IArgon2Library library, ISecretProvider secret, in Argon2ConfigParams setup) => new (library, secret, setup); /// - /// Initalizes the class + /// Creates a new instance using the default + /// library. /// /// The password secret provider - /// A positive integer for the size of the random salt used during the hashing proccess - /// The Argon2 time cost parameter - /// The Argon2 memory cost parameter - /// The size of the hash to produce during hashing operations - /// - /// The Argon2 parallelism parameter (the number of threads to use for hasing) - /// (default = 0 - defaults to the number of logical processors) - /// - /// - public PasswordHashing(ISecretProvider secret, int saltLen = 32, uint timeCost = 4, uint memoryCost = UInt16.MaxValue, uint parallism = 0, uint hashLen = 128) + /// The configuration setup arguments + /// The instance of the library to use + /// + 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 + }; } - /// /// /// @@ -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 /// Uses fixed time comparison from class public bool Verify(ReadOnlySpan hash, ReadOnlySpan salt, ReadOnlySpan password) { - //Alloc a buffer with the same size as the hash - using UnsafeMemoryHandle 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 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 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); + } } /// @@ -142,22 +162,25 @@ namespace VNLib.Plugins.Essentials.Accounts /// A of the hashed and encoded password public PrivateString Hash(ReadOnlySpan password) { + Argon2CostParams costParams = GetCostParams(); + //Alloc shared buffer for the salt and secret buffer - using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(SaltLen + _secret.BufferSize, true); + using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(_config.SaltLen + _secret.BufferSize, true); + + //Split buffers + Span saltBuf = buffer.Span[.._config.SaltLen]; + Span secretBuf = buffer.Span[_config.SaltLen..]; + + //Fill the buffer with random bytes + RandomHash.GetRandomBytes(saltBuf); + try { - //Split buffers - Span saltBuf = buffer.Span[..SaltLen]; - Span 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 /// A of the hashed and encoded password public PrivateString Hash(ReadOnlySpan password) { - using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(SaltLen + _secret.BufferSize, true); - try - { - //Split buffers - Span saltBuf = buffer.Span[..SaltLen]; - Span secretBuf = buffer.Span[SaltLen..]; + Argon2CostParams costParams = GetCostParams(); + + using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(_config.SaltLen + _secret.BufferSize, true); - //Fill the buffer with random bytes - RandomHash.GetRandomBytes(saltBuf); + //Split buffers + Span saltBuf = buffer.Span[.._config.SaltLen]; + Span 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 /// public void Hash(ReadOnlySpan password, ReadOnlySpan salt, Span hashOutput) { + Argon2CostParams costParams = GetCostParams(); + //alloc secret buffer - using UnsafeMemoryHandle secretBuffer = MemoryUtil.UnsafeAlloc(_secret.BufferSize, true); + using UnsafeMemoryHandle 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 /// /// /// - public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) - { - throw new NotSupportedException(); - } + public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) => throw new NotSupportedException(); /// /// public ERRNO Hash(ReadOnlySpan password, Span 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 buffer = MemoryUtil.UnsafeAllocNearestPage(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 /// SessionType SessionType { get; } + /// /// UTC time in when the session was created /// DateTimeOffset Created { get; } + /// /// Privilages associated with user specified during login /// ulong Privilages { get; set; } + /// /// Key that identifies the current session. (Identical to cookie::sessionid) /// string SessionID { get; } + /// /// User ID associated with session /// string UserID { get; set; } + /// /// Marks the session as invalid /// void Invalidate(bool all = false); + /// /// Gets or sets the session's authorization token /// string Token { get; set; } + /// /// The IP address belonging to the client /// IPAddress UserIP { get; } + /// /// Sets the session ID to be regenerated if applicable /// @@ -90,5 +98,11 @@ namespace VNLib.Plugins.Essentials.Sessions /// A value that indicates this session was newly created /// bool IsNew { get; } + + /// + /// This is a special function that requests the session to be detached from the current http connection + /// but allow it to remain available. + /// + 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 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void RegenID() => Flags.Set(REGEN_ID_MSK); + + /// + public virtual void Detach() => Flags.Set(DETACHED_MSK); + /// /// Invoked when the indexer is is called to /// 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 /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invalidate(bool all = false) => UserSession.Invalidate(all); + /// /// Marks the session ID to be regenerated during closing event /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RegenID() => UserSession.RegenID(); + /// + /// Marks the session to be detached from the current connection. + /// + public void Detach() => UserSession.Detach(); + + #nullable disable + /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetObject(string key) => JsonSerializer.Deserialize(this[key], SR_OPTIONS); + /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetObject(string key, T obj) => this[key] = obj == null ? null: JsonSerializer.Serialize(obj, SR_OPTIONS); + #nullable enable /// 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>, IIndexable { /// - /// The user's privilage level + /// The user's privilege level /// - ulong Privilages { get; } + ulong Privileges { get; set; } + /// /// The user's ID /// string UserID { get; } + /// /// Date the user's account was created /// DateTimeOffset Created { get; } + /// /// The user's password hash if retreived from the backing store, otherwise null /// PrivateString? PassHash { get; } + /// /// Status of account /// UserStatus Status { get; set; } + /// /// Is the account only usable from local network? /// bool LocalOnly { get; set; } + /// /// The user's email address /// string EmailAddress { get; set; } + /// /// Marks the user for deletion on release /// 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) /// /// The user id - /// A number representing the privilage level of the account + /// A number representing the privilage level of the account /// Value to store in the password field /// A token to cancel the operation /// The account email address @@ -82,7 +82,7 @@ namespace VNLib.Plugins.Essentials.Users /// /// /// - Task CreateUserAsync(string userid, string emailAddress, ulong privilages, PrivateString passHash, CancellationToken cancellation = default); + Task CreateUserAsync(string userid, string emailAddress, ulong privileges, PrivateString passHash, CancellationToken cancellation = default); /// /// 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 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 +{ + /// + /// Provides methods for parsing an argument list + /// + public class ArgumentList : IIndexable + { + private readonly List _args; + + /// + /// Initalzies a the argument parser by copying the given argument array + /// + /// The array of arguments to clone + /// + public ArgumentList(string[] args) + { + _ = args ?? throw new ArgumentNullException(nameof(args)); + _args = args.ToList(); + } + + /// + /// Initalizes the argument parser by copying the given argument list + /// + /// The argument list to clone + /// + public ArgumentList(IReadOnlyList args) + { + _ = args ?? throw new ArgumentNullException(nameof(args)); + _args = args.ToList(); + } + + /// + /// Gets the number of arguments in the list + /// + public int Count => _args.Count; + + /// + public string this[int key] + { + get => _args[key]; + set => _args[key] = value; + } + + /// + /// Determines of the given argument is present in the argument list + /// + /// + /// A value that indicates if the argument is present in the list + public bool HasArgument(string arg) => _args.Contains(arg); + + /// + /// Determines if the argument is present in the argument list and + /// has a non-null value following it. + /// + /// The argument name to test + /// A value that indicates if a non-null argument is present in the list + public bool HasArgumentValue(string arg) => GetArgument(arg) != null; + + /// + /// Gets the value following the specified argument, or + /// null no value follows the specified argument + /// + /// The argument to get following value of + /// The argument value if found + 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 { - /// - /// A callback delegate used for updating a - /// - /// The to be updated - /// The serialized data to be stored/updated - /// - public delegate Task AsyncUpdateCallback(object source, Stream data); - /// - /// A callback delegate invoked when a delete is requested - /// - /// The to be deleted - /// - public delegate Task AsyncDeleteCallback(object source); /// /// 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 /// public abstract class AsyncUpdatableResource : BackedResourceBase, IAsyncExclusiveResource { - protected abstract AsyncUpdateCallback UpdateCb { get; } - protected abstract AsyncDeleteCallback DeleteCb { get; } + /// + /// The resource update handler that will be invoked when the resource is modified + /// + protected abstract IAsyncResourceStateHandler AsyncHandler { get; } /// /// Releases the resource and flushes pending changes to its backing store. @@ -62,7 +49,7 @@ namespace VNLib.Utils.Async /// /// /// - 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 /// /// - 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 /// /// Releases the resource from use. Called when a is disposed /// - 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 +{ + /// + /// Represents a resource update handler that processes updates asynchronously + /// when requested by a resource holder + /// + public interface IAsyncResourceStateHandler + { + /// + /// Updates the resource in it's backing store + /// + /// The instance of the handler that is requesting the update + /// The wrapped resource to update + /// A token to cancel the operation + /// A task that completes when the resource data has successfully been updated + Task UpdateAsync(AsyncUpdatableResource resource, object state, CancellationToken cancellation); + + /// + /// Deletes the resource from it's backing store + /// + /// The instance of the source data to delete + /// A token to cancel the operation + /// A task that completes when the resource has been deleted + 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 /// The unmanged data type to provide allocations from /// The new heap wrapper. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryPool ToPool(this IUnmangedHeap heap) where T : unmanaged + public static MemoryPool ToPool(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged { - return new PrivateBuffersMemoryPool(heap); + return new PrivateBuffersMemoryPool(heap, maxBufferSize); } /// @@ -388,6 +388,7 @@ namespace VNLib.Utils.Extensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe MemoryHandle Alloc(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 : LRUDataStore where TKey : notnull { /// - protected LRUCache() - {} + protected LRUCache(): base() + { } + /// protected LRUCache(int initialCapacity) : base(initialCapacity) - {} + { } + /// protected LRUCache(IEqualityComparer keyComparer) : base(keyComparer) - {} + { } + /// protected LRUCache(int initialCapacity, IEqualityComparer keyComparer) : base(initialCapacity, keyComparer) - {} + { } /// /// The maximum number of items to store in LRU cache @@ -121,6 +124,7 @@ namespace VNLib.Utils.Memory.Caching /// /// The record that is being evicted protected abstract void Evicted(ref KeyValuePair evicted); + /// /// Invoked when an entry was requested and was not found in cache. /// 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 /// protected Dictionary>> LookupTable { get; } + /// /// 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 /// /// Initializes an empty /// - protected LRUDataStore() - { - LookupTable = new(); - List = new(); - } + protected LRUDataStore() : this(EqualityComparer.Default) + { } /// /// Initializes an empty and sets @@ -98,6 +96,7 @@ namespace VNLib.Utils.Memory.Caching /// The key identifying the value /// The value stored at the given key /// Items are promoted in the store when accessed + /// 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(key, value); + oldNode.Value = new KeyValuePair(key, value); //Move the item to the back of the list List.AddLast(oldNode); @@ -135,8 +134,11 @@ namespace VNLib.Utils.Memory.Caching /// /// public virtual ICollection Values => throw new NotSupportedException("Values are not stored in an independent collection, as they are not directly mutable"); + IEnumerable IReadOnlyDictionary.Keys => LookupTable.Keys; + IEnumerable IReadOnlyDictionary.Values => List.Select(static node => node.Value); + IEnumerator IEnumerable.GetEnumerator() => List.Select(static node => node.Value).GetEnumerator(); /// @@ -164,10 +166,13 @@ namespace VNLib.Utils.Memory.Caching public bool Remove(in KeyValuePair item) => Remove(item.Key); /// IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator(); + /// public void CopyTo(KeyValuePair[] array, int arrayIndex) => List.CopyTo(array, arrayIndex); + /// public virtual bool ContainsKey(TKey key) => LookupTable.ContainsKey(key); + /// public virtual IEnumerator> GetEnumerator() => List.GetEnumerator(); @@ -194,6 +199,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable.Clear(); List.Clear(); } + /// /// Determines if the exists in the store /// @@ -207,6 +213,7 @@ namespace VNLib.Utils.Memory.Caching } return false; } + /// public virtual bool Remove(TKey key) { @@ -219,6 +226,7 @@ namespace VNLib.Utils.Memory.Caching } return false; } + /// /// Tries to get a value from the store with its key. Found items are promoted /// 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 { /// - /// Provides a wrapper for using unmanged s + /// Provides a wrapper for using an s /// /// Unamanged memory type to provide data memory instances from public sealed class PrivateBuffersMemoryPool : MemoryPool 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; } + /// - public override int MaxBufferSize => int.MaxValue; + public override int MaxBufferSize { get; } + /// /// /// @@ -53,10 +56,8 @@ namespace VNLib.Utils.Memory /// The unmanaged data type to allocate for /// Minumum size of the buffer /// The memory owner of a different data type - public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged - { - return new SysBufferMemoryManager(Heap, (uint)minBufferSize, false); - } + public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged => new SysBufferMemoryManager(Heap, (uint)minBufferSize, false); + /// 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 /// public abstract class BackedResourceBase : IResource { - /// - public bool IsReleased { get; protected set; } + const int IsReleasedFlag = 1 << 0; + const int IsDeletedFlag = 1 << 2; + const int IsModifiedFlag = 1 << 3; - /// - /// Optional to be used when serializing - /// the resource - /// - internal protected virtual JsonSerializerOptions? JSO { get; } + private uint _flags; + + /// + public bool IsReleased + { + get => (_flags & IsReleasedFlag) == IsReleasedFlag; + protected set => _flags |= IsReleasedFlag; + } /// /// A value indicating whether the instance should be deleted when released /// - protected bool Deleted { get; set; } + protected bool Deleted + { + get => (_flags & IsDeletedFlag) == IsDeletedFlag; + set => _flags |= IsDeletedFlag; + } + /// /// A value indicating whether the instance should be updated when released /// - protected bool Modified { get; set; } + protected bool Modified + { + get => (_flags & IsModifiedFlag) == IsModifiedFlag; + set => _flags |= IsModifiedFlag; + } /// /// 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 /// /// Marks the resource for deletion from backing store during closing events /// - 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 +{ + /// + /// Implemented by a resource that is backed by an external data store, that when modified or deleted will + /// be reflected to the backing store. + /// + public interface IResourceStateHandler + { + /// + /// Called when a resource update has been requested + /// + /// The to be updated + /// The wrapped state data to update + void Update(UpdatableResource resource, object data); + + /// + /// Called when a resource delete has been requested + /// + /// The to be deleted + /// + 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 { - /// - /// A callback delegate used for updating a - /// - /// The to be updated - /// The serialized data to be stored/updated - /// - public delegate void UpdateCallback(object source, Stream data); - /// - /// A callback delegate invoked when a delete is requested - /// - /// The to be deleted - /// - public delegate void DeleteCallback(object source); /// /// 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 { /// - /// The update callback method to invoke during a release operation - /// when the resource is updated. + /// Gets the that will be invoked when the resource is released /// - protected abstract UpdateCallback UpdateCb { get; } - /// - /// The callback method to invoke during a realease operation - /// when the resource should be deleted - /// - protected abstract DeleteCallback DeleteCb { get; } + protected abstract IResourceStateHandler Handler { get; } - /// /// - /// /// /// /// @@ -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 } /// + /// /// Writes the current state of the the resource to the backing store /// immediatly by invoking the specified callback. - ///

- ///

+ ///
+ /// /// Only call this method if your store supports multiple state updates + /// ///
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; - } + } + /// /// Converts the base32 character buffer to its structure representation /// @@ -485,7 +486,7 @@ namespace VNLib.Utils //calc size of bin buffer int size = base32.Length; //Rent a bin buffer - using UnsafeMemoryHandle binBuffer = Memory.MemoryUtil.UnsafeAlloc(size); + using UnsafeMemoryHandle 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 tempBuffer = Memory.MemoryUtil.UnsafeAlloc(base32.Length); + using UnsafeMemoryHandle 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; } + /// /// 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 @@  - - 1 - + + F:\Programming\VNLib\core\lib\WinRpMalloc\src\x64\Debug\rpmalloc.dll + 1 + \ No newline at end of file -- cgit