diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs | 4 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs | 2 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/ManagedHash.cs | 8 | ||||
-rw-r--r-- | lib/Net.Messaging.FBM/src/Client/FBMRequest.cs | 2 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs | 2 | ||||
-rw-r--r-- | lib/Utils/src/Extensions/MemoryExtensions.cs | 44 | ||||
-rw-r--r-- | lib/Utils/src/Memory/ArrayPoolBuffer.cs | 33 | ||||
-rw-r--r-- | lib/Utils/src/Memory/IResizeableMemoryHandle.cs | 6 | ||||
-rw-r--r-- | lib/Utils/src/Memory/IUnmangedHeap.cs | 6 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryHandle.cs | 17 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 82 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtilAlloc.cs | 31 | ||||
-rw-r--r-- | lib/Utils/src/Memory/UnmanagedHeapBase.cs | 6 | ||||
-rw-r--r-- | lib/Utils/src/Memory/UnsafeMemoryHandle.cs | 118 | ||||
-rw-r--r-- | lib/Utils/src/Memory/VnString.cs | 198 | ||||
-rw-r--r-- | lib/Utils/src/VnEncoding.cs | 109 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 6 |
17 files changed, 427 insertions, 247 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs index 278359c..65704df 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs @@ -163,7 +163,7 @@ namespace VNLib.Hashing.IdentityUtility using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(hashBufSize); //compute hash - if (!hmac.TryComputeHash(raw, buffer, out int hashBytesWritten)) + if (!hmac.TryComputeHash(raw, buffer.Span, out int hashBytesWritten)) { throw new InternalBufferTooSmallException("Hash buffer size was too small"); } @@ -199,7 +199,7 @@ namespace VNLib.Hashing.IdentityUtility using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(buffSize, true); //Encode data - int converted = enc.GetBytes(data, buffer); + int converted = enc.GetBytes(data, buffer.Span); //Try encrypt return !alg.TryEncrypt(buffer.Span, output, padding, out int bytesWritten) ? ERRNO.E_FAIL : (ERRNO)bytesWritten; diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index c5409a8..158a1b8 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -69,7 +69,7 @@ namespace VNLib.Hashing.IdentityUtility using MemoryHandle<byte> binBuffer = heap.Alloc<byte>(utf8Size, true); //Decode to utf8 - utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer); + utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer.Span); //Parse and return the jwt return ParseRaw(binBuffer.Span[..utf8Size], heap); diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs index 5a6aaba..43c2a7c 100644 --- a/lib/Hashing.Portable/src/ManagedHash.cs +++ b/lib/Hashing.Portable/src/ManagedHash.cs @@ -136,7 +136,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHash(binbuf.Span[..byteCount], buffer, type); @@ -156,7 +156,7 @@ namespace VNLib.Hashing //Alloc buffer using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHash(binbuf.Span[..byteCount], type); } @@ -289,7 +289,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], output, type); @@ -312,7 +312,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], type); diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs index 418a9ec..8432d27 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs @@ -244,7 +244,7 @@ namespace VNLib.Net.Messaging.FBM.Client using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(charSize + 128); - ERRNO count = Compile(buffer); + ERRNO count = Compile(buffer.Span); return buffer.AsSpan(0, count).ToString(); } diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs index b9fde20..509a8d0 100644 --- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs +++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs @@ -103,7 +103,7 @@ namespace VNLib.Plugins.Essentials.Accounts //Alloc heap buffer using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAlloc(_secret.BufferSize, true); - return VerifyInternal(passHash, password, secretBuffer); + return VerifyInternal(passHash, password, secretBuffer.Span); } } diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index 9553578..7ff9936 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -48,7 +48,39 @@ namespace VNLib.Utils.Extensions /// <param name="size">The minimum size array to allocate</param> /// <param name="zero">Should elements from 0 to size be set to default(T)</param> /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> - public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged => new(pool, size, zero); + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged + { + T[] array = pool.Rent(size); + + if (zero) + { + MemoryUtil.InitializeBlock(array, (uint)size); + } + + return new(pool, array, size); + } + + /// <summary> + /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the + /// array when work is completed + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="pool"></param> + /// <param name="size">The minimum size array to allocate</param> + /// <param name="zero">Should elements from 0 to size be set to default(T)</param> + /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> + public static IMemoryHandle<T> SafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : struct + { + T[] array = pool.Rent(size); + + if (zero) + { + MemoryUtil.InitializeBlock(array, (uint)size); + } + + //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed + return new ArrayPoolBuffer<T>(pool, array, size); + } /// <summary> /// Retreives a buffer that is at least the reqested length, and clears the array from 0-size. @@ -60,14 +92,14 @@ namespace VNLib.Utils.Extensions /// <param name="zero">True if contents should be zeroed</param> /// <returns>The zeroed array</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Rent<T>(this ArrayPool<T> pool, int size, bool zero) + public static T[] Rent<T>(this ArrayPool<T> pool, int size, bool zero) { //Rent the array T[] arr = pool.Rent(size); //If zero flag is set, zero only the used section if (zero) { - arr.AsSpan().Clear(); + Array.Clear(arr, 0, size); } return arr; } @@ -405,7 +437,7 @@ namespace VNLib.Utils.Extensions /// <param name="heap"></param> /// <param name="structRef">A reference to the structure</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void StructFreeRef<T>(this IUnmangedHeap heap, ref T structRef) where T : unmanaged => MemoryUtil.StructFreeRef(heap, ref structRef); + public static void StructFreeRef<T>(this IUnmangedHeap heap, ref T structRef) where T : unmanaged => MemoryUtil.StructFreeRef(heap, ref structRef); /// <summary> /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type @@ -519,7 +551,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged { if (elements < 1) { @@ -528,7 +560,7 @@ namespace VNLib.Utils.Extensions } //Get element size - nuint elementSize = (nuint)sizeof(T); + nuint elementSize = (nuint)Unsafe.SizeOf<T>(); //If zero flag is set then specify zeroing memory (safe case because of the above check) IntPtr block = heap.Alloc((nuint)elements, elementSize, zero); diff --git a/lib/Utils/src/Memory/ArrayPoolBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs index 92a2022..2f00e66 100644 --- a/lib/Utils/src/Memory/ArrayPoolBuffer.cs +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: VnTempBuffer.cs +* File: ArrayPoolBuffer.cs * -* VnTempBuffer.cs is part of VNLib.Utils which is part of the larger +* ArrayPoolBuffer.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 @@ -35,7 +35,11 @@ namespace VNLib.Utils.Memory /// A disposable temporary buffer from shared ArrayPool /// </summary> /// <typeparam name="T">Type of buffer to create</typeparam> - public sealed class ArrayPoolBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T> + public sealed class ArrayPoolBuffer<T> : + VnDisposeable, + IIndexable<int, T>, + IMemoryHandle<T>, + IMemoryOwner<T> { private readonly ArrayPool<T> Pool; @@ -92,7 +96,28 @@ namespace VNLib.Utils.Memory Buffer = pool.Rent(minSize, zero); InitSize = minSize; } - + + /// <summary> + /// Initialzies a new <see cref="ArrayPoolBuffer{T}"/> from the specified rented array + /// that belongs to the supplied pool + /// </summary> + /// <param name="pool">The pool the array was rented from</param> + /// <param name="array">The rented array</param> + /// <param name="size">The size of the buffer around the array. May be smaller or exact size of the array</param> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public ArrayPoolBuffer(ArrayPool<T> pool, T[] array, int size) + { + Pool = pool ?? throw new ArgumentNullException(nameof(pool)); + Buffer = array ?? throw new ArgumentNullException(nameof(array)); + + if (size < 0 || size > array.Length) + throw new ArgumentOutOfRangeException(nameof(size)); + + InitSize = size; + } + + /// <summary> /// Gets an offset wrapper around the current buffer /// </summary> diff --git a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs index f788b48..1d83196 100644 --- a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs +++ b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs @@ -42,11 +42,15 @@ namespace VNLib.Utils.Memory /// Resizes a memory handle to a new number of elements. /// </summary> /// <remarks> - /// Even if a handle is resizable resizing may not be supported for all types of handles. + /// Even if a handle is resizable resizing may not be supported for all types of handles. <br/> + /// Be careful not to resize handles that are pinned or have raw pointers/references floating. /// </remarks> /// <param name="elements">The new number of elements to resize the handle to</param> /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="OverflowException"></exception> /// <exception cref="NotSupportedException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> void Resize(nuint elements); } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/IUnmangedHeap.cs b/lib/Utils/src/Memory/IUnmangedHeap.cs index cb4b6ba..fdc0f9b 100644 --- a/lib/Utils/src/Memory/IUnmangedHeap.cs +++ b/lib/Utils/src/Memory/IUnmangedHeap.cs @@ -43,15 +43,19 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements to allocate</param> /// <param name="zero">An optional parameter to zero the block of memory</param> /// <returns></returns> + /// <exception cref="OutOfMemoryException"></exception> IntPtr Alloc(nuint elements, nuint size, bool zero); /// <summary> - /// Resizes the allocated block of memory to the new size + /// Resizes the allocated block of memory to the new size. + /// May not be supported by all heaps. /// </summary> /// <param name="block">The block to resize</param> /// <param name="elements">The new number of elements</param> /// <param name="size">The size (in bytes) of the type</param> /// <param name="zero">An optional parameter to zero the block of memory</param> + /// <exception cref="NotSupportedException"></exception> + /// <exception cref="OutOfMemoryException"></exception> void Resize(ref IntPtr block, nuint elements, nuint size, bool zero); /// <summary> diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 4d2ff0c..f474abd 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -71,10 +71,7 @@ namespace VNLib.Utils.Memory get => (IntPtr)GetOffset(0); } - /// <summary> - /// Gets a span over the entire allocated block - /// </summary> - /// <returns>A <see cref="Span{T}"/> over the internal data</returns> + /// <inheritdoc/> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="OverflowException"></exception> public unsafe Span<T> Span @@ -145,10 +142,7 @@ namespace VNLib.Utils.Memory Heap = null!; } - /// <summary> - /// Resizes the current handle on the heap - /// </summary> - /// <param name="elements">Positive number of elemnts the current handle should referrence</param> + /// <inheritdoc/> /// <exception cref="OverflowException"></exception> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> @@ -263,12 +257,5 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> public override int GetHashCode() => base.GetHashCode(); - - ///<inheritdoc/> - public static implicit operator Span<T>(MemoryHandle<T> handle) - { - //If the handle is invalid or closed return an empty span - return handle.IsClosed || handle.IsInvalid || handle._length == 0 ? Span<T>.Empty : handle.Span; - } } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 52a9528..c149c85 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -243,12 +243,9 @@ namespace VNLib.Utils.Memory } uint byteSize = ByteCount<T>((uint)block.Length); - ref T r0 = ref MemoryMarshal.GetReference(block); - ref byte byteRef = ref Unsafe.As<T, byte>(ref r0); - //Calls memset - Unsafe.InitBlock(ref byteRef, 0, byteSize); + ZeroByRef(ref r0, byteSize); } /// <summary> @@ -272,6 +269,19 @@ namespace VNLib.Utils.Memory Unsafe.InitBlock(handle.Pointer, 0, byteSize); } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static void ZeroByRef<T>(ref T src, uint elements) + { + Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); + + //Convert to bytes + uint byteSize = ByteCount<T>(elements); + ref byte byteRef = ref Unsafe.As<T, byte>(ref src); + + //Call init block + Unsafe.InitBlock(ref byteRef, 0, byteSize); + } + /* * Initializing a non-readonly span/memory as of .NET 6.0 is a reference * reintpretation, essentially a pointer cast, so there is little/no cost @@ -294,6 +304,39 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitializeBlock<T>(Memory<T> block) where T : struct => UnsafeZeroMemory<T>(block); + /// <summary> + /// Initializes the entire array with zeros + /// </summary> + /// <typeparam name="T">A structure type to initialize</typeparam> + /// <param name="array">The array to zero</param> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitializeBlock<T>(T[] array) where T : struct => InitializeBlock(array, (uint)array.Length); + + /// <summary> + /// Initializes the array with zeros up to the specified count + /// </summary> + /// <typeparam name="T">A structure type to initialize</typeparam> + /// <param name="array">The array to zero</param> + /// <param name="count">The number of elements in the array to zero</param> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitializeBlock<T>(T[] array, uint count) where T: struct + { + if(array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + //Check bounds + CheckBounds(array, 0, count); + + //Get array data reference + ref T arrRef = ref MemoryMarshal.GetArrayDataReference(array); + ZeroByRef(ref arrRef, count); + } /// <summary> /// Zeroes a block of memory of the given unmanaged type @@ -314,10 +357,7 @@ namespace VNLib.Utils.Memory return; } - //To bytereference - ref byte byteRef = ref Unsafe.As<T, byte>(ref block); - //Zero block - Unsafe.InitBlock(ref byteRef, 0, ByteCount<T>((uint)itemCount)); + ZeroByRef(ref block, (uint)itemCount); } /// <summary> @@ -1287,5 +1327,31 @@ namespace VNLib.Utils.Memory //Multiply back to page sizes return pages * SystemPageSize; } + + /// <summary> + /// Rounds the requested number of elements up to the nearest page + /// </summary> + /// <typeparam name="T">The unmanaged type</typeparam> + /// <param name="elements">The number of elements of size T to round</param> + /// <returns>The number of elements rounded to the nearest page in elements</returns> + public static nuint NearestPage<T>(nuint elements) where T : unmanaged + { + nuint elSize = (nuint)sizeof(T); + //Round to nearest page (in bytes) + return NearestPage(elements * elSize) / elSize; + } + + /// <summary> + /// Rounds the requested number of elements up to the nearest page + /// </summary> + /// <typeparam name="T">The unmanaged type</typeparam> + /// <param name="elements">The number of elements of size T to round</param> + /// <returns>The number of elements rounded to the nearest page in elements</returns> + public static nint NearestPage<T>(nint elements) where T : unmanaged + { + nint elSize = sizeof(T); + //Round to nearest page (in bytes) + return NearestPage(elements * elSize) / elSize; + } } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs index bc0f35e..0f25682 100644 --- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -52,6 +52,11 @@ namespace VNLib.Utils.Memory throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } + if (elements == 0) + { + return default; + } + /* * We may allocate from the share heap only if the heap is not using locks * or if the element size could cause performance issues because its too large @@ -69,7 +74,8 @@ namespace VNLib.Utils.Memory } else { - return new(ArrayPool<T>.Shared, elements, zero); + //Rent the array from the pool + return ArrayPool<T>.Shared.UnsafeAlloc(elements, zero); } } @@ -84,7 +90,6 @@ namespace VNLib.Utils.Memory /// <returns>A handle to the block of memory</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged { if (elements < 0) @@ -93,11 +98,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(ByteCount<T>(elements)); - - //Resize to element size - np /= sizeof(T); - + nint np = NearestPage<T>(elements); return UnsafeAlloc<T>((int)np, zero); } @@ -155,11 +156,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(ByteCount<T>(elements)); - - //Resize to element size - np /= sizeof(T); - + nint np = NearestPage<T>(elements); return SafeAlloc<T>((int)np, zero); } @@ -247,7 +244,6 @@ namespace VNLib.Utils.Memory #region ByteOptimimzations - /// <summary> /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. @@ -264,6 +260,11 @@ namespace VNLib.Utils.Memory throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } + if(elements == 0) + { + return default; + } + /* * We may allocate from the share heap only if the heap is not using locks * or if the element size could cause performance issues because its too large @@ -281,7 +282,7 @@ namespace VNLib.Utils.Memory } else { - return new(ArrayPool<byte>.Shared, elements, zero); + return ArrayPool<byte>.Shared.UnsafeAlloc(elements, zero); } } @@ -305,7 +306,6 @@ namespace VNLib.Utils.Memory //Round to nearest page (in bytes) nint np = NearestPage(elements); - return UnsafeAlloc((int)np, zero); } @@ -362,7 +362,6 @@ namespace VNLib.Utils.Memory //Round to nearest page (in bytes) nint np = NearestPage(elements); - return SafeAlloc((int)np, zero); } diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 0310582..daf360c 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -152,10 +152,14 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> ///<exception cref="OverflowException"></exception> - ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> public void Resize(ref LPVOID block, nuint elements, nuint size, bool zero) { + if ((_flags & HeapCreation.SupportsRealloc) == 0) + { + throw new NotSupportedException("The underlying heap does not support block reallocation"); + } + //Check for overflow for size _ = checked(elements * size); diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index 6a1fcc8..f79a094 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -24,6 +24,7 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; @@ -32,6 +33,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Utils.Memory { + /// <summary> /// Represents an unsafe handle to managed/unmanaged memory that should be used cautiously. /// A referrence counter is not maintained. @@ -59,55 +61,51 @@ namespace VNLib.Utils.Memory public readonly Span<T> Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, _length) : MemoryUtil.GetSpan<T>(_memoryPtr, _length); + get + { + return _handleType switch + { + HandleType.None => Span<T>.Empty, + HandleType.Pool => _poolArr!.AsSpan(0, _length), + HandleType.PrivateHeap => MemoryUtil.GetSpan<T>(_memoryPtr, _length), + _ => throw new InvalidOperationException("Invalid handle type"), + }; + } } + /// <summary> /// Gets the integer number of elements of the block of memory pointed to by this handle /// </summary> public readonly int IntLength => _length; + ///<inheritdoc/> public readonly nuint Length => (nuint)_length; /// <summary> - /// Creates an empty <see cref="UnsafeMemoryHandle{T}"/> - /// </summary> - public UnsafeMemoryHandle() - { - _pool = null; - _heap = null; - _poolArr = null; - _memoryPtr = IntPtr.Zero; - _handleType = HandleType.None; - _length = 0; - } - - /// <summary> /// Inializes a new <see cref="UnsafeMemoryHandle{T}"/> using the specified /// <see cref="ArrayPool{T}"/> /// </summary> /// <param name="elements">The number of elements to store</param> - /// <param name="zero">Zero initial contents?</param> + /// <param name="array">The array reference to store/param> /// <param name="pool">The explicit pool to alloc buffers from</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public UnsafeMemoryHandle(ArrayPool<T> pool, int elements, bool zero) + internal UnsafeMemoryHandle(ArrayPool<T> pool, T[] array, int elements) { if (elements < 0) { throw new ArgumentOutOfRangeException(nameof(elements)); } - //Pool is required + //Pool and array is required _pool = pool ?? throw new ArgumentNullException(nameof(pool)); - //Rent the array from the pool and hold referrence to it - _poolArr = pool.Rent(elements, zero); - //Cant store ref to array becase GC can move it - _memoryPtr = IntPtr.Zero; + _poolArr = array ?? throw new ArgumentNullException(nameof(array)); //Set pool handle type _handleType = HandleType.Pool; //No heap being loaded _heap = null; _length = elements; + _memoryPtr = IntPtr.Zero; } /// <summary> @@ -129,8 +127,13 @@ namespace VNLib.Utils.Memory /// <summary> /// Releases memory back to the pool or heap from which is was allocated. + /// <para> + /// After this method is called, this handle points to invalid memory + /// </para> + /// <para> + /// Warning: Double Free -> Do not call more than once. Using statment is encouraged + /// </para> /// </summary> - /// <remarks>After this method is called, this handle points to invalid memory</remarks> public readonly void Dispose() { switch (_handleType) @@ -145,42 +148,37 @@ namespace VNLib.Utils.Memory { IntPtr unalloc = _memoryPtr; //Free the unmanaged handle - _heap!.Free(ref unalloc); + bool unsafeFreed = _heap!.Free(ref unalloc); + Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free"); } break; } - } - - ///<inheritdoc/> - public readonly override int GetHashCode() => _handleType == HandleType.Pool ? _poolArr!.GetHashCode() : _memoryPtr.GetHashCode(); + } + ///<inheritdoc/> public readonly MemoryHandle Pin(int elementIndex) { - //guard empty handle - if (_handleType == HandleType.None) - { - throw new InvalidOperationException("The handle is empty, and cannot be pinned"); - } - //Guard size if (elementIndex < 0 || elementIndex >= _length) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); - } - - if (_handleType == HandleType.Pool) - { - return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); } - else + + switch (_handleType) { - //Add an offset to the base address of the memory block - int byteOffset = MemoryUtil.ByteCount<T>(elementIndex); - IntPtr offset = IntPtr.Add(_memoryPtr, byteOffset); - //Unmanaged memory is always pinned, so no need to pass this as IPinnable, since it will cause a box - return MemoryUtil.GetMemoryHandleFromPointer(offset); + case HandleType.Pool: + return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); + case HandleType.PrivateHeap: + //Add an offset to the base address of the memory block + int byteOffset = MemoryUtil.ByteCount<T>(elementIndex); + IntPtr offset = IntPtr.Add(_memoryPtr, byteOffset); + //Unmanaged memory is always pinned, so no need to pass this as IPinnable, since it will cause a box + return MemoryUtil.GetMemoryHandleFromPointer(offset); + default: + throw new InvalidOperationException("The handle is empty, and cannot be pinned"); } } + ///<inheritdoc/> public readonly void Unpin() { @@ -188,6 +186,7 @@ namespace VNLib.Utils.Memory } ///<inheritdoc/> + ///<exception cref="InvalidOperationException"></exception> public readonly ref T GetReference() { switch (_handleType) @@ -201,18 +200,38 @@ namespace VNLib.Utils.Memory } } + ///<inheritdoc/> + public readonly override int GetHashCode() + { + //Get hashcode for the proper memory type + return _handleType switch + { + HandleType.Pool => _poolArr!.GetHashCode(), + HandleType.PrivateHeap => _memoryPtr.GetHashCode(), + _ => base.GetHashCode(), + }; + } + /// <summary> /// Determines if the other handle represents the same memory block as the /// current handle. /// </summary> /// <param name="other">The other handle to test</param> /// <returns>True if the other handle points to the same block of memory as the current handle</returns> - public readonly bool Equals(UnsafeMemoryHandle<T> other) + public readonly bool Equals(in UnsafeMemoryHandle<T> other) { return _handleType == other._handleType && Length == other.Length && GetHashCode() == other.GetHashCode(); } /// <summary> + /// Determines if the other handle represents the same memory block as the + /// current handle. + /// </summary> + /// <param name="other">The other handle to test</param> + /// <returns>True if the other handle points to the same block of memory as the current handle</returns> + public readonly bool Equals(UnsafeMemoryHandle<T> other) => Equals(in other); + + /// <summary> /// Override for object equality operator, will cause boxing /// for structures /// </summary> @@ -222,13 +241,7 @@ namespace VNLib.Utils.Memory /// and uses the structure equality operator /// false otherwise. /// </returns> - public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is UnsafeMemoryHandle<T> other && Equals(other); - - /// <summary> - /// Casts the handle to it's <see cref="Span{T}"/> representation - /// </summary> - /// <param name="handle">the handle to cast</param> - public static implicit operator Span<T>(in UnsafeMemoryHandle<T> handle) => handle.Span; + public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is UnsafeMemoryHandle<T> other && Equals(in other); /// <summary> /// Equality overload @@ -237,6 +250,7 @@ namespace VNLib.Utils.Memory /// <param name="right"></param> /// <returns>True if handles are equal, flase otherwise</returns> public static bool operator ==(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => left.Equals(right); + /// <summary> /// Equality overload /// </summary> diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index c937ccc..d5b34ac 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -42,7 +42,13 @@ namespace VNLib.Utils.Memory /// </summary> [ComVisible(false)] [ImmutableObject(true)] - public sealed class VnString : VnDisposeable, IEquatable<VnString>, IEquatable<string>, IEquatable<char[]>, IComparable<VnString>, IComparable<string> + public sealed class VnString : + VnDisposeable, + IEquatable<VnString>, + IEquatable<string>, + IEquatable<char[]>, + IComparable<VnString>, + IComparable<string> { private readonly IMemoryHandle<char>? Handle; @@ -58,10 +64,7 @@ namespace VNLib.Utils.Memory /// </summary> public bool IsEmpty => Length == 0; - private VnString(SubSequence<char> sequence) - { - _stringSequence = sequence; - } + private VnString(SubSequence<char> sequence) => _stringSequence = sequence; private VnString(IMemoryHandle<char> handle, nuint start, int length) { @@ -97,7 +100,77 @@ namespace VNLib.Utils.Memory //Get subsequence over the whole copy of data _stringSequence = Handle.GetSubSequence(0, data.Length); } - + + /// <summary> + /// Creates a new <see cref="VnString"/> from the binary data, using the specified encoding + /// and allocating the internal buffer from the desired heap, or <see cref="MemoryUtil.Shared"/> + /// heap instance if null. If the <paramref name="data"/> is empty, an empty <see cref="VnString"/> + /// is returned. + /// </summary> + /// <param name="data">The data to decode</param> + /// <param name="encoding"></param> + /// <param name="heap"></param> + /// <returns>The decoded string from the binary data, or an empty string if no data was provided</returns> + /// <exception cref="ArgumentNullException"></exception> + public static VnString FromBinary(ReadOnlySpan<byte> data, Encoding encoding, IUnmangedHeap? heap = null) + { + _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); + + if (data.IsEmpty) + { + return new VnString(); + } + + //Fall back to shared heap + heap ??= MemoryUtil.Shared; + + //Get the number of characters + int numChars = encoding.GetCharCount(data); + + //New handle for decoded data + MemoryHandle<char> charBuffer = heap.Alloc<char>(numChars); + try + { + //Write characters to character buffer + _ = encoding.GetChars(data, charBuffer.Span); + //Consume the new handle + return ConsumeHandle(charBuffer, 0, numChars); + } + catch + { + //If an error occured, dispose the buffer + charBuffer.Dispose(); + throw; + } + } + + /// <summary> + /// Creates a new Vnstring from the <see cref="MemoryHandle{T}"/> buffer provided. This function "consumes" + /// a handle, meaning it now takes ownsership of the the memory it points to. + /// </summary> + /// <param name="handle">The <see cref="MemoryHandle{T}"/> to consume</param> + /// <param name="start">The offset from the begining of the buffer marking the begining of the string</param> + /// <param name="length">The number of characters this string points to</param> + /// <returns>The new <see cref="VnString"/></returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public static VnString ConsumeHandle(IMemoryHandle<char> handle, nuint start, int length) + { + if (handle is null) + { + throw new ArgumentNullException(nameof(handle)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + //Check handle bounts + MemoryUtil.CheckBounds(handle, start, (nuint)length); + + return new VnString(handle, start, length); + } + /// <summary> /// Allocates a temporary buffer to read data from the stream until the end of the stream is reached. /// Decodes data from the user-specified encoding @@ -111,7 +184,7 @@ namespace VNLib.Utils.Memory /// <exception cref="OverflowException"></exception> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="InvalidOperationException"></exception> - public static VnString FromStream(Stream stream, Encoding encoding, IUnmangedHeap heap, uint bufferSize) + public static VnString FromStream(Stream stream, Encoding encoding, IUnmangedHeap heap, int bufferSize) { _ = stream ?? throw new ArgumentNullException(nameof(stream)); _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); @@ -125,36 +198,27 @@ namespace VNLib.Utils.Memory //See if the stream is a vn memory stream if (stream is VnMemoryStream vnms) { - //Get the number of characters - int numChars = encoding.GetCharCount(vnms.AsSpan()); - //New handle - MemoryHandle<char> charBuffer = heap.Alloc<char>(numChars); - try - { - //Write characters to character buffer - _ = encoding.GetChars(vnms.AsSpan(), charBuffer); - //Consume the new handle - return ConsumeHandle(charBuffer, 0, numChars); - } - catch - { - //If an error occured, dispose the buffer - charBuffer.Dispose(); - throw; - } + return FromBinary(vnms.AsSpan(), encoding, heap); + } + //Try to get the internal buffer from am memory span + else if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment<byte> arrSeg)) + { + return FromBinary(arrSeg.AsSpan(), encoding, heap); } //Need to read from the stream old school with buffers else { + //Allocate a binary buffer + using UnsafeMemoryHandle<byte> binBuffer = heap.UnsafeAlloc<byte>(bufferSize); + //Create a new char bufer that will expand dyanmically MemoryHandle<char> charBuffer = heap.Alloc<char>(bufferSize); - //Allocate a binary buffer - MemoryHandle<byte> binBuffer = heap.Alloc<byte>(bufferSize); + try { int length = 0; //span ref to bin buffer - Span<byte> buffer = binBuffer; + Span<byte> buffer = binBuffer.Span; //Run in checked context for overflows checked { @@ -193,40 +257,8 @@ namespace VNLib.Utils.Memory //We still want the exception to be propagated! throw; } - finally - { - //Dispose the binary buffer - binBuffer.Dispose(); - } - } - } - - /// <summary> - /// Creates a new Vnstring from the <see cref="MemoryHandle{T}"/> buffer provided. This function "consumes" - /// a handle, meaning it now takes ownsership of the the memory it points to. - /// </summary> - /// <param name="handle">The <see cref="MemoryHandle{T}"/> to consume</param> - /// <param name="start">The offset from the begining of the buffer marking the begining of the string</param> - /// <param name="length">The number of characters this string points to</param> - /// <returns>The new <see cref="VnString"/></returns> - /// <exception cref="ArgumentOutOfRangeException"></exception> - public static VnString ConsumeHandle(MemoryHandle<char> handle, nuint start, int length) - { - if (handle is null) - { - throw new ArgumentNullException(nameof(handle)); - } - - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); } - - //Check handle bounts - MemoryUtil.CheckBounds(handle, start, (nuint)length); - - return new VnString(handle, start, length); - } + } /// <summary> /// Asynchronously reads data from the specified stream and uses the specified encoding @@ -251,36 +283,25 @@ namespace VNLib.Utils.Memory throw new IOException("The input stream is not readable"); } - //See if the stream is a vn memory stream + /* + * If the stream is some type of memory stream, we can just use + * the underlying buffer if possible + */ if (stream is VnMemoryStream vnms) { - //Get the number of characters - int numChars = encoding.GetCharCount(vnms.AsSpan()); - - //New handle - MemoryHandle<char> charBuffer = heap.Alloc<char>(numChars); - - try - { - //Write characters to character buffer - _ = encoding.GetChars(vnms.AsSpan(), charBuffer); - //Consume the new handle - return ConsumeHandle(charBuffer, 0, numChars); - } - catch - { - //If an error occured, dispose the buffer - charBuffer.Dispose(); - throw; - } + return FromBinary(vnms.AsSpan(), encoding, heap); + } + else if(stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment<byte> arrSeg)) + { + return FromBinary(arrSeg.AsSpan(), encoding, heap); } else { - //Create a new char bufer starting with the buffer size - MemoryHandle<char> charBuffer = heap.Alloc<char>(bufferSize); - //Rent a temp binary buffer - IMemoryOwner<byte> binBuffer = heap.DirectAlloc<byte>(bufferSize); + using MemoryManager<byte> binBuffer = heap.DirectAlloc<byte>(bufferSize); + + //Create a new char buffer starting with the buffer size + MemoryHandle<char> charBuffer = heap.Alloc<char>(bufferSize); try { @@ -302,14 +323,14 @@ namespace VNLib.Utils.Memory //Guard for overflow if (((ulong)(numChars + length)) >= int.MaxValue) { - throw new OverflowException(); + throw new OverflowException("The provided stream is larger than 2gb and is not supported"); } //Re-alloc buffer charBuffer.ResizeIfSmaller(length + numChars); //Decode and update position - _ = encoding.GetChars(binBuffer.Memory.Span[..read], charBuffer.Span.Slice(length, numChars)); + _ = encoding.GetChars(binBuffer.GetSpan()[..read], charBuffer.Span.Slice(length, numChars)); //Update char count length += numChars; @@ -323,11 +344,6 @@ namespace VNLib.Utils.Memory //We still want the exception to be propagated! throw; } - finally - { - //Dispose the binary buffer - binBuffer.Dispose(); - } } } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index e24f7df..9eb60df 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -69,7 +69,7 @@ namespace VNLib.Utils //resize the handle to fit the data handle = heap.Alloc<byte>(byteCount); //encode - int size = encoding.GetBytes(data, handle); + int size = encoding.GetBytes(data, handle.Span); //Consume the handle into a new vnmemstream and return it return VnMemoryStream.ConsumeHandle(handle, size, true); } @@ -459,27 +459,28 @@ namespace VNLib.Utils string value; //Calculate the base32 entropy to alloc an appropriate buffer (minium buffer of 2 chars) int entropy = Base32CalcMaxBufferSize(binBuffer.Length); + //Alloc buffer for enough size (2*long bytes) is not an issue - using (UnsafeMemoryHandle<char> charBuffer = MemoryUtil.UnsafeAlloc<char>(entropy)) + using UnsafeMemoryHandle<char> charBuffer = MemoryUtil.UnsafeAlloc<char>(entropy); + + //Encode + ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span); + if (!encoded) { - //Encode - ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span); - if (!encoded) - { - throw new InternalBufferTooSmallException("Base32 char buffer was too small"); - } - //Convert with or w/o padding - if (withPadding) - { - value = charBuffer.Span[0..(int)encoded].ToString(); - } - else - { - value = charBuffer.Span[0..(int)encoded].Trim('=').ToString(); - } + throw new InternalBufferTooSmallException("Base32 char buffer was too small"); + } + //Convert with or w/o padding + if (withPadding) + { + value = charBuffer.Span[0..(int)encoded].ToString(); } + else + { + value = charBuffer.Span[0..(int)encoded].Trim('=').ToString(); + } + return value; - } + } /// <summary> /// Converts the base32 character buffer to its structure representation @@ -506,7 +507,6 @@ namespace VNLib.Utils /// </summary> /// <param name="base32">The character array to decode</param> /// <returns>The byte[] of the decoded binary data, or null if the supplied character array was empty</returns> - /// <exception cref="InternalBufferTooSmallException"></exception> public static byte[]? FromBase32String(ReadOnlySpan<char> base32) { if (base32.IsEmpty) @@ -515,10 +515,12 @@ namespace VNLib.Utils } //Buffer size of the base32 string will always be enough buffer space using UnsafeMemoryHandle<byte> tempBuffer = MemoryUtil.UnsafeAlloc(base32.Length); + //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span); - - return decoded ? tempBuffer.Span[0..(int)decoded].ToArray() : throw new InternalBufferTooSmallException("Binbuffer was too small"); + Debug.Assert(decoded > 0, "The supplied base32 buffer was too small to decode data into, but should not have been"); + + return tempBuffer.Span[0..(int)decoded].ToArray(); } /// <summary> @@ -942,16 +944,28 @@ namespace VNLib.Utils { return ERRNO.E_FAIL; } + //Set the encoding to utf8 encoding ??= Encoding.UTF8; //get the number of bytes to alloc a buffer int decodedSize = encoding.GetByteCount(chars); - //alloc buffer - using UnsafeMemoryHandle<byte> decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize); - //Get the utf8 binary data - int count = encoding.GetBytes(chars, decodeHandle); - return Base64UrlDecode(decodeHandle.Span[..count], output); + if(decodedSize > MAX_STACKALLOC) + { + //Alloc heap buffer + using UnsafeMemoryHandle<byte> decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize); + //Get the utf8 binary data + int count = encoding.GetBytes(chars, decodeHandle.Span); + return Base64UrlDecode(decodeHandle.Span[..count], output); + } + else + { + //Alloc stack buffer + Span<byte> decodeBuffer = stackalloc byte[decodedSize]; + //Get the utf8 binary data + int count = encoding.GetBytes(chars, decodeBuffer); + return Base64UrlDecode(decodeBuffer[..count], output); + } } @@ -1036,16 +1050,14 @@ namespace VNLib.Utils if(maxBufSize > MAX_STACKALLOC) { - //alloc buffer + //Alloc heap buffer using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(maxBufSize); - return ConvertToBase64UrlStringInternal(rawData, buffer.Span, includePadding); } else { //Stack alloc buffer Span<byte> buffer = stackalloc byte[maxBufSize]; - return ConvertToBase64UrlStringInternal(rawData, buffer, includePadding); } } @@ -1116,24 +1128,37 @@ namespace VNLib.Utils //We need to alloc an intermediate buffer, get the base64 max size int maxSize = Base64.GetMaxEncodedToUtf8Length(input.Length); - //Alloc buffer - using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(maxSize); - - //Encode to url safe binary - ERRNO count = Base64UrlEncode(input, buffer.Span, includePadding); - - if (count <= 0) + if (maxSize > MAX_STACKALLOC) { - return count; + //Alloc heap buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(maxSize); + return Base64UrlEncodeCore(input, buffer.Span, output, encoding, includePadding); } + else + { + //Alloc stack buffer + Span<byte> bufer = stackalloc byte[maxSize]; + return Base64UrlEncodeCore(input, bufer, output, encoding, includePadding); + } + + static ERRNO Base64UrlEncodeCore(ReadOnlySpan<byte> input, Span<byte> buffer, Span<char> output, Encoding encoding, bool includePadding) + { + //Encode to url safe binary + ERRNO count = Base64UrlEncode(input, buffer, includePadding); - //Get char count to return to caller - int charCount = encoding.GetCharCount(buffer.Span[..(int)count]); + if (count <= 0) + { + return count; + } - //Encode to characters - encoding.GetChars(buffer.AsSpan(0, count), output); + //Get char count to return to caller + int charCount = encoding.GetCharCount(buffer[..(int)count]); - return charCount; + //Encode to characters + encoding.GetChars(buffer[0..(int)count], output); + + return charCount; + } } #endregion diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index e63dc03..0774f47 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -165,6 +165,10 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(1024)); } + //Use the byte only overload + using (UnsafeMemoryHandle<byte> handle = MemoryUtil.UnsafeAlloc(1024)) + { } + //test against negative number Assert.ThrowsException<ArgumentException>(() => MemoryUtil.UnsafeAlloc<byte>(-1)); @@ -217,7 +221,7 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(0 == empty.IntLength); //Test pinning while empty - Assert.ThrowsException<InvalidOperationException>(() => _ = empty.Pin(0)); + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = empty.Pin(0)); } //Negative value |