diff options
Diffstat (limited to 'lib')
45 files changed, 1520 insertions, 929 deletions
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index 01cfe74..7b467ba 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -51,7 +51,7 @@ namespace VNLib.Hashing public const string ARGON2_DEFUALT_LIB_NAME = "Argon2"; private static readonly Encoding LocEncoding = Encoding.Unicode; - private static readonly Lazy<IUnmangedHeap> _heap = new (Memory.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy<IUnmangedHeap> _heap = new (MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); private static readonly Lazy<Argon2NativeLibary> _nativeLibrary = new(LoadNativeLib, LazyThreadSafetyMode.PublicationOnly); @@ -137,16 +137,22 @@ namespace VNLib.Hashing { //Get bytes count int saltbytes = LocEncoding.GetByteCount(salt); + //Get bytes count for password int passBytes = LocEncoding.GetByteCount(password); + //Alloc memory for salt using MemoryHandle<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes, true); + Span<byte> saltBuffer = buffer.AsSpan(0, saltbytes); Span<byte> passBuffer = buffer.AsSpan(passBytes); + //Encode salt with span the same size of the salt _ = LocEncoding.GetBytes(salt, saltBuffer); + //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, passBuffer); + //Hash return Hash2id(passBuffer, saltBuffer, secret, timeCost, memCost, parallelism, hashLen); } @@ -170,10 +176,13 @@ namespace VNLib.Hashing { //Get bytes count int passBytes = LocEncoding.GetByteCount(password); + //Alloc memory for password using MemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes, true); + //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, pwdHandle); + //Hash return Hash2id(pwdHandle.Span, salt, secret, timeCost, memCost, parallelism, hashLen); } @@ -197,12 +206,16 @@ namespace VNLib.Hashing string hash, salts; //Alloc data for hash output using MemoryHandle<byte> hashHandle = PwHeap.Alloc<byte>(hashLen, true); + //hash the password Hash2id(password, salt, secret, hashHandle.Span, timeCost, memCost, parallelism); + //Encode hash hash = Convert.ToBase64String(hashHandle.Span); + //encode salt 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}"; } @@ -277,6 +290,7 @@ namespace VNLib.Hashing { //Alloc data for hash output using MemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length, true); + //Get pointers fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt) { @@ -333,6 +347,7 @@ namespace VNLib.Hashing { throw new VnArgon2PasswordFormatException("The hash argument supplied is not a valid format and cannot be decoded"); } + Argon2PasswordEntry entry; try { @@ -343,12 +358,15 @@ namespace VNLib.Hashing { throw new VnArgon2PasswordFormatException("Password format was not recoverable", ex); } + //Calculate base64 buffer sizes int passBase64BufSize = Base64.GetMaxDecodedFromUtf8Length(entry.Hash.Length); int saltBase64BufSize = Base64.GetMaxDecodedFromUtf8Length(entry.Salt.Length); int rawPassLen = LocEncoding.GetByteCount(rawPass); + //Alloc buffer for decoded data - using MemoryHandle<byte> rawBufferHandle = Memory.Shared.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen, true); + using MemoryHandle<byte> rawBufferHandle = MemoryUtil.Shared.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen, true); + //Split buffers Span<byte> saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; Span<byte> passBuf = rawBufferHandle.AsSpan(saltBase64BufSize, passBase64BufSize); @@ -362,6 +380,7 @@ namespace VNLib.Hashing //Resize pass buff passBuf = passBuf[..actualHashLen]; } + //Decode salt { if (!Convert.TryFromBase64Chars(entry.Salt, saltBuf, out int actualSaltLen)) @@ -371,6 +390,7 @@ namespace VNLib.Hashing //Resize salt buff saltBuf = saltBuf[..actualSaltLen]; } + //encode password bytes rawPassLen = LocEncoding.GetBytes(rawPass, rawPassBuf); //Verify password diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs index f36b151..5ff37e8 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs @@ -45,27 +45,39 @@ namespace VNLib.Hashing.IdentityUtility /// <param name="data">The data to compute the hash of</param> /// <param name="encoding">The <see cref="Encoding"/> used to encode the character buffer</param> /// <returns>The base64 UTF8 string of the computed hash of the specified data</returns> - public static string ComputeBase64Hash(this HMAC hmac, ReadOnlySpan<char> data, Encoding encoding = null) + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + public static string ComputeBase64Hash(this HMAC hmac, ReadOnlySpan<char> data, Encoding? encoding = null) { + _ = hmac ?? throw new ArgumentNullException(nameof(hmac)); + encoding ??= Encoding.UTF8; + //Calc hashsize to alloc buffer int hashBufSize = (hmac.HashSize / 8); + //Calc buffer size int encBufSize = encoding.GetByteCount(data); + //Alloc buffer for encoding data - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(encBufSize + hashBufSize); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(encBufSize + hashBufSize); + Span<byte> encBuffer = buffer.Span[0..encBufSize]; Span<byte> hashBuffer = buffer.Span[encBufSize..]; + //Encode data _ = encoding.GetBytes(data, encBuffer); + //compute hash if (!hmac.TryComputeHash(encBuffer, hashBuffer, out int hashBytesWritten)) { - throw new OutOfMemoryException("Hash buffer size was too small"); + throw new InternalBufferTooSmallException("Hash buffer size was too small"); } + //Convert to base64 string return Convert.ToBase64String(hashBuffer[..hashBytesWritten]); } + /// <summary> /// Computes the hash of the raw data and compares the computed hash against /// the specified base64hash @@ -77,36 +89,48 @@ namespace VNLib.Hashing.IdentityUtility /// <returns>A value indicating if the hash values match</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> - public static bool VerifyBase64Hash(this HMAC hmac, ReadOnlySpan<char> base64Hmac, ReadOnlySpan<char> raw, Encoding encoding = null) + /// <exception cref="ArgumentNullException"></exception> + public static bool VerifyBase64Hash(this HMAC hmac, ReadOnlySpan<char> base64Hmac, ReadOnlySpan<char> raw, Encoding? encoding = null) { _ = hmac ?? throw new ArgumentNullException(nameof(hmac)); + if (raw.IsEmpty) { throw new ArgumentException("Raw data buffer must not be empty", nameof(raw)); } + if (base64Hmac.IsEmpty) { throw new ArgumentException("Hmac buffer must not be empty", nameof(base64Hmac)); } + encoding ??= Encoding.UTF8; + //Calc buffer size int rawDataBufSize = encoding.GetByteCount(raw); + //Calc base64 buffer size int base64BufSize = base64Hmac.Length; + //Alloc buffer for encoding and raw data - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(rawDataBufSize + base64BufSize, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(rawDataBufSize + base64BufSize, true); + Span<byte> rawDataBuf = buffer.Span[0..rawDataBufSize]; Span<byte> base64Buf = buffer.Span[rawDataBufSize..]; + //encode _ = encoding.GetBytes(raw, rawDataBuf); + //Convert to binary if(!Convert.TryFromBase64Chars(base64Hmac, base64Buf, out int base64Converted)) { - throw new OutOfMemoryException("Base64 buffer too small"); + throw new InternalBufferTooSmallException("Base64 buffer too small"); } + //Compare hash buffers return hmac.VerifyHash(base64Buf[0..base64Converted], rawDataBuf); } + /// <summary> /// Computes the hash of the raw data and compares the computed hash against /// the specified hash @@ -117,25 +141,33 @@ namespace VNLib.Hashing.IdentityUtility /// <returns>A value indicating if the hash values match</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> public static bool VerifyHash(this HMAC hmac, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> raw) { + _ = hmac ?? throw new ArgumentNullException(nameof(hmac)); + if (raw.IsEmpty) { throw new ArgumentException("Raw data buffer must not be empty", nameof(raw)); } + if (hash.IsEmpty) { throw new ArgumentException("Hash buffer must not be empty", nameof(hash)); } + //Calc hashsize to alloc buffer int hashBufSize = hmac.HashSize / 8; + //Alloc buffer for hash - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(hashBufSize); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(hashBufSize); + //compute hash if (!hmac.TryComputeHash(raw, buffer, out int hashBytesWritten)) { - throw new OutOfMemoryException("Hash buffer size was too small"); + throw new InternalBufferTooSmallException("Hash buffer size was too small"); } + //Compare hash buffers return CryptographicOperations.FixedTimeEquals(buffer.Span[0..hashBytesWritten], hash); } @@ -153,17 +185,22 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="ArgumentNullException"></exception> /// <exception cref="CryptographicException"></exception> /// <exception cref="ObjectDisposedException"></exception> - public static ERRNO TryEncrypt(this RSA alg, ReadOnlySpan<char> data, in Span<byte> output, RSAEncryptionPadding padding, Encoding enc = null) + public static ERRNO TryEncrypt(this RSA alg, ReadOnlySpan<char> data, in Span<byte> output, RSAEncryptionPadding padding, Encoding? enc = null) { _ = alg ?? throw new ArgumentNullException(nameof(alg)); + //Default to UTF8 encoding enc ??= Encoding.UTF8; + //Alloc decode buffer int buffSize = enc.GetByteCount(data); + //Alloc buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(buffSize, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(buffSize, true); + //Encode data int converted = enc.GetBytes(data, buffer); + //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/JsonWebKey.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs index 54098c2..9076e5b 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs @@ -449,10 +449,13 @@ namespace VNLib.Hashing.IdentityUtility { return null; } + //bin buffer for temp decoding - using UnsafeMemoryHandle<byte> binBuffer = Memory.UnsafeAlloc<byte>(base64.Length + 16, false); + using UnsafeMemoryHandle<byte> binBuffer = MemoryUtil.UnsafeAlloc<byte>(base64.Length + 16, false); + //base64url decode ERRNO count = VnEncoding.Base64UrlDecode(base64, binBuffer.Span); + //Return buffer or null if failed return count ? binBuffer.AsSpan(0, count).ToArray() : null; } diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index 716dd4c..e3822d0 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -39,7 +39,7 @@ namespace VNLib.Hashing.IdentityUtility /// Provides a dynamic JSON Web Token class that will store and /// compute Base64Url encoded WebTokens /// </summary> - public class JsonWebToken : VnDisposeable, IStringSerializeable, IDisposable + public class JsonWebToken : VnDisposeable, IStringSerializeable { internal const byte SAEF_PERIOD = 0x2e; internal const byte PADDING_BYTES = 0x3d; @@ -53,15 +53,19 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="FormatException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> - public static JsonWebToken Parse(ReadOnlySpan<char> urlEncJwtString, IUnmangedHeap heap = null) + public static JsonWebToken Parse(ReadOnlySpan<char> urlEncJwtString, IUnmangedHeap? heap = null) { - heap ??= Memory.Shared; + heap ??= MemoryUtil.Shared; + //Calculate the decoded size of the characters to alloc a buffer int utf8Size = Encoding.UTF8.GetByteCount(urlEncJwtString); + //Alloc bin buffer to store decode data using MemoryHandle<byte> binBuffer = heap.Alloc<byte>(utf8Size, true); + //Decode to utf8 utf8Size = Encoding.UTF8.GetBytes(urlEncJwtString, binBuffer); + //Parse and return the jwt return ParseRaw(binBuffer.Span[..utf8Size], heap); } @@ -75,14 +79,16 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="FormatException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> - public static JsonWebToken ParseRaw(ReadOnlySpan<byte> utf8JWTData, IUnmangedHeap heap = null) + public static JsonWebToken ParseRaw(ReadOnlySpan<byte> utf8JWTData, IUnmangedHeap? heap = null) { if (utf8JWTData.IsEmpty) { throw new ArgumentException("JWT data may not be empty", nameof(utf8JWTData)); } + //Set default heap of non was specified - heap ??= Memory.Shared; + heap ??= MemoryUtil.Shared; + //Alloc the token and copy the supplied data to a new mem stream JsonWebToken jwt = new(heap, new (heap, utf8JWTData)); try @@ -144,17 +150,19 @@ namespace VNLib.Hashing.IdentityUtility { Heap = heap; DataStream = initialData; + + //Update position to the end of the initial data initialData.Position = initialData.Length; } /// <summary> /// Creates a new empty JWT instance, with an optional heap to alloc - /// buffers from. (<see cref="Memory.Shared"/> is used as default) + /// buffers from. (<see cref="MemoryUtil.Shared"/> is used as default) /// </summary> /// <param name="heap">The <see cref="IUnmangedHeap"/> to alloc buffers from</param> - public JsonWebToken(IUnmangedHeap heap = null) + public JsonWebToken(IUnmangedHeap? heap = null) { - Heap = heap ?? Memory.Shared; + Heap = heap ?? MemoryUtil.Shared; DataStream = new(Heap, 100, true); } @@ -186,8 +194,10 @@ namespace VNLib.Hashing.IdentityUtility #endregion #region Payload + private int PayloadStart => HeaderEnd + 1; private int PayloadEnd; + /// <summary> /// The Base64URL encoded UTF8 bytes of the payload portion of the current JWT /// </summary> @@ -218,6 +228,7 @@ namespace VNLib.Hashing.IdentityUtility //Store final position PayloadEnd = ByteSize; } + /// <summary> /// Encodes the specified value and writes it to the /// internal buffer @@ -233,7 +244,7 @@ namespace VNLib.Hashing.IdentityUtility //Slice off the begiing of the buffer for the base64 encoding if(Base64.EncodeToUtf8(value, binBuffer.Span, out _, out int written) != OperationStatus.Done) { - throw new OutOfMemoryException(); + throw new InternalBufferTooSmallException("Failed to encode the specified value to base64"); } //Base64 encoded Span<byte> base64Data = binBuffer.Span[..written].Trim(PADDING_BYTES); @@ -245,8 +256,10 @@ namespace VNLib.Hashing.IdentityUtility #endregion #region Signature + private int SignatureStart => PayloadEnd + 1; private int SignatureEnd => ByteSize; + /// <summary> /// The Base64URL encoded UTF8 bytes of the signature portion of the current JWT /// </summary> @@ -266,23 +279,31 @@ namespace VNLib.Hashing.IdentityUtility public virtual void Sign(HashAlgorithm signatureAlgorithm) { Check(); + _ = signatureAlgorithm ?? throw new ArgumentNullException(nameof(signatureAlgorithm)); + //Calculate the size of the buffer to use for the current algorithm int bufferSize = signatureAlgorithm.HashSize / 8; + //Alloc buffer for signature output Span<byte> signatureBuffer = stackalloc byte[bufferSize]; + //Compute the hash of the current payload if(!signatureAlgorithm.TryComputeHash(DataBuffer, signatureBuffer, out int bytesWritten)) { - throw new OutOfMemoryException(); + throw new InternalBufferTooSmallException(); } + //Reset the stream position to the end of the payload DataStream.SetLength(PayloadEnd); + //Write leading period DataStream.WriteByte(SAEF_PERIOD); + //Write the signature data to the buffer WriteValue(signatureBuffer[..bytesWritten]); } + /// <summary> /// Use an RSA algorithm to sign the JWT message /// </summary> @@ -296,20 +317,27 @@ namespace VNLib.Hashing.IdentityUtility public virtual void Sign(RSA rsa, in HashAlgorithmName hashAlg, RSASignaturePadding padding, int hashSize) { Check(); + _ = rsa ?? throw new ArgumentNullException(nameof(rsa)); + //Calculate the size of the buffer to use for the current algorithm using UnsafeMemoryHandle<byte> sigBuffer = Heap.UnsafeAlloc<byte>(hashSize); + if(!rsa.TrySignData(HeaderAndPayload, sigBuffer.Span, hashAlg, padding, out int hashBytesWritten)) { - throw new OutOfMemoryException("Signature buffer is not large enough to store the hash"); + throw new InternalBufferTooSmallException("Signature buffer is not large enough to store the hash"); } + //Reset the stream position to the end of the payload DataStream.SetLength(PayloadEnd); + //Write leading period DataStream.WriteByte(SAEF_PERIOD); + //Write the signature data to the buffer WriteValue(sigBuffer.Span[..hashBytesWritten]); } + /// <summary> /// Use an RSA algorithm to sign the JWT message /// </summary> @@ -322,17 +350,23 @@ namespace VNLib.Hashing.IdentityUtility public virtual void Sign(ECDsa alg, in HashAlgorithmName hashAlg, int hashSize) { Check(); + _ = alg ?? throw new ArgumentNullException(nameof(alg)); + //Calculate the size of the buffer to use for the current algorithm using UnsafeMemoryHandle<byte> sigBuffer = Heap.UnsafeAlloc<byte>(hashSize); + if (!alg.TrySignData(HeaderAndPayload, sigBuffer.Span, hashAlg, out int hashBytesWritten)) { - throw new OutOfMemoryException("Signature buffer is not large enough to store the hash"); + throw new InternalBufferTooSmallException("Signature buffer is not large enough to store the hash"); } + //Reset the stream position to the end of the payload DataStream.SetLength(PayloadEnd); + //Write leading period DataStream.WriteByte(SAEF_PERIOD); + //Write the signature data to the buffer WriteValue(sigBuffer.Span[..hashBytesWritten]); } @@ -379,7 +413,6 @@ namespace VNLib.Hashing.IdentityUtility //Clear pointers, so buffer get operations just return empty instead of throwing Reset(); DataStream.Dispose(); - } - + } } } diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs index 46a8cb8..b4e5f09 100644 --- a/lib/Hashing.Portable/src/ManagedHash.cs +++ b/lib/Hashing.Portable/src/ManagedHash.cs @@ -79,10 +79,13 @@ namespace VNLib.Hashing public static ERRNO ComputeHash(ReadOnlySpan<char> data, Span<byte> buffer, HashAlg type) { int byteCount = CharEncoding.GetByteCount(data); + //Alloc buffer - using UnsafeMemoryHandle<byte> binbuf = Memory.UnsafeAlloc<byte>(byteCount, true); + using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc<byte>(byteCount, true); + //Encode data byteCount = CharEncoding.GetBytes(data, binbuf); + //hash the buffer return ComputeHash(binbuf.Span[..byteCount], buffer, type); } @@ -99,7 +102,7 @@ namespace VNLib.Hashing { int byteCount = CharEncoding.GetByteCount(data); //Alloc buffer - using UnsafeMemoryHandle<byte> binbuf = Memory.UnsafeAlloc<byte>(byteCount, true); + using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc<byte>(byteCount, true); //Encode data byteCount = CharEncoding.GetBytes(data, binbuf); //hash the buffer @@ -229,10 +232,13 @@ namespace VNLib.Hashing public static ERRNO ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<char> data, Span<byte> output, HashAlg type) { int byteCount = CharEncoding.GetByteCount(data); + //Alloc buffer - using UnsafeMemoryHandle<byte> binbuf = Memory.UnsafeAlloc<byte>(byteCount, true); + using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc<byte>(byteCount, true); + //Encode data byteCount = CharEncoding.GetBytes(data, binbuf); + //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], output, type); } @@ -249,10 +255,13 @@ namespace VNLib.Hashing public static byte[] ComputeHmac(ReadOnlySpan<byte> key, ReadOnlySpan<char> data, HashAlg type) { int byteCount = CharEncoding.GetByteCount(data); + //Alloc buffer - using UnsafeMemoryHandle<byte> binbuf = Memory.UnsafeAlloc<byte>(byteCount, true); + using UnsafeMemoryHandle<byte> binbuf = MemoryUtil.UnsafeAlloc<byte>(byteCount, true); + //Encode data byteCount = CharEncoding.GetBytes(data, binbuf); + //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], type); } @@ -317,12 +326,15 @@ namespace VNLib.Hashing { //Alloc hash buffer Span<byte> hashBuffer = stackalloc byte[(int)type]; + //hash the buffer ERRNO count = ComputeHmac(key, data, hashBuffer, type); + if (!count) { throw new InternalBufferTooSmallException("Failed to compute the hash of the data"); } + //Convert to hex string return mode switch { @@ -347,12 +359,15 @@ namespace VNLib.Hashing { //Alloc hash buffer Span<byte> hashBuffer = stackalloc byte[(int)type]; + //hash the buffer ERRNO count = ComputeHmac(key, data, hashBuffer, type); + if (!count) { throw new InternalBufferTooSmallException("Failed to compute the hash of the data"); } + //Convert to hex string return mode switch { diff --git a/lib/Hashing.Portable/src/RandomHash.cs b/lib/Hashing.Portable/src/RandomHash.cs index 5a4fc66..67518ad 100644 --- a/lib/Hashing.Portable/src/RandomHash.cs +++ b/lib/Hashing.Portable/src/RandomHash.cs @@ -23,6 +23,7 @@ */ using System; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using VNLib.Utils; @@ -36,6 +37,8 @@ namespace VNLib.Hashing public static class RandomHash { + private const int MAX_STACK_ALLOC = 128; + /// <summary> /// Generates a cryptographic random number, computes the hash, and encodes the hash as a string. /// </summary> @@ -45,12 +48,28 @@ namespace VNLib.Hashing /// <returns>String containing hash of the random number</returns> public static string GetRandomHash(HashAlg alg, int size = 64, HashEncodingMode encoding = HashEncodingMode.Base64) { - //Get temporary buffer for storing random keys - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(size); - //Fill with random non-zero bytes - GetRandomBytes(buffer.Span); - //Compute hash - return ManagedHash.ComputeHash(buffer.Span, alg, encoding); + if(size > MAX_STACK_ALLOC) + { + //Get temporary buffer for storing random keys + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(size); + + //Fill with random non-zero bytes + GetRandomBytes(buffer.Span); + + //Compute hash + return ManagedHash.ComputeHash(buffer.Span, alg, encoding); + } + else + { + //Get temporary buffer for storing random keys + Span<byte> buffer = stackalloc byte[size]; + + //Fill with random non-zero bytes + GetRandomBytes(buffer); + + //Compute hash + return ManagedHash.ComputeHash(buffer, alg, encoding); + } } /// <summary> @@ -60,25 +79,27 @@ namespace VNLib.Hashing /// <exception cref="FormatException"></exception> public static string GetGuidHash(HashAlg alg, HashEncodingMode encoding = HashEncodingMode.Base64) { - //Get temp buffer - Span<byte> buffer = stackalloc byte[16]; + //Get temp buffer, the size of the guid + Span<byte> buffer = stackalloc byte[Unsafe.SizeOf<Guid>()]; + //Get a new GUID and write bytes to - if (!Guid.NewGuid().TryWriteBytes(buffer)) - { - throw new FormatException("Failed to get a guid hash"); - } - return ManagedHash.ComputeHash(buffer, alg, encoding); + return Guid.NewGuid().TryWriteBytes(buffer) + ? ManagedHash.ComputeHash(buffer, alg, encoding) + : throw new FormatException("Failed to get a guid hash"); } + /// <summary> /// Generates a secure random number and seeds a GUID object, then returns the string GUID /// </summary> /// <returns>Guid string</returns> public static Guid GetSecureGuid() { - //Get temp buffer - Span<byte> buffer = stackalloc byte[16]; + //Get temp buffer size of Guid + Span<byte> buffer = stackalloc byte[Unsafe.SizeOf<Guid>()]; + //Generate non zero bytes GetRandomBytes(buffer); + //Get a GUID initialized with the key data and return the string represendation return new Guid(buffer); } @@ -90,13 +111,30 @@ namespace VNLib.Hashing /// <returns>Base64 string of the random number</returns> public static string GetRandomBase64(int size = 64) { - //Get temp buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(size); - //Generate non zero bytes - GetRandomBytes(buffer.Span); - //Convert to base 64 - return Convert.ToBase64String(buffer.Span, Base64FormattingOptions.None); + if (size > MAX_STACK_ALLOC) + { + //Get temp buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(size); + + //Generate non zero bytes + GetRandomBytes(buffer.Span); + + //Convert to base 64 + return Convert.ToBase64String(buffer.Span, Base64FormattingOptions.None); + } + else + { + //Get temp buffer + Span<byte> buffer = stackalloc byte[size]; + + //Generate non zero bytes + GetRandomBytes(buffer); + + //Convert to base 64 + return Convert.ToBase64String(buffer, Base64FormattingOptions.None); + } } + /// <summary> /// Generates a cryptographic random number and returns the hex string of that number /// </summary> @@ -104,13 +142,30 @@ namespace VNLib.Hashing /// <returns>Hex string of the random number</returns> public static string GetRandomHex(int size = 64) { - //Get temp buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(size); - //Generate non zero bytes - GetRandomBytes(buffer.Span); - //Convert to hex - return Convert.ToHexString(buffer.Span); + if (size > MAX_STACK_ALLOC) + { + //Get temp buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(size); + + //Generate non zero bytes + GetRandomBytes(buffer.Span); + + //Convert to hex + return Convert.ToHexString(buffer.Span); + } + else + { + //Get temp buffer + Span<byte> buffer = stackalloc byte[size]; + + //Generate non zero bytes + GetRandomBytes(buffer); + + //Convert to hex + return Convert.ToHexString(buffer); + } } + /// <summary> /// Generates a cryptographic random number and returns the Base32 encoded string of that number /// </summary> @@ -118,12 +173,28 @@ namespace VNLib.Hashing /// <returns>Base32 string of the random number</returns> public static string GetRandomBase32(int size = 64) { - //Get temporary buffer for storing random keys - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(size); - //Fill with random non-zero bytes - GetRandomBytes(buffer.Span); - //Return string of encoded data - return VnEncoding.ToBase32String(buffer.Span); + if (size > MAX_STACK_ALLOC) + { + //Get temp buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(size); + + //Generate non zero bytes + GetRandomBytes(buffer.Span); + + //Convert to hex + return VnEncoding.ToBase32String(buffer.Span); + } + else + { + //Get temp buffer + Span<byte> buffer = stackalloc byte[size]; + + //Generate non zero bytes + GetRandomBytes(buffer); + + //Convert to hex + return VnEncoding.ToBase32String(buffer); + } } /// <summary> @@ -137,13 +208,11 @@ namespace VNLib.Hashing GetRandomBytes(rand); return rand; } + /// <summary> /// Fill the buffer with non-zero bytes /// </summary> /// <param name="data">Buffer to fill</param> - public static void GetRandomBytes(Span<byte> data) - { - RandomNumberGenerator.Fill(data); - } + public static void GetRandomBytes(Span<byte> data) => RandomNumberGenerator.Fill(data); } }
\ No newline at end of file diff --git a/lib/Net.Http/src/ConnectionInfo.cs b/lib/Net.Http/src/Core/ConnectionInfo.cs index 6e1660d..6e1660d 100644 --- a/lib/Net.Http/src/ConnectionInfo.cs +++ b/lib/Net.Http/src/Core/ConnectionInfo.cs diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs index 593275d..356c3f6 100644 --- a/lib/Net.Http/src/Core/Request/HttpRequest.cs +++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs @@ -137,9 +137,12 @@ namespace VNLib.Net.Http.Core public string Compile() { //Alloc char buffer for compilation - using UnsafeMemoryHandle<char> buffer = Memory.UnsafeAlloc<char>(16 * 1024, true); + using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(16 * 1024, true); + ForwardOnlyWriter<char> writer = new(buffer.Span); + Compile(ref writer); + return writer.ToString(); } diff --git a/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs index 5cc5ed9..15c617c 100644 --- a/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs +++ b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs @@ -104,7 +104,7 @@ namespace VNLib.Net.Http.Core /// </summary> public static IUnmangedHeap HttpPrivateHeap => _lazyHeap.Value; - private static readonly Lazy<IUnmangedHeap> _lazyHeap = new(Memory.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy<IUnmangedHeap> _lazyHeap = new(MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); /// <summary> /// Alloctes an unsafe block of memory from the internal heap, or buffer pool @@ -120,11 +120,11 @@ namespace VNLib.Net.Http.Core size = (size / 4096 + 1) * 4096; //If rpmalloc lib is loaded, use it - if (Memory.IsRpMallocLoaded) + if (MemoryUtil.IsRpMallocLoaded) { - return Memory.Shared.UnsafeAlloc<byte>(size, zero); + return MemoryUtil.Shared.UnsafeAlloc<byte>(size, zero); } - else if (size > Memory.MAX_UNSAFE_POOL_SIZE) + else if (size > MemoryUtil.MAX_UNSAFE_POOL_SIZE) { return HttpPrivateHeap.UnsafeAlloc<byte>(size, zero); } @@ -140,12 +140,12 @@ namespace VNLib.Net.Http.Core size = (size / 4096 + 1) * 4096; //If rpmalloc lib is loaded, use it - if (Memory.IsRpMallocLoaded) + if (MemoryUtil.IsRpMallocLoaded) { - return Memory.Shared.DirectAlloc<byte>(size, zero); + return MemoryUtil.Shared.DirectAlloc<byte>(size, zero); } //Avoid locking in heap unless the buffer is too large to alloc array - else if (size > Memory.MAX_UNSAFE_POOL_SIZE) + else if (size > MemoryUtil.MAX_UNSAFE_POOL_SIZE) { return HttpPrivateHeap.DirectAlloc<byte>(size, zero); } diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs index f02724a..0e46582 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs @@ -276,8 +276,11 @@ namespace VNLib.Net.Messaging.FBM.Client public string Compile() { int charSize = Helpers.DefaultEncoding.GetCharCount(RequestData.Span); - using UnsafeMemoryHandle<char> buffer = Memory.UnsafeAlloc<char>(charSize + 128); + + using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(charSize + 128); + ERRNO count = Compile(buffer.Span); + return buffer.AsSpan(0, count).ToString(); } ///<inheritdoc/> diff --git a/lib/Plugins.Essentials/src/Accounts/AccountManager.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs index f148fdb..610d646 100644 --- a/lib/Plugins.Essentials/src/Accounts/AccountManager.cs +++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs @@ -39,7 +39,6 @@ using VNLib.Plugins.Essentials.Users; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Essentials.Extensions; - #nullable enable namespace VNLib.Plugins.Essentials.Accounts @@ -50,7 +49,7 @@ namespace VNLib.Plugins.Essentials.Accounts /// to facilitate unified user-controls, athentication, and security /// application-wide /// </summary> - public static partial class AccountManager + public static partial class AccountUtil { public const int MAX_EMAIL_CHARS = 50; public const int ID_FIELD_CHARS = 65; @@ -104,6 +103,7 @@ namespace VNLib.Plugins.Essentials.Accounts private const string FAILED_LOGIN_ENTRY = "acnt.flc"; private const string LOCAL_ACCOUNT_ENTRY = "acnt.ila"; private const string ACC_ORIGIN_ENTRY = "__.org"; + private const string TOKEN_UPDATE_TIME_ENTRY = "acnt.tut"; //private const string CHALLENGE_HASH_ENTRY = "acnt.chl"; //Privlage masks @@ -164,7 +164,7 @@ namespace VNLib.Plugins.Essentials.Accounts throw new ObjectDisposedException("The specifed user object has been released"); } //Alloc a buffer - using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(size); + using IMemoryHandle<byte> buffer = MemoryUtil.SafeAlloc<byte>(size); //Use the CGN to get a random set RandomHash.GetRandomBytes(buffer.Span); //Hash the new random password @@ -189,6 +189,7 @@ namespace VNLib.Plugins.Essentials.Accounts /// This field is not required /// </summary> /// <returns>The origin of the account</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetAccountOrigin(this IUser ud) => ud[ACC_ORIGIN_ENTRY]; /// <summary> /// If this account was created by any means other than a local account creation. @@ -196,6 +197,7 @@ namespace VNLib.Plugins.Essentials.Accounts /// </summary> /// <param name="ud"></param> /// <param name="origin">Value of the account origin</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetAccountOrigin(this IUser ud, string origin) => ud[ACC_ORIGIN_ENTRY] = origin; /// <summary> @@ -241,6 +243,8 @@ namespace VNLib.Plugins.Essentials.Accounts { throw new InvalidOperationException("The session is not set or the session is not a web-based session type"); } + //Update session-id for "upgrade" + ev.Session.RegenID(); //derrive token from login data TryGenerateToken(base64PubKey, out string base64ServerToken, out string base64ClientData); //Clear flags @@ -260,6 +264,8 @@ namespace VNLib.Plugins.Essentials.Accounts ev.Session.HasLocalAccount(localAccount); //Store the base64 server key to compute the hmac later ev.Session.Token = base64ServerToken; + //Update the last token upgrade time + ev.Session.LastTokenUpgrade(ev.RequestedTimeUtc); //Return the client encrypted data return base64ClientData; } @@ -325,7 +331,7 @@ namespace VNLib.Plugins.Essentials.Accounts private static void TryGenerateToken(string base64clientPublicKey, out string base64Digest, out string base64ClientData) { //Temporary work buffer - using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(4096, true); + using IMemoryHandle<byte> buffer = MemoryUtil.SafeAlloc<byte>(4096, true); /* * Create a new token buffer for bin buffers. * This buffer struct is used to break up @@ -396,7 +402,7 @@ namespace VNLib.Plugins.Essentials.Accounts * are equal it should mean that the client must have the private * key that generated the public key that was sent */ - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(TokenHashSize * 2, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(TokenHashSize * 2, true); //Slice up buffers Span<byte> headerBuffer = buffer.Span[..TokenHashSize]; Span<byte> sessionBuffer = buffer.Span[TokenHashSize..]; @@ -405,8 +411,31 @@ namespace VNLib.Plugins.Essentials.Accounts && Convert.TryFromBase64String(ev.Session.Token, sessionBuffer, out int sessionTokenLen)) { //Do a fixed time equal (probably overkill, but should not matter too much) - return CryptographicOperations.FixedTimeEquals(headerBuffer[..headerTokenLen], sessionBuffer[..sessionTokenLen]); + if(CryptographicOperations.FixedTimeEquals(headerBuffer[..headerTokenLen], sessionBuffer[..sessionTokenLen])) + { + return true; + } + } + + /* + * If the token does not match, or cannot be found, check if the client + * has login cookies set, if not remove them. + * + * This does not affect the session, but allows for a web client to update + * its login state if its no-longer logged in + */ + + //Expire login cookie if set + if (ev.Server.RequestCookies.ContainsKey(LOGIN_COOKIE_NAME)) + { + ev.Server.ExpireCookie(LOGIN_COOKIE_NAME, sameSite: CookieSameSite.SameSite); } + //Expire the LI cookie if set + if (ev.Server.RequestCookies.ContainsKey(LOGIN_COOKIE_IDENTIFIER)) + { + ev.Server.ExpireCookie(LOGIN_COOKIE_IDENTIFIER, sameSite: CookieSameSite.SameSite); + } + return false; } @@ -432,6 +461,8 @@ namespace VNLib.Plugins.Essentials.Accounts TryGenerateToken(clientPublicKey, out string base64Digest, out string base64ClientData); //store the token to the user's session ev.Session.Token = base64Digest; + //Update the last token upgrade time + ev.Session.LastTokenUpgrade(ev.RequestedTimeUtc); //return the clients encrypted secret return base64ClientData; } @@ -478,7 +509,7 @@ namespace VNLib.Plugins.Essentials.Accounts return false; } //Alloc a buffer for decoding the public key - using UnsafeMemoryHandle<byte> pubKeyBuffer = Memory.UnsafeAlloc<byte>(PUBLIC_KEY_BUFFER_SIZE, true); + using UnsafeMemoryHandle<byte> pubKeyBuffer = MemoryUtil.UnsafeAlloc<byte>(PUBLIC_KEY_BUFFER_SIZE, true); //Decode the public key ERRNO pbkBytesWritten = VnEncoding.TryFromBase64Chars(base64PubKey, pubKeyBuffer); //Try to encrypt the data @@ -590,7 +621,7 @@ namespace VNLib.Plugins.Essentials.Accounts * be 2 * LOGIN_COOKIE_SIZE, and it can be split in half and shared * for both conversions */ - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(2 * LOGIN_COOKIE_SIZE, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(2 * LOGIN_COOKIE_SIZE, true); //Slice up buffers Span<byte> cookieBuffer = buffer.Span[..LOGIN_COOKIE_SIZE]; Span<byte> sessionBuffer = buffer.Span.Slice(LOGIN_COOKIE_SIZE, LOGIN_COOKIE_SIZE); @@ -602,7 +633,7 @@ namespace VNLib.Plugins.Essentials.Accounts if(CryptographicOperations.FixedTimeEquals(cookieBuffer, sessionBuffer)) { //If the user is "logged in" and the request is using the POST method, then we can update the cookie - if(ev.Server.Method == HttpMethod.POST && ev.Session.Created.Add(RegenIdPeriod) < DateTimeOffset.UtcNow) + if(ev.Server.Method == HttpMethod.POST && ev.Session.Created.Add(RegenIdPeriod) < ev.RequestedTimeUtc) { //Regen login token ev.SetLogin(); @@ -655,7 +686,26 @@ namespace VNLib.Plugins.Essentials.Accounts } } } - + + /// <summary> + /// Gets the last time the session token was set + /// </summary> + /// <param name="session"></param> + /// <returns>The last time the token was updated/generated, or <see cref="DateTimeOffset.MinValue"/> if not set</returns> + public static DateTimeOffset LastTokenUpgrade(this in SessionInfo session) + { + //Get the serialized time value + string timeString = session[TOKEN_UPDATE_TIME_ENTRY]; + return long.TryParse(timeString, out long time) ? DateTimeOffset.FromUnixTimeSeconds(time) : DateTimeOffset.MinValue; + } + + /// <summary> + /// Updates the last time the session token was set + /// </summary> + /// <param name="session"></param> + /// <param name="updated">The UTC time the last token was set</param> + private static void LastTokenUpgrade(this in SessionInfo session, DateTimeOffset updated) + => session[TOKEN_UPDATE_TIME_ENTRY] = updated.ToUnixTimeSeconds().ToString(); /// <summary> /// Stores the browser's id during a login process @@ -711,7 +761,7 @@ namespace VNLib.Plugins.Essentials.Accounts //Calculate the password buffer size required int passByteCount = Encoding.UTF8.GetByteCount(rawPass); //Allocate the buffer - using UnsafeMemoryHandle<byte> bufferHandle = Memory.UnsafeAlloc<byte>(passByteCount + 64, true); + using UnsafeMemoryHandle<byte> bufferHandle = MemoryUtil.UnsafeAlloc<byte>(passByteCount + 64, true); //Slice buffers Span<byte> utf8PassBytes = bufferHandle.Span[..passByteCount]; Span<byte> hashBuffer = bufferHandle.Span[passByteCount..]; @@ -750,7 +800,7 @@ namespace VNLib.Plugins.Essentials.Accounts } int bufSize = base32Digest.Length + base64PasswordDigest.Length; //Alloc buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(bufSize); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(bufSize); //Split buffers Span<byte> localBuf = buffer.Span[..base32Digest.Length]; Span<byte> passBuf = buffer.Span[base32Digest.Length..]; diff --git a/lib/Plugins.Essentials/src/Accounts/INonce.cs b/lib/Plugins.Essentials/src/Accounts/INonce.cs index 7d53183..3a1b779 100644 --- a/lib/Plugins.Essentials/src/Accounts/INonce.cs +++ b/lib/Plugins.Essentials/src/Accounts/INonce.cs @@ -24,9 +24,6 @@ using System; -using VNLib.Utils; -using VNLib.Utils.Memory; - namespace VNLib.Plugins.Essentials.Accounts { /// <summary> @@ -48,43 +45,4 @@ namespace VNLib.Plugins.Essentials.Accounts /// <returns>True if the nonce values are equal, flase otherwise</returns> bool VerifyNonce(ReadOnlySpan<byte> nonceBytes); } - - /// <summary> - /// Provides INonce extensions for computing/verifying nonce values - /// </summary> - public static class NonceExtensions - { - /// <summary> - /// Computes a base32 nonce of the specified size and returns a string - /// representation - /// </summary> - /// <param name="nonce"></param> - /// <param name="size">The size (in bytes) of the nonce</param> - /// <returns>The base32 string of the computed nonce</returns> - public static string ComputeNonce<T>(this T nonce, int size) where T: INonce - { - //Alloc bin buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(size); - //Compute nonce - nonce.ComputeNonce(buffer.Span); - //Return base32 string - return VnEncoding.ToBase32String(buffer.Span, false); - } - /// <summary> - /// Compares the base32 encoded nonce value against the previously - /// generated nonce - /// </summary> - /// <param name="nonce"></param> - /// <param name="base32Nonce">The base32 encoded nonce string</param> - /// <returns>True if the nonce values are equal, flase otherwise</returns> - public static bool VerifyNonce<T>(this T nonce, ReadOnlySpan<char> base32Nonce) where T : INonce - { - //Alloc bin buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(base32Nonce.Length); - //Decode base32 nonce - ERRNO count = VnEncoding.TryFromBase32Chars(base32Nonce, buffer.Span); - //Verify nonce - return nonce.VerifyNonce(buffer.Span[..(int)count]); - } - } } diff --git a/lib/Plugins.Essentials/src/Accounts/ISecretProvider.cs b/lib/Plugins.Essentials/src/Accounts/ISecretProvider.cs new file mode 100644 index 0000000..41fb44d --- /dev/null +++ b/lib/Plugins.Essentials/src/Accounts/ISecretProvider.cs @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: ISecretProvider.cs +* +* ISecretProvider.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; + +using VNLib.Utils; + +namespace VNLib.Plugins.Essentials.Accounts +{ + /// <summary> + /// Provides a password hashing secret aka pepper. + /// </summary> + public interface ISecretProvider + { + /// <summary> + /// The size of the buffer to use when retrieving the secret + /// </summary> + int BufferSize { get; } + + /// <summary> + /// Writes the secret to the buffer and returns the number of bytes + /// written to the buffer + /// </summary> + /// <param name="buffer">The buffer to write the secret data to</param> + /// <returns>The number of secret bytes written to the buffer</returns> + ERRNO GetSecret(Span<byte> buffer); + } +}
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Accounts/NonceExtensions.cs b/lib/Plugins.Essentials/src/Accounts/NonceExtensions.cs new file mode 100644 index 0000000..5a40d29 --- /dev/null +++ b/lib/Plugins.Essentials/src/Accounts/NonceExtensions.cs @@ -0,0 +1,75 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: NonceExtensions.cs +* +* NonceExtensions.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; + +using VNLib.Utils; +using VNLib.Utils.Memory; + +namespace VNLib.Plugins.Essentials.Accounts +{ + /// <summary> + /// Provides INonce extensions for computing/verifying nonce values + /// </summary> + public static class NonceExtensions + { + /// <summary> + /// Computes a base32 nonce of the specified size and returns a string + /// representation + /// </summary> + /// <param name="nonce"></param> + /// <param name="size">The size (in bytes) of the nonce</param> + /// <returns>The base32 string of the computed nonce</returns> + public static string ComputeNonce<T>(this T nonce, int size) where T: INonce + { + //Alloc bin buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(size); + + //Compute nonce + nonce.ComputeNonce(buffer.Span); + + //Return base32 string + return VnEncoding.ToBase32String(buffer.Span, false); + } + + /// <summary> + /// Compares the base32 encoded nonce value against the previously + /// generated nonce + /// </summary> + /// <param name="nonce"></param> + /// <param name="base32Nonce">The base32 encoded nonce string</param> + /// <returns>True if the nonce values are equal, flase otherwise</returns> + public static bool VerifyNonce<T>(this T nonce, ReadOnlySpan<char> base32Nonce) where T : INonce + { + //Alloc bin buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(base32Nonce.Length); + + //Decode base32 nonce + ERRNO count = VnEncoding.TryFromBase32Chars(base32Nonce, buffer.Span); + + //Verify nonce + return nonce.VerifyNonce(buffer.Span[..(int)count]); + } + } +} diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs index 1c3770b..9dc3ea1 100644 --- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs +++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs @@ -32,21 +32,12 @@ using VNLib.Utils.Memory; namespace VNLib.Plugins.Essentials.Accounts { /// <summary> - /// A delegate method to recover a temporary copy of the secret/pepper - /// for a request - /// </summary> - /// <param name="buffer">The buffer to write the pepper to</param> - /// <returns>The number of bytes written to the buffer</returns> - public delegate ERRNO SecretAction(Span<byte> buffer); - - /// <summary> - /// Provides a structrued password hashing system implementing the <seealso cref="VnArgon2"/> library + /// Provides a structured password hashing system implementing the <seealso cref="VnArgon2"/> library /// with fixed time comparison /// </summary> public sealed class PasswordHashing { - private readonly SecretAction _getter; - private readonly int _secretSize; + private readonly ISecretProvider _secret; private readonly uint TimeCost; private readonly uint MemoryCost; @@ -57,23 +48,20 @@ namespace VNLib.Plugins.Essentials.Accounts /// <summary> /// Initalizes the <see cref="PasswordHashing"/> class /// </summary> - /// <param name="getter"></param> - /// <param name="secreteSize">The expected size of the secret (the size of the buffer to alloc for a copy)</param> + /// <param name="secret">The password secret provider</param> /// <param name="saltLen">A positive integer for the size of the random salt used during the hashing proccess</param> /// <param name="timeCost">The Argon2 time cost parameter</param> /// <param name="memoryCost">The Argon2 memory cost parameter</param> /// <param name="hashLen">The size of the hash to produce during hashing operations</param> /// <param name="parallism"> /// The Argon2 parallelism parameter (the number of threads to use for hasing) - /// (default = 0 - the number of processors) + /// (default = 0 - defaults to the number of logical processors) /// </param> /// <exception cref="ArgumentNullException"></exception> - /// <exception cref="ArgumentOutOfRangeException"></exception> - public PasswordHashing(SecretAction getter, int secreteSize, int saltLen = 32, uint timeCost = 4, uint memoryCost = UInt16.MaxValue, uint parallism = 0, uint hashLen = 128) + public PasswordHashing(ISecretProvider secret, int saltLen = 32, uint timeCost = 4, uint memoryCost = UInt16.MaxValue, uint parallism = 0, uint hashLen = 128) { //Store getter - _getter = getter ?? throw new ArgumentNullException(nameof(getter)); - _secretSize = secreteSize; + _secret = secret ?? throw new ArgumentNullException(nameof(secret)); //Store parameters HashLen = hashLen; @@ -114,18 +102,18 @@ namespace VNLib.Plugins.Essentials.Accounts return false; } //alloc secret buffer - using UnsafeMemoryHandle<byte> secretBuffer = Memory.UnsafeAlloc<byte>(_secretSize, true); + using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAlloc<byte>(_secret.BufferSize, true); try { //Get the secret from the callback - ERRNO count = _getter(secretBuffer.Span); + ERRNO count = _secret.GetSecret(secretBuffer.Span); //Verify return VnArgon2.Verify2id(password, passHash, secretBuffer.Span[..(int)count]); } finally { //Erase secret buffer - Memory.InitializeBlock(secretBuffer.Span); + MemoryUtil.InitializeBlock(secretBuffer.Span); } } /// <summary> @@ -140,7 +128,7 @@ namespace VNLib.Plugins.Essentials.Accounts public bool Verify(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> password) { //Alloc a buffer with the same size as the hash - using UnsafeMemoryHandle<byte> hashBuf = Memory.UnsafeAlloc<byte>(hash.Length, true); + using UnsafeMemoryHandle<byte> hashBuf = MemoryUtil.UnsafeAlloc<byte>(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 @@ -164,7 +152,7 @@ namespace VNLib.Plugins.Essentials.Accounts public PrivateString Hash(ReadOnlySpan<char> password) { //Alloc shared buffer for the salt and secret buffer - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(SaltLen + _secretSize, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(SaltLen + _secret.BufferSize, true); try { //Split buffers @@ -175,14 +163,14 @@ namespace VNLib.Plugins.Essentials.Accounts RandomHash.GetRandomBytes(saltBuf); //recover the secret - ERRNO count = _getter(secretBuf); + 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); } finally { - Memory.InitializeBlock(buffer.Span); + MemoryUtil.InitializeBlock(buffer.Span); } } @@ -194,7 +182,7 @@ namespace VNLib.Plugins.Essentials.Accounts /// <returns>A <see cref="PrivateString"/> of the hashed and encoded password</returns> public PrivateString Hash(ReadOnlySpan<byte> password) { - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(SaltLen + _secretSize, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(SaltLen + _secret.BufferSize, true); try { //Split buffers @@ -205,14 +193,14 @@ namespace VNLib.Plugins.Essentials.Accounts RandomHash.GetRandomBytes(saltBuf); //recover the secret - ERRNO count = _getter(secretBuf); + 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); } finally { - Memory.InitializeBlock(buffer.Span); + MemoryUtil.InitializeBlock(buffer.Span); } } /// <summary> @@ -226,18 +214,18 @@ namespace VNLib.Plugins.Essentials.Accounts public void Hash(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, Span<byte> hashOutput) { //alloc secret buffer - using UnsafeMemoryHandle<byte> secretBuffer = Memory.UnsafeAlloc<byte>(_secretSize, true); + using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAlloc<byte>(_secret.BufferSize, true); try { //Get the secret from the callback - ERRNO count = _getter(secretBuffer.Span); + 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); } finally { //Erase secret buffer - Memory.InitializeBlock(secretBuffer.Span); + MemoryUtil.InitializeBlock(secretBuffer.Span); } } } diff --git a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs b/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs index 22cccd9..d087c06 100644 --- a/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs +++ b/lib/Plugins.Essentials/src/Extensions/JsonResponse.cs @@ -49,12 +49,19 @@ namespace VNLib.Plugins.Essentials.Extensions internal JsonResponse(IObjectRental<JsonResponse> pool) { + /* + * I am breaking the memoryhandle rules by referrencing the same + * memory handle in two different wrappers. + */ + _pool = pool; //Alloc buffer - _handle = Memory.Shared.Alloc<byte>(4096, false); + _handle = MemoryUtil.Shared.Alloc<byte>(4096, false); + //Consume handle for stream, but make sure not to dispose the stream _asStream = VnMemoryStream.ConsumeHandle(_handle, 0, false); + //Get memory owner from handle _memoryOwner = _handle.ToMemoryManager(false); } diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs index ffad607..416b004 100644 --- a/lib/Plugins.Essentials/src/HttpEntity.cs +++ b/lib/Plugins.Essentials/src/HttpEntity.cs @@ -77,6 +77,9 @@ namespace VNLib.Plugins.Essentials IsLocalConnection = entity.Server.LocalEndpoint.Address.IsLocalSubnet(TrustedRemoteIp); //Cache value IsSecure = entity.Server.IsSecure(IsBehindDownStreamServer); + + //Cache current time + RequestedTimeUtc = DateTimeOffset.UtcNow; } /// <summary> @@ -100,6 +103,11 @@ namespace VNLib.Plugins.Essentials /// or behind a trusted downstream server that is using tls. /// </summary> public readonly bool IsSecure; + /// <summary> + /// Caches a <see cref="DateTimeOffset"/> that was created when the connection was created. + /// The approximate current UTC time + /// </summary> + public readonly DateTimeOffset RequestedTimeUtc; /// <summary> /// The connection info object assocated with the entity diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs index 13e2a84..6a974e0 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs @@ -106,7 +106,7 @@ namespace VNLib.Plugins.Essentials.Sessions /// </summary> public readonly Uri SpecifiedOrigin; /// <summary> - /// Privilages associated with user specified during login + /// The time the session was created /// </summary> public readonly DateTimeOffset Created; /// <summary> diff --git a/lib/Utils/src/Extensions/IoExtensions.cs b/lib/Utils/src/Extensions/IoExtensions.cs index baba7dc..637cfab 100644 --- a/lib/Utils/src/Extensions/IoExtensions.cs +++ b/lib/Utils/src/Extensions/IoExtensions.cs @@ -33,7 +33,7 @@ using System.Runtime.CompilerServices; using VNLib.Utils.IO; using VNLib.Utils.Memory; -using static VNLib.Utils.Memory.Memory; +using static VNLib.Utils.Memory.MemoryUtil; namespace VNLib.Utils.Extensions { diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index c8ee5ef..17ad79d 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -124,7 +124,7 @@ namespace VNLib.Utils.Extensions } /// <summary> - /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="PrivateHeap"/> instance + /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance /// of the specified number of elements /// </summary> /// <typeparam name="T">The unmanaged data type</typeparam> @@ -133,13 +133,39 @@ namespace VNLib.Utils.Extensions /// <param name="zero">Optionally zeros conents of the block when allocated</param> /// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, ulong size, bool zero = false) where T : unmanaged + public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, nuint size, bool zero = false) where T : unmanaged { return new SysBufferMemoryManager<T>(heap, size, zero); } /// <summary> - /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="PrivateHeap"/> instance + /// Gets the integer length (number of elements) of the <see cref="IMemoryHandle{T}"/> + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="handle"></param> + /// <returns> + /// The integer length of the handle, or throws <see cref="OverflowException"/> if + /// the platform is 64bit and the handle is larger than <see cref="int.MaxValue"/> + /// </returns> + /// <exception cref="OverflowException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetIntLength<T>(this IMemoryHandle<T> handle) => Convert.ToInt32(handle.Length); + + /// <summary> + /// Gets the integer length (number of elements) of the <see cref="UnsafeMemoryHandle{T}"/> + /// </summary> + /// <typeparam name="T">The unmanaged type</typeparam> + /// <param name="handle"></param> + /// <returns> + /// The integer length of the handle, or throws <see cref="OverflowException"/> if + /// the platform is 64bit and the handle is larger than <see cref="int.MaxValue"/> + /// </returns> + //Method only exists for consistancy since unsafe handles are always 32bit + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T: unmanaged => handle.IntLength; + + /// <summary> + /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance /// of the specified number of elements /// </summary> /// <typeparam name="T">The unmanaged data type</typeparam> @@ -147,11 +173,12 @@ namespace VNLib.Utils.Extensions /// <param name="size">The number of elements to allocate on the heap</param> /// <param name="zero">Optionally zeros conents of the block when allocated</param> /// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns> + /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, long size, bool zero = false) where T : unmanaged + public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, nint size, bool zero = false) where T : unmanaged { - return size < 0 ? throw new ArgumentOutOfRangeException(nameof(size)) : DirectAlloc<T>(heap, (ulong)size, zero); + return size >= 0 ? DirectAlloc<T>(heap, (nuint)size, zero) : throw new ArgumentOutOfRangeException(nameof(size), "The size paramter must be a positive integer"); } /// <summary> /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks @@ -161,11 +188,10 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <returns><typeparamref name="T"/> pointer to the memory offset specified</returns> - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe T* GetOffset<T>(this MemoryHandle<T> memory, long elements) where T : unmanaged + public static unsafe T* GetOffset<T>(this MemoryHandle<T> memory, nint elements) where T : unmanaged { - return elements < 0 ? throw new ArgumentOutOfRangeException(nameof(elements)) : memory.GetOffset((ulong)elements); + return elements >= 0 ? memory.GetOffset((nuint)elements) : throw new ArgumentOutOfRangeException(nameof(elements), "The elements paramter must be a positive integer"); } /// <summary> /// Resizes the current handle on the heap @@ -177,13 +203,13 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Resize<T>(this MemoryHandle<T> memory, long elements) where T : unmanaged + public static void Resize<T>(this MemoryHandle<T> memory, nint elements) where T : unmanaged { if (elements < 0) { throw new ArgumentOutOfRangeException(nameof(elements)); } - memory.Resize((ulong)elements); + memory.Resize((nuint)elements); } /// <summary> @@ -197,13 +223,13 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, long count) where T : unmanaged + public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nint count) where T : unmanaged { if(count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } - ResizeIfSmaller(handle, (ulong)count); + ResizeIfSmaller(handle, (nuint)count); } /// <summary> @@ -217,7 +243,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, ulong count) where T : unmanaged + public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nuint count) where T : unmanaged { //Check handle size if(handle.Length < count) @@ -227,7 +253,7 @@ namespace VNLib.Utils.Extensions } } -#if TARGET_64_BIT + /// <summary> /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/> /// </summary> @@ -238,9 +264,10 @@ namespace VNLib.Utils.Extensions /// <returns>The offset span</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, ulong offset, int size) where T: unmanaged + public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nuint offset, int size) where T: unmanaged { _ = block ?? throw new ArgumentNullException(nameof(block)); + if(size < 0) { throw new ArgumentOutOfRangeException(nameof(size)); @@ -249,14 +276,13 @@ namespace VNLib.Utils.Extensions { return Span<T>.Empty; } - //Make sure the offset size is within the size of the block - if(offset + (ulong)size <= block.Length) - { - //Get long offset from the destination handle - void* ofPtr = block.GetOffset(offset); - return new Span<T>(ofPtr, size); - } - throw new ArgumentOutOfRangeException(nameof(size)); + + //Check bounds + MemoryUtil.CheckBounds(block, offset, (nuint)size); + + //Get long offset from the destination handle + void* ofPtr = block.GetOffset(offset); + return new Span<T>(ofPtr, size); } /// <summary> /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/> @@ -268,12 +294,11 @@ namespace VNLib.Utils.Extensions /// <returns>The offset span</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, long offset, int size) where T : unmanaged + public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged { - return offset < 0 ? throw new ArgumentOutOfRangeException(nameof(offset)) : block.GetOffsetSpan<T>((ulong)offset, size); + return offset >= 0 ? block.GetOffsetSpan((nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset)); } - /// <summary> /// Gets a <see cref="SubSequence{T}"/> window within the current block /// </summary> @@ -283,12 +308,8 @@ namespace VNLib.Utils.Extensions /// <param name="size">The size of the window</param> /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, ulong offset, int size) where T : unmanaged - { - return new SubSequence<T>(block, offset, size); - } -#else - + public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nuint offset, int size) where T : unmanaged => new (block, offset, size); + /// <summary> /// Gets a <see cref="SubSequence{T}"/> window within the current block /// </summary> @@ -298,30 +319,12 @@ namespace VNLib.Utils.Extensions /// <param name="size">The size of the window</param> /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, int offset, int size) where T : unmanaged + public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged { - return new SubSequence<T>(block, offset, size); + return offset >= 0 ? new (block, (nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset)); } /// <summary> - /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/> - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="block"></param> - /// <param name="offset">The offset (in elements) from the begining of the block</param> - /// <param name="size">The size of the block (in elements)</param> - /// <returns>The offset span</returns> - /// <exception cref="OverflowException"></exception> - /// <exception cref="ArgumentOutOfRangeException"></exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, long offset, int size) where T : unmanaged - { - //TODO fix 32bit/64 bit, this is a safe lazy workaround - return block.Span.Slice(checked((int) offset), size); - } -#endif - - /// <summary> /// Wraps the current instance with a <see cref="MemoryPool{T}"/> wrapper /// to allow System.Memory buffer rentals. /// </summary> @@ -346,10 +349,11 @@ namespace VNLib.Utils.Extensions public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged { //Allocate the struct on the heap and zero memory it points to - IntPtr handle = heap.Alloc(1, (uint)sizeof(T), true); + IntPtr handle = heap.Alloc(1, (nuint)sizeof(T), true); //returns the handle return (T*)handle; } + /// <summary> /// Frees a structure at the specified address from the this heap. /// This must be the same heap the structure was allocated from @@ -366,6 +370,7 @@ namespace VNLib.Utils.Extensions //Clear ref *structPtr = default; } + /// <summary> /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type /// </summary> @@ -378,17 +383,18 @@ namespace VNLib.Utils.Extensions /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, ulong elements, bool zero = false) where T : unmanaged + public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged { //Minimum of one element elements = Math.Max(elements, 1); //Get element size - uint elementSize = (uint)sizeof(T); + nuint elementSize = (nuint)sizeof(T); //If zero flag is set then specify zeroing memory IntPtr block = heap.Alloc(elements, elementSize, zero); //Return handle wrapper return new MemoryHandle<T>(heap, block, elements, zero); } + /// <summary> /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type /// </summary> @@ -401,10 +407,11 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, long elements, bool zero = false) where T : unmanaged + public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged { - return elements < 0 ? throw new ArgumentOutOfRangeException(nameof(elements)) : Alloc<T>(heap, (ulong)elements, zero); + return elements >= 0 ? Alloc<T>(heap, (nuint)elements, zero) : throw new ArgumentOutOfRangeException(nameof(elements)); } + /// <summary> /// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer /// </summary> @@ -417,8 +424,12 @@ namespace VNLib.Utils.Extensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T:unmanaged { + //Aloc block MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length); - Memory.Copy(initialData, handle, 0); + + //Copy initial data + MemoryUtil.Copy(initialData, handle, 0); + return handle; } @@ -435,12 +446,13 @@ namespace VNLib.Utils.Extensions public static void WriteAndResize<T>(this MemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged { handle.Resize(input.Length); - Memory.Copy(input, handle, 0); + MemoryUtil.Copy(input, handle, 0); } /// <summary> /// Allocates a block of unamanged memory of the number of elements of an unmanaged type, and - /// returns the <see cref="UnsafeMemoryHandle{T}"/> that must be used cautiously + /// returns the <see cref="UnsafeMemoryHandle{T}"/> that must be used cautiously. + /// If elements is less than 1 an empty handle is returned /// </summary> /// <typeparam name="T">The unamanged value type</typeparam> /// <param name="heap">The heap to allocate block from</param> @@ -455,14 +467,16 @@ namespace VNLib.Utils.Extensions { if (elements < 1) { - throw new ArgumentException("Elements must be greater than 0", nameof(elements)); + //Return an empty handle + return new UnsafeMemoryHandle<T>(); } - //Minimum of one element - elements = Math.Max(elements, 1); + //Get element size - uint elementSize = (uint)sizeof(T); - //If zero flag is set then specify zeroing memory - IntPtr block = heap.Alloc((uint)elements, elementSize, zero); + nuint elementSize = (nuint)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); + //handle wrapper return new (heap, block, elements); } @@ -560,8 +574,6 @@ namespace VNLib.Utils.Extensions buffer.Advance(charsWritten); } - - /// <summary> /// Encodes a set of characters in the input characters span and any characters /// in the internal buffer into a sequence of bytes that are stored in the input @@ -644,11 +656,13 @@ namespace VNLib.Utils.Extensions /// Converts the buffer data to a <see cref="PrivateString"/> /// </summary> /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true); /// <summary> /// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer /// </summary> /// <returns>A <see cref="Span{T}"/> over the modified data</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<T> AsSpan<T>(this ref ForwardOnlyWriter<T> buffer) => buffer.Buffer[..buffer.Written]; diff --git a/lib/Utils/src/Extensions/VnStringExtensions.cs b/lib/Utils/src/Extensions/VnStringExtensions.cs index 285fc4f..329c7a6 100644 --- a/lib/Utils/src/Extensions/VnStringExtensions.cs +++ b/lib/Utils/src/Extensions/VnStringExtensions.cs @@ -25,13 +25,17 @@ using System; using System.Linq; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using VNLib.Utils.Memory; +using System.Runtime.CompilerServices; + +#pragma warning disable CA1062 // Validate arguments of public methods namespace VNLib.Utils.Extensions { - [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")] + /// <summary> + /// A collection of extensions for <see cref="VnString"/> + /// </summary> public static class VnStringExtensions { /// <summary> @@ -41,7 +45,9 @@ namespace VNLib.Utils.Extensions /// <param name="value">The value to find</param> /// <returns>True if the character exists within the instance</returns> /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Contains(this VnString str, char value) => str.AsSpan().Contains(value); + /// <summary> /// Derermines if the sequence exists within the instance /// </summary> @@ -50,9 +56,10 @@ namespace VNLib.Utils.Extensions /// <param name="stringComparison"></param> /// <returns>True if the character exists within the instance</returns> /// <exception cref="ObjectDisposedException"></exception> - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Contains(this VnString str, ReadOnlySpan<char> value, StringComparison stringComparison) => str.AsSpan().Contains(value, stringComparison); + /// <summary> /// Searches for the first occurrance of the specified character within the current instance /// </summary> @@ -60,7 +67,9 @@ namespace VNLib.Utils.Extensions /// <param name="value">The character to search for within the instance</param> /// <returns>The 0 based index of the occurance, -1 if the character was not found</returns> /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOf(this VnString str, char value) => str.IsEmpty ? -1 : str.AsSpan().IndexOf(value); + /// <summary> /// Searches for the first occurrance of the specified sequence within the current instance /// </summary> @@ -68,12 +77,9 @@ namespace VNLib.Utils.Extensions /// <param name="search">The sequence to search for</param> /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns> /// <exception cref="ObjectDisposedException"></exception> - public static int IndexOf(this VnString str, ReadOnlySpan<char> search) - { - //Using spans to avoid memory leaks... - ReadOnlySpan<char> self = str.AsSpan(); - return self.IndexOf(search); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this VnString str, ReadOnlySpan<char> search) => str.AsSpan().IndexOf(search); + /// <summary> /// Searches for the first occurrance of the specified sequence within the current instance /// </summary> @@ -82,12 +88,9 @@ namespace VNLib.Utils.Extensions /// <param name="comparison">The <see cref="StringComparison"/> type to use in searchr</param> /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns> /// <exception cref="ObjectDisposedException"></exception> - public static int IndexOf(this VnString str, ReadOnlySpan<char> search, StringComparison comparison) - { - //Using spans to avoid memory leaks... - ReadOnlySpan<char> self = str.AsSpan(); - return self.IndexOf(search, comparison); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this VnString str, ReadOnlySpan<char> search, StringComparison comparison) => str.AsSpan().IndexOf(search, comparison); + /// <summary> /// Searches for the 0 based index of the first occurance of the search parameter after the start index. /// </summary> @@ -136,11 +139,13 @@ namespace VNLib.Utils.Extensions /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="IndexOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static VnString AbsoluteTrim(this VnString data, int start, int end) { AbsoluteTrim(data, ref start, ref end); return data[start..end]; } + /// <summary> /// Finds whitespace characters within the sequence defined between start and end parameters /// and adjusts the specified window to "trim" whitespace @@ -175,6 +180,7 @@ namespace VNLib.Utils.Extensions end--; } } + /// <summary> /// Allows for trimming whitespace characters in a realtive sequence from /// within a <see cref="VnString"/> buffer and returning the trimmed entry. @@ -184,13 +190,16 @@ namespace VNLib.Utils.Extensions /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="IndexOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static VnString AbsoluteTrim(this VnString data, int start) => AbsoluteTrim(data, start, data.Length); + /// <summary> /// Trims leading or trailing whitespace characters and returns a new child instance /// without leading or trailing whitespace /// </summary> /// <returns>A child <see cref="VnString"/> of the current instance without leading or trailing whitespaced</returns> /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static VnString RelativeTirm(this VnString data) => AbsoluteTrim(data, 0); /// <summary> @@ -349,19 +358,6 @@ namespace VNLib.Utils.Extensions } /// <summary> - /// Unoptimized character enumerator. You should use <see cref="VnString.AsSpan"/> to enumerate the unerlying data. - /// </summary> - /// <returns>The next character in the sequence</returns> - /// <exception cref="ObjectDisposedException"></exception> - public static IEnumerator<char> GetEnumerator(this VnString data) - { - int index = 0; - while (index < data.Length) - { - yield return data[index++]; - } - } - /// <summary> /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper /// for a memory handle /// </summary> @@ -370,14 +366,9 @@ namespace VNLib.Utils.Extensions /// <returns>The new <see cref="VnString"/> wrapper</returns> /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static VnString ToVnString(this MemoryHandle<char> handle, int length) - { - if(handle.Length > int.MaxValue) - { - throw new OverflowException("The handle is larger than 2GB in size"); - } - return VnString.ConsumeHandle(handle, 0, length); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static VnString ToVnString(this MemoryHandle<char> handle, int length) => VnString.ConsumeHandle(handle, 0, length); + /// <summary> /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper /// for a memory handle @@ -386,10 +377,9 @@ namespace VNLib.Utils.Extensions /// <returns>The new <see cref="VnString"/> wrapper</returns> /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static VnString ToVnString(this MemoryHandle<char> handle) - { - return VnString.ConsumeHandle(handle, 0, handle.IntLength); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static VnString ToVnString(this MemoryHandle<char> handle) => VnString.ConsumeHandle(handle, 0, handle.GetIntLength()); + /// <summary> /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper /// for a memory handle @@ -398,21 +388,8 @@ namespace VNLib.Utils.Extensions /// <param name="offset">The offset in characters that represents the begining of the string</param> /// <param name="length">The number of characters from the handle to reference (length of the string)</param> /// <returns>The new <see cref="VnString"/> wrapper</returns> - /// <exception cref="OverflowException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static VnString ToVnString(this MemoryHandle<char> handle, -#if TARGET_64_BIT - ulong offset, -#else - int offset, -#endif - int length) - { - if (handle.Length > int.MaxValue) - { - throw new OverflowException("The handle is larger than 2GB in size"); - } - return VnString.ConsumeHandle(handle, offset, length); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static VnString ToVnString(this MemoryHandle<char> handle, nuint offset, int length) => VnString.ConsumeHandle(handle, offset, length); } }
\ No newline at end of file diff --git a/lib/Utils/src/IO/InMemoryTemplate.cs b/lib/Utils/src/IO/InMemoryTemplate.cs index ae8bf79..5a4a799 100644 --- a/lib/Utils/src/IO/InMemoryTemplate.cs +++ b/lib/Utils/src/IO/InMemoryTemplate.cs @@ -165,7 +165,7 @@ namespace VNLib.Utils.IO try { //Copy async - await fs.CopyToAsync(newBuf, 8192, Memory.Memory.Shared, cancellationToken); + await fs.CopyToAsync(newBuf, 8192, Memory.MemoryUtil.Shared, cancellationToken); } catch { diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs index 4e8a2b3..e984cc1 100644 --- a/lib/Utils/src/IO/VnMemoryStream.cs +++ b/lib/Utils/src/IO/VnMemoryStream.cs @@ -28,24 +28,23 @@ using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices; +using VNLib.Utils.Memory; using VNLib.Utils.Extensions; namespace VNLib.Utils.IO { - - using Utils.Memory; - /// <summary> /// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for /// high frequency memory operations. Similar to <see cref="UnmanagedMemoryStream"/> /// </summary> public sealed class VnMemoryStream : Stream, ICloneable { - private long _position; - private long _length; + private nint _position; + private nint _length; + private bool _isReadonly; + //Memory private readonly MemoryHandle<byte> _buffer; - private bool IsReadonly; //Default owns handle private readonly bool OwnsHandle = true; @@ -57,7 +56,7 @@ namespace VNLib.Utils.IO /// <param name="readOnly">Should the stream be readonly?</param> /// <exception cref="ArgumentException"></exception> /// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns> - public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, Int64 length, bool readOnly) + public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, nint length, bool readOnly) { handle.ThrowIfClosed(); return new VnMemoryStream(handle, length, readOnly, true); @@ -71,7 +70,7 @@ namespace VNLib.Utils.IO public static VnMemoryStream CreateReadonly(VnMemoryStream stream) { //Set the readonly flag - stream.IsReadonly = true; + stream._isReadonly = true; //Return the stream return stream; } @@ -79,11 +78,12 @@ namespace VNLib.Utils.IO /// <summary> /// Creates a new memory stream /// </summary> - public VnMemoryStream() : this(Memory.Shared) { } + public VnMemoryStream() : this(MemoryUtil.Shared) { } + /// <summary> /// Create a new memory stream where buffers will be allocated from the specified heap /// </summary> - /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { } @@ -92,13 +92,13 @@ namespace VNLib.Utils.IO /// Creates a new memory stream and pre-allocates the internal /// buffer of the specified size on the specified heap to avoid resizing. /// </summary> - /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> /// <param name="bufferSize">Number of bytes (length) of the stream if known</param> /// <param name="zero">Zero memory allocations during buffer expansions</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public VnMemoryStream(IUnmangedHeap heap, long bufferSize, bool zero) + public VnMemoryStream(IUnmangedHeap heap, nuint bufferSize, bool zero) { _ = heap ?? throw new ArgumentNullException(nameof(heap)); _buffer = heap.Alloc<byte>(bufferSize, zero); @@ -107,7 +107,7 @@ namespace VNLib.Utils.IO /// <summary> /// Creates a new memory stream from the data provided /// </summary> - /// <param name="heap"><see cref="PrivateHeap"/> to allocate memory from</param> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> /// <param name="data">Initial data</param> public VnMemoryStream(IUnmangedHeap heap, ReadOnlySpan<byte> data) { @@ -116,8 +116,7 @@ namespace VNLib.Utils.IO _buffer = heap.AllocAndCopy(data); //Set length _length = data.Length; - //Position will default to 0 cuz its dotnet :P - return; + _position = 0; } /// <summary> @@ -127,18 +126,18 @@ namespace VNLib.Utils.IO /// <param name="length">The length property of the stream</param> /// <param name="readOnly">Is the stream readonly (should mostly be true!)</param> /// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param> - private VnMemoryStream(MemoryHandle<byte> buffer, long length, bool readOnly, bool ownsHandle) + private VnMemoryStream(MemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle) { OwnsHandle = ownsHandle; _buffer = buffer; //Consume the handle _length = length; //Store length of the buffer - IsReadonly = readOnly; + _isReadonly = readOnly; } /// <summary> /// UNSAFE Number of bytes between position and length. Never negative /// </summary> - private long LenToPosDiff => Math.Max(_length - _position, 0); + private nint LenToPosDiff => Math.Max(_length - _position, 0); /// <summary> /// If the current stream is a readonly stream, creates an unsafe shallow copy for reading only. @@ -148,7 +147,7 @@ namespace VNLib.Utils.IO public VnMemoryStream GetReadonlyShallowCopy() { //Create a new readonly copy (stream does not own the handle) - return !IsReadonly + return !_isReadonly ? throw new NotSupportedException("This stream is not readonly. Cannot create shallow copy on a mutable stream") : new VnMemoryStream(_buffer, _length, true, false); } @@ -163,6 +162,10 @@ namespace VNLib.Utils.IO public override void CopyTo(Stream destination, int bufferSize) { _ = destination ?? throw new ArgumentNullException(nameof(destination)); + if(bufferSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than 0"); + } if (!destination.CanWrite) { @@ -250,7 +253,7 @@ namespace VNLib.Utils.IO /// True unless the stream is (or has been converted to) a readonly /// stream. /// </summary> - public override bool CanWrite => !IsReadonly; + public override bool CanWrite => !_isReadonly; ///<inheritdoc/> public override long Length => _length; ///<inheritdoc/> @@ -279,7 +282,7 @@ namespace VNLib.Utils.IO ///<inheritdoc/> public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; ///<inheritdoc/> - public override int Read(byte[] buffer, int offset, int count) => Read(new Span<byte>(buffer, offset, count)); + public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count)); ///<inheritdoc/> public override int Read(Span<byte> buffer) { @@ -288,12 +291,14 @@ namespace VNLib.Utils.IO return 0; } //Number of bytes to read from memory buffer - int bytesToRead = checked((int)Math.Min(LenToPosDiff, buffer.Length)); + int bytesToRead = (int)Math.Min(LenToPosDiff, buffer.Length); + //Copy bytes to buffer - Memory.Copy(_buffer, _position, buffer, 0, bytesToRead); + MemoryUtil.Copy(_buffer, _position, buffer, 0, bytesToRead); + //Increment buffer position _position += bytesToRead; - //Bytestoread should never be larger than int.max because span length is an integer + return bytesToRead; } @@ -322,22 +327,27 @@ namespace VNLib.Utils.IO { throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be less than 0"); } + if(offset > nint.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Offset cannot be less than nint.MaxValue"); + } + + //safe cast to nint + nint _offset = (nint)offset; + switch (origin) { case SeekOrigin.Begin: - //Length will never be greater than int.Max so output will never exceed int.max - _position = Math.Min(_length, offset); - return _position; + //Length will never be greater than nint.Max so output will never exceed nint.max + return _position = Math.Min(_length, _offset); case SeekOrigin.Current: - long newPos = _position + offset; - //Length will never be greater than int.Max so output will never exceed length - _position = Math.Min(_length, newPos); - return newPos; + //Calc new seek position from current position + nint newPos = _position + _offset; + return _position = Math.Min(_length, newPos); case SeekOrigin.End: - long real_index = _length - offset; - //If offset moves the position negative, just set the position to 0 and continue - _position = Math.Min(real_index, 0); - return real_index; + //Calc new seek position from end of stream, should be len -1 so 0 can be specified from the end + nint realIndex = _length - (_offset - 1); + return _position = Math.Min(realIndex, 0); default: throw new ArgumentException("Stream operation is not supported on current stream"); } @@ -356,7 +366,7 @@ namespace VNLib.Utils.IO /// <exception cref="ArgumentOutOfRangeException"></exception> public override void SetLength(long value) { - if (IsReadonly) + if (_isReadonly) { throw new NotSupportedException("This stream is readonly"); } @@ -364,25 +374,33 @@ namespace VNLib.Utils.IO { throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be less than 0"); } + if(value > nint.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be greater than nint.MaxValue"); + } + + nint _value = (nint)value; + //Resize the buffer to the specified length - _buffer.Resize(value); + _buffer.Resize(_value); + //Set length - _length = value; - //Make sure the position is not pointing outside of the buffer + _length = _value; + + //Make sure the position is not pointing outside of the buffer after resize _position = Math.Min(_position, _length); - return; } ///<inheritdoc/> - public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count)); + public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count)); ///<inheritdoc/> public override void Write(ReadOnlySpan<byte> buffer) { - if (IsReadonly) + if (_isReadonly) { throw new NotSupportedException("Write operation is not allowed on readonly stream!"); } //Calculate the new final position - long newPos = (_position + buffer.Length); + nint newPos = (_position + buffer.Length); //Determine if the buffer needs to be expanded if (buffer.Length > LenToPosDiff) { @@ -392,10 +410,9 @@ namespace VNLib.Utils.IO _length = newPos; } //Copy the input buffer to the internal buffer - Memory.Copy(buffer, _buffer, _position); + MemoryUtil.Copy(buffer, _buffer, (nuint)_position); //Update the position _position = newPos; - return; } ///<inheritdoc/> public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -423,16 +440,17 @@ namespace VNLib.Utils.IO /// </summary> /// <returns>Copy of internal buffer</returns> /// <exception cref="OutOfMemoryException"></exception> - /// <exception cref="OutOfMemoryException"></exception> public byte[] ToArray() { - //Alloc a new array of the size of the internal buffer + //Alloc a new array of the size of the internal buffer, may be 64 bit large block byte[] data = new byte[_length]; - //Copy data from the internal buffer to the output buffer - _buffer.Span.CopyTo(data); + + //Copy the internal buffer to the new array + MemoryUtil.Copy(_buffer, 0, data, 0, (nuint)_length); + return data; - } + /// <summary> /// Returns a <see cref="ReadOnlySpan{T}"/> window over the data within the entire stream /// </summary> @@ -440,8 +458,10 @@ namespace VNLib.Utils.IO /// <exception cref="OverflowException"></exception> public ReadOnlySpan<byte> AsSpan() { - ReadOnlySpan<byte> output = _buffer.Span; - return output[..(int)_length]; + //Get 32bit length or throw + int len = Convert.ToInt32(_length); + //Get span with no offset + return _buffer.AsSpan(0, len); } /// <summary> diff --git a/lib/Utils/src/Memory/IMemoryHandle.cs b/lib/Utils/src/Memory/IMemoryHandle.cs index 75d1cce..cf19ce9 100644 --- a/lib/Utils/src/Memory/IMemoryHandle.cs +++ b/lib/Utils/src/Memory/IMemoryHandle.cs @@ -34,15 +34,9 @@ namespace VNLib.Utils.Memory public interface IMemoryHandle<T> : IDisposable, IPinnable { /// <summary> - /// The size of the block as an integer - /// </summary> - /// <exception cref="OverflowException"></exception> - int IntLength { get; } - - /// <summary> /// The number of elements in the block /// </summary> - ulong Length { get; } + nuint Length { get; } /// <summary> /// Gets the internal block as a span diff --git a/lib/Utils/src/Memory/IUnmangedHeap.cs b/lib/Utils/src/Memory/IUnmangedHeap.cs index 5d8f4bf..94f34c8 100644 --- a/lib/Utils/src/Memory/IUnmangedHeap.cs +++ b/lib/Utils/src/Memory/IUnmangedHeap.cs @@ -38,7 +38,7 @@ 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> - IntPtr Alloc(UInt64 elements, UInt64 size, bool zero); + IntPtr Alloc(nuint elements, nuint size, bool zero); /// <summary> /// Resizes the allocated block of memory to the new size @@ -47,7 +47,7 @@ namespace VNLib.Utils.Memory /// <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> - void Resize(ref IntPtr block, UInt64 elements, UInt64 size, bool zero); + void Resize(ref IntPtr block, nuint elements, nuint size, bool zero); /// <summary> /// Free's a previously allocated block of memory diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index a09edea..df2792b 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -34,7 +34,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Utils.Memory { /// <summary> - /// Provides a wrapper for using umanged memory handles from an assigned <see cref="PrivateHeap"/> for <see cref="UnmanagedType"/> types + /// Provides a wrapper for using umanged memory handles from an assigned <see cref="Win32PrivateHeap"/> for <see cref="UnmanagedType"/> types /// </summary> /// <remarks> /// Handles are configured to address blocks larger than 2GB, @@ -72,31 +72,21 @@ namespace VNLib.Utils.Memory get { this.ThrowIfClosed(); - return _length == 0 ? Span<T>.Empty : new Span<T>(Base, IntLength); + int len = Convert.ToInt32(_length); + return _length == 0 ? Span<T>.Empty : new Span<T>(Base, len); } } private readonly bool ZeroMemory; private readonly IUnmangedHeap Heap; - private ulong _length; + private nuint _length; - /// <summary> - /// Number of elements allocated to the current instance - /// </summary> - public ulong Length + ///<inheritdoc/> + public nuint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _length; } - /// <summary> - /// Number of elements in the memory block casted to an integer - /// </summary> - /// <exception cref="OverflowException"></exception> - public int IntLength - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => checked((int)_length); - } /// <summary> /// Number of bytes allocated to the current instance @@ -106,7 +96,7 @@ namespace VNLib.Utils.Memory { //Check for overflows when converting to bytes (should run out of memory before this is an issue, but just incase) [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => checked(_length * (UInt64)sizeof(T)); + get => MemoryUtil.ByteCount<T>(_length); } /// <summary> @@ -116,7 +106,7 @@ namespace VNLib.Utils.Memory /// <param name="elements">Number of elements to allocate</param> /// <param name="zero">Zero all memory during allocations from heap</param> /// <param name="initial">The initial block of allocated memory to wrap</param> - internal MemoryHandle(IUnmangedHeap heap, IntPtr initial, ulong elements, bool zero) : base(true) + internal MemoryHandle(IUnmangedHeap heap, IntPtr initial, nuint elements, bool zero) : base(true) { //Set element size (always allocate at least 1 object) _length = elements; @@ -133,7 +123,7 @@ namespace VNLib.Utils.Memory /// <exception cref="OverflowException"></exception> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> - public unsafe void Resize(ulong elements) + public unsafe void Resize(nuint elements) { this.ThrowIfClosed(); //Update size (should never be less than inital size) @@ -141,7 +131,7 @@ namespace VNLib.Utils.Memory //Re-alloc (Zero if required) try { - Heap.Resize(ref handle, Length, (ulong)sizeof(T), ZeroMemory); + Heap.Resize(ref handle, Length, (nuint)sizeof(T), ZeroMemory); } //Catch the disposed exception so we can invalidate the current ptr catch (ObjectDisposedException) @@ -161,7 +151,7 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentOutOfRangeException"></exception> /// <returns><typeparamref name="T"/> pointer to the memory offset specified</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe T* GetOffset(ulong elements) + public unsafe T* GetOffset(nuint elements) { if (elements >= _length) { @@ -176,10 +166,14 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> ///<exception cref="ObjectDisposedException"></exception> ///<exception cref="ArgumentOutOfRangeException"></exception> + ///<remarks> + ///Calling this method increments the handle's referrence count. + ///Disposing the returned handle decrements the handle count. + ///</remarks> public unsafe MemoryHandle Pin(int elementIndex) { //Get ptr and guard checks before adding the referrence - T* ptr = GetOffset((ulong)elementIndex); + T* ptr = GetOffset((nuint)elementIndex); bool addRef = false; //use the pinned field as success val @@ -198,15 +192,12 @@ namespace VNLib.Utils.Memory DangerousRelease(); } - ///<inheritdoc/> protected override bool ReleaseHandle() { //Return result of free return Heap.Free(ref handle); - } - - + } /// <summary> /// Determines if the memory blocks are equal by comparing their base addresses. diff --git a/lib/Utils/src/Memory/Memory.cs b/lib/Utils/src/Memory/MemoryUtil.cs index e04c386..410db6b 100644 --- a/lib/Utils/src/Memory/Memory.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -23,7 +23,6 @@ */ using System; -using System.IO; using System.Buffers; using System.Security; using System.Threading; @@ -39,7 +38,7 @@ namespace VNLib.Utils.Memory /// </summary> [SecurityCritical] [ComVisible(false)] - public unsafe static class Memory + public unsafe static class MemoryUtil { public const string SHARED_HEAP_TYPE_ENV= "VNLIB_SHARED_HEAP_TYPE"; public const string SHARED_HEAP_INTIAL_SIZE_ENV = "VNLIB_SHARED_HEAP_SIZE"; @@ -47,7 +46,7 @@ namespace VNLib.Utils.Memory /// <summary> /// Initial shared heap size (bytes) /// </summary> - public const ulong SHARED_HEAP_INIT_SIZE = 20971520; + public const nuint SHARED_HEAP_INIT_SIZE = 20971520; public const int MAX_BUF_SIZE = 2097152; public const int MIN_BUF_SIZE = 16000; @@ -68,14 +67,18 @@ namespace VNLib.Utils.Memory /// </remarks> public static IUnmangedHeap Shared => _sharedHeap.Value; - private static readonly Lazy<IUnmangedHeap> _sharedHeap; + + private static readonly Lazy<IUnmangedHeap> _sharedHeap = InitHeapInternal(); - static Memory() + //Avoiding statit initializer + private static Lazy<IUnmangedHeap> InitHeapInternal() { - _sharedHeap = new Lazy<IUnmangedHeap>(() => InitHeapInternal(true), LazyThreadSafetyMode.PublicationOnly); + Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true), LazyThreadSafetyMode.PublicationOnly); //Cleanup the heap on process exit AppDomain.CurrentDomain.DomainUnload += DomainUnloaded; + return heap; } + private static void DomainUnloaded(object? sender, EventArgs e) { @@ -99,11 +102,11 @@ namespace VNLib.Utils.Memory { bool IsWindows = OperatingSystem.IsWindows(); //Get environment varable - string heapType = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV); + string? heapType = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV); //Get inital size - string sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV); + string? sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV); //Try to parse the shared size from the env - if (!ulong.TryParse(sharedSize, out ulong defaultSize)) + if (!nuint.TryParse(sharedSize, out nuint defaultSize)) { defaultSize = SHARED_HEAP_INIT_SIZE; } @@ -115,12 +118,12 @@ namespace VNLib.Utils.Memory { throw new PlatformNotSupportedException("Win32 private heaps are not supported on non-windows platforms"); } - return PrivateHeap.Create(defaultSize); + return Win32PrivateHeap.Create(defaultSize); case "rpmalloc": //If the shared heap is being allocated, then return a lock free global heap return isShared ? RpMallocPrivateHeap.GlobalHeap : new RpMallocPrivateHeap(false); default: - return IsWindows ? PrivateHeap.Create(defaultSize) : new ProcessHeap(); + return IsWindows ? Win32PrivateHeap.Create(defaultSize) : new ProcessHeap(); } } @@ -130,6 +133,7 @@ namespace VNLib.Utils.Memory public static bool IsRpMallocLoaded { get; } = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV) == "rpmalloc"; #region Zero + /// <summary> /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// </summary> @@ -150,6 +154,7 @@ namespace VNLib.Utils.Memory } } } + /// <summary> /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// </summary> @@ -175,12 +180,15 @@ namespace VNLib.Utils.Memory /// </summary> /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitializeBlock<T>(Span<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block); + /// <summary> /// Initializes a block of memory with zeros /// </summary> /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitializeBlock<T>(Memory<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block); /// <summary> @@ -195,6 +203,7 @@ namespace VNLib.Utils.Memory //Zero block Unsafe.InitBlock(block.ToPointer(), 0, (uint)size); } + /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> @@ -207,6 +216,7 @@ namespace VNLib.Utils.Memory //Zero block Unsafe.InitBlock(structPtr, 0, (uint)size); } + /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> @@ -223,6 +233,7 @@ namespace VNLib.Utils.Memory #endregion #region Copy + /// <summary> /// Copies data from source memory to destination memory of an umanged data type /// </summary> @@ -231,24 +242,25 @@ namespace VNLib.Utils.Memory /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param> /// <param name="destOffset">Dest offset</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(ReadOnlySpan<T> source, MemoryHandle<T> dest, Int64 destOffset) where T : unmanaged + public static void Copy<T>(ReadOnlySpan<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged { - if (source.IsEmpty) - { - return; - } - if (dest.Length < (ulong)(destOffset + source.Length)) + if (dest is null) { - throw new ArgumentException("Source data is larger than the dest data block", nameof(source)); + throw new ArgumentNullException(nameof(dest)); } - //Get long offset from the destination handle - T* offset = dest.GetOffset(destOffset); - fixed(void* src = &MemoryMarshal.GetReference(source)) + + if (source.IsEmpty) { - int byteCount = checked(source.Length * sizeof(T)); - Unsafe.CopyBlock(offset, src, (uint)byteCount); + return; } + + //Get long offset from the destination handle (also checks bounds) + Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length); + + //Copy data + source.CopyTo(dst); } + /// <summary> /// Copies data from source memory to destination memory of an umanged data type /// </summary> @@ -257,24 +269,25 @@ namespace VNLib.Utils.Memory /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param> /// <param name="destOffset">Dest offset</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(ReadOnlyMemory<T> source, MemoryHandle<T> dest, Int64 destOffset) where T : unmanaged + public static void Copy<T>(ReadOnlyMemory<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged { - if (source.IsEmpty) + if (dest is null) { - return; + throw new ArgumentNullException(nameof(dest)); } - if (dest.Length < (ulong)(destOffset + source.Length)) + + if (source.IsEmpty) { - throw new ArgumentException("Dest constraints are larger than the dest data block", nameof(source)); + return; } - //Get long offset from the destination handle - T* offset = dest.GetOffset(destOffset); - //Pin the source memory - using MemoryHandle srcHandle = source.Pin(); - int byteCount = checked(source.Length * sizeof(T)); - //Copy block using unsafe class - Unsafe.CopyBlock(offset, srcHandle.Pointer, (uint)byteCount); + + //Get long offset from the destination handle (also checks bounds) + Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length); + + //Copy data + source.Span.CopyTo(dst); } + /// <summary> /// Copies data from source memory to destination memory of an umanged data type /// </summary> @@ -285,31 +298,27 @@ namespace VNLib.Utils.Memory /// <param name="destOffset">Dest offset</param> /// <param name="count">Number of elements to copy</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(MemoryHandle<T> source, Int64 sourceOffset, Span<T> dest, int destOffset, int count) where T : unmanaged + public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Span<T> dest, int destOffset, int count) where T : unmanaged { - if (count <= 0) + //Validate source/dest/count + ValidateArgs(sourceOffset, destOffset, count); + + //Check count last for debug reasons + if (count == 0) { return; } - if (source.Length < (ulong)(sourceOffset + count)) - { - throw new ArgumentException("Source constraints are larger than the source data block", nameof(count)); - } - if (dest.Length < destOffset + count) - { - throw new ArgumentOutOfRangeException(nameof(destOffset), "Destination offset range cannot exceed the size of the destination buffer"); - } - //Get offset to allow large blocks of memory - T* src = source.GetOffset(sourceOffset); - fixed(T* dst = &MemoryMarshal.GetReference(dest)) - { - //Cacl offset - T* dstoffset = dst + destOffset; - int byteCount = checked(count * sizeof(T)); - //Aligned copy - Unsafe.CopyBlock(dstoffset, src, (uint)byteCount); - } + + //Get offset span, also checks bounts + Span<T> src = source.GetOffsetSpan(sourceOffset, count); + + //slice the dest span + Span<T> dst = dest.Slice(destOffset, count); + + //Copy data + src.CopyTo(dst); } + /// <summary> /// Copies data from source memory to destination memory of an umanged data type /// </summary> @@ -319,77 +328,214 @@ namespace VNLib.Utils.Memory /// <param name="dest">Destination <see cref="Memory{T}"/></param> /// <param name="destOffset">Dest offset</param> /// <param name="count">Number of elements to copy</param> + /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(MemoryHandle<T> source, Int64 sourceOffset, Memory<T> dest, int destOffset, int count) where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Memory<T> dest, int destOffset, int count) where T : unmanaged { - if (count == 0) + //Call copy method with dest as span + Copy(source, sourceOffset, dest.Span, destOffset, count); + } + + private static void ValidateArgs(nint sourceOffset, nint destOffset, nint count) + { + if(sourceOffset < 0) { - return; + throw new ArgumentOutOfRangeException(nameof(sourceOffset), "Source offset must be a postive integer"); } - if (source.Length < (ulong)(sourceOffset + count)) + + if(destOffset < 0) { - throw new ArgumentException("Source constraints are larger than the source data block", nameof(count)); + throw new ArgumentOutOfRangeException(nameof(destOffset), "Destination offset must be a positive integer"); } - if(dest.Length < destOffset + count) + + if(count < 0) { - throw new ArgumentOutOfRangeException(nameof(destOffset), "Destination offset range cannot exceed the size of the destination buffer"); + throw new ArgumentOutOfRangeException(nameof(count), "Count parameter must be a postitive integer"); } - //Get offset to allow large blocks of memory - T* src = source.GetOffset(sourceOffset); - //Pin the memory handle - using MemoryHandle handle = dest.Pin(); - //Byte count - int byteCount = checked(count * sizeof(T)); - //Dest offset - T* dst = ((T*)handle.Pointer) + destOffset; - //Aligned copy - Unsafe.CopyBlock(dst, src, (uint)byteCount); } - #endregion - #region Streams /// <summary> - /// Copies data from one stream to another in specified blocks + /// 32/64 bit large block copy /// </summary> - /// <param name="source">Source memory</param> - /// <param name="srcOffset">Source offset</param> - /// <param name="dest">Destination memory</param> - /// <param name="destOffst">Destination offset</param> - /// <param name="count">Number of elements to copy</param> - public static void Copy(Stream source, Int64 srcOffset, Stream dest, Int64 destOffst, Int64 count) + /// <typeparam name="T"></typeparam> + /// <param name="source">The source memory handle to copy data from</param> + /// <param name="offset">The element offset to begin reading from</param> + /// <param name="dest">The destination array to write data to</param> + /// <param name="destOffset"></param> + /// <param name="count">The number of elements to copy</param> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public static void Copy<T>(IMemoryHandle<T> source, nuint offset, T[] dest, nuint destOffset, nuint count) where T : unmanaged { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dest is null) + { + throw new ArgumentNullException(nameof(dest)); + } + if (count == 0) { return; } - if (count < 0) + + //Check source bounds + CheckBounds(source, offset, count); + + //Check dest bounts + CheckBounds(dest, destOffset, count); + + +#if TARGET_64BIT + //Get the number of bytes to copy + nuint byteCount = ByteCount<T>(count); + + //Get memory handle from source + using MemoryHandle srcHandle = source.Pin(0); + + //get source offset + T* src = (T*)srcHandle.Pointer + offset; + + //pin array + fixed (T* dst = dest) + { + //Offset dest ptr + T* dstOffset = dst + destOffset; + + //Copy src to set + Buffer.MemoryCopy(src, dstOffset, byteCount, byteCount); + } +#else + //If 32bit its safe to use spans + + Span<T> src = source.Span.Slice((int)offset, (int)count); + Span<T> dst = dest.AsSpan((int)destOffset, (int)count); + //Copy + src.CopyTo(dst); +#endif + } + + #endregion + + #region Validation + + /// <summary> + /// Gets the size in bytes of the handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="handle">The handle to get the byte size of</param> + /// <returns>The number of bytes pointed to by the handle</returns> + /// <exception cref="ArgumentNullException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint ByteSize<T>(IMemoryHandle<T> handle) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + return checked(handle.Length * (nuint)Unsafe.SizeOf<T>()); + } + + /// <summary> + /// Gets the size in bytes of the handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="handle">The handle to get the byte size of</param> + /// <returns>The number of bytes pointed to by the handle</returns> + /// <exception cref="ArgumentNullException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint ByteSize<T>(in UnsafeMemoryHandle<T> handle) where T : unmanaged => checked(handle.Length * (nuint)sizeof(T)); + + /// <summary> + /// Gets the byte multiple of the length parameter + /// </summary> + /// <typeparam name="T">The type to get the byte offset of</typeparam> + /// <param name="elementCount">The number of elements to get the byte count of</param> + /// <returns>The byte multiple of the number of elments</returns> + /// <exception cref="OverflowException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint ByteCount<T>(nuint elementCount) => checked(elementCount * (nuint)Unsafe.SizeOf<T>()); + /// <summary> + /// Gets the byte multiple of the length parameter + /// </summary> + /// <typeparam name="T">The type to get the byte offset of</typeparam> + /// <param name="elementCount">The number of elements to get the byte count of</param> + /// <returns>The byte multiple of the number of elments</returns> + /// <exception cref="OverflowException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ByteCount<T>(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf<T>()); + + /// <summary> + /// Checks if the offset/count paramters for the given memory handle + /// point outside the block wrapped in the handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="handle">The handle to check bounds of</param> + /// <param name="offset">The base offset to add</param> + /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckBounds<T>(IMemoryHandle<T> handle, nuint offset, nuint count) + { + if (offset + count > handle.Length) { - throw new ArgumentException("Count must be a positive integer", nameof(count)); + throw new ArgumentException("The offset or count is outside of the range of the block of memory"); } - //Seek streams - _ = source.Seek(srcOffset, SeekOrigin.Begin); - _ = dest.Seek(destOffst, SeekOrigin.Begin); - //Create new buffer - using IMemoryHandle<byte> buffer = Shared.Alloc<byte>(count); - Span<byte> buf = buffer.Span; - int total = 0; - do + } + + /// <summary> + /// Checks if the offset/count paramters for the given block + /// point outside the block wrapped in the handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="block">The handle to check bounds of</param> + /// <param name="offset">The base offset to add</param> + /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckBounds<T>(ReadOnlySpan<T> block, int offset, int count) + { + //Call slice and discard to raise exception + _ = block.Slice(offset, count); + } + + /// <summary> + /// Checks if the offset/count paramters for the given block + /// point outside the block wrapped in the handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="block">The handle to check bounds of</param> + /// <param name="offset">The base offset to add</param> + /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckBounds<T>(Span<T> block, int offset, int count) + { + //Call slice and discard to raise exception + _ = block.Slice(offset, count); + } + + /// <summary> + /// Checks if the offset/count paramters for the given block + /// point outside the block bounds + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="block">The handle to check bounds of</param> + /// <param name="offset">The base offset to add</param> + /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckBounds<T>(T[] block, nuint offset, nuint count) + { + if (((nuint)block.LongLength - offset) <= count) { - //read from source - int read = source.Read(buf); - //guard - if (read == 0) - { - break; - } - //write read slice to dest - dest.Write(buf[..read]); - //update total read - total += read; - } while (total < count); + throw new ArgumentException("The offset or count is outside of the range of the block of memory"); + } } + #endregion + #region alloc /// <summary> @@ -408,6 +554,7 @@ namespace VNLib.Utils.Memory { throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } + if(elements > MAX_UNSAFE_POOL_SIZE || IsRpMallocLoaded) { // Alloc from heap diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs index 1e85207..e73a26f 100644 --- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs +++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs @@ -28,7 +28,7 @@ using System.Buffers; namespace VNLib.Utils.Memory { /// <summary> - /// Provides a <see cref="MemoryPool{T}"/> wrapper for using unmanged <see cref="PrivateHeap"/>s + /// Provides a <see cref="MemoryPool{T}"/> wrapper for using unmanged <see cref="Win32PrivateHeap"/>s /// </summary> /// <typeparam name="T">Unamanged memory type to provide data memory instances from</typeparam> public sealed class PrivateBuffersMemoryPool<T> : MemoryPool<T> where T : unmanaged diff --git a/lib/Utils/src/Memory/PrivateStringManager.cs b/lib/Utils/src/Memory/PrivateStringManager.cs index 9ed8f5f..8f01e98 100644 --- a/lib/Utils/src/Memory/PrivateStringManager.cs +++ b/lib/Utils/src/Memory/PrivateStringManager.cs @@ -63,7 +63,7 @@ namespace VNLib.Utils.Memory //Clear the old value before setting the new one if (!string.IsNullOrEmpty(ProtectedElements[index])) { - Memory.UnsafeZeroMemory<char>(ProtectedElements[index]); + MemoryUtil.UnsafeZeroMemory<char>(ProtectedElements[index]); } //set new value ProtectedElements[index] = value; @@ -87,7 +87,7 @@ namespace VNLib.Utils.Memory if (!string.IsNullOrEmpty(ProtectedElements[i])) { //Zero the string memory - Memory.UnsafeZeroMemory<char>(ProtectedElements[i]); + MemoryUtil.UnsafeZeroMemory<char>(ProtectedElements[i]); //Set to null ProtectedElements[i] = null; } diff --git a/lib/Utils/src/Memory/ProcessHeap.cs b/lib/Utils/src/Memory/ProcessHeap.cs index 4f06d52..7afe4b1 100644 --- a/lib/Utils/src/Memory/ProcessHeap.cs +++ b/lib/Utils/src/Memory/ProcessHeap.cs @@ -48,20 +48,23 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> ///<exception cref="OverflowException"></exception> ///<exception cref="OutOfMemoryException"></exception> - public IntPtr Alloc(ulong elements, ulong size, bool zero) + public IntPtr Alloc(nuint elements, nuint size, bool zero) { return zero - ? (IntPtr)NativeMemory.AllocZeroed((nuint)elements, (nuint)size) - : (IntPtr)NativeMemory.Alloc((nuint)elements, (nuint)size); + ? (IntPtr)NativeMemory.AllocZeroed(elements, size) + : (IntPtr)NativeMemory.Alloc(elements, size); } ///<inheritdoc/> public bool Free(ref IntPtr block) { //Free native mem from ptr NativeMemory.Free(block.ToPointer()); + block = IntPtr.Zero; + return true; } + ///<inheritdoc/> protected override void Free() { @@ -69,14 +72,25 @@ namespace VNLib.Utils.Memory Trace.WriteLine($"Default heap instnace disposed {GetHashCode():x}"); #endif } + ///<inheritdoc/> ///<exception cref="OverflowException"></exception> ///<exception cref="OutOfMemoryException"></exception> - public void Resize(ref IntPtr block, ulong elements, ulong size, bool zero) + public void Resize(ref IntPtr block, nuint elements, nuint size, bool zero) { - nuint bytes = checked((nuint)(elements * size)); - IntPtr old = block; - block = (IntPtr)NativeMemory.Realloc(old.ToPointer(), bytes); + nuint bytes = checked(elements * size); + + //Alloc + void* newBlock = NativeMemory.Realloc(block.ToPointer(), bytes); + + //Check + if (newBlock == null) + { + throw new NativeMemoryOutOfMemoryException("Failed to resize the allocated block"); + } + + //Assign block ptr + block = (IntPtr)newBlock; } } } diff --git a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs b/lib/Utils/src/Memory/RpMallocPrivateHeap.cs index 8ed79b6..f9b7db6 100644 --- a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs +++ b/lib/Utils/src/Memory/RpMallocPrivateHeap.cs @@ -28,7 +28,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; -using size_t = System.UInt64; using LPVOID = System.IntPtr; using LPHEAPHANDLE = System.IntPtr; @@ -59,22 +58,22 @@ namespace VNLib.Utils.Memory static extern void rpmalloc_heap_release(LPHEAPHANDLE heap); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_alloc(LPHEAPHANDLE heap, size_t size); + static extern LPVOID rpmalloc_heap_alloc(LPHEAPHANDLE heap, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_alloc(LPHEAPHANDLE heap, size_t alignment, size_t size); + static extern LPVOID rpmalloc_heap_aligned_alloc(LPHEAPHANDLE heap, nuint alignment, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_calloc(LPHEAPHANDLE heap, size_t num, size_t size); + static extern LPVOID rpmalloc_heap_calloc(LPHEAPHANDLE heap, nuint num, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_calloc(LPHEAPHANDLE heap, size_t alignment, size_t num, size_t size); + static extern LPVOID rpmalloc_heap_aligned_calloc(LPHEAPHANDLE heap, nuint alignment, nuint num, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_realloc(LPHEAPHANDLE heap, LPVOID ptr, size_t size, nuint flags); + static extern LPVOID rpmalloc_heap_realloc(LPHEAPHANDLE heap, LPVOID ptr, nuint size, nuint flags); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_realloc(LPHEAPHANDLE heap, LPVOID ptr, size_t alignment, size_t size, nuint flags); + static extern LPVOID rpmalloc_heap_aligned_realloc(LPHEAPHANDLE heap, LPVOID ptr, nuint alignment, nuint size, nuint flags); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] static extern void rpmalloc_heap_free(LPHEAPHANDLE heap, LPVOID ptr); @@ -96,13 +95,13 @@ namespace VNLib.Utils.Memory static extern void rpmalloc_thread_finalize(int release_caches); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc(size_t size); + static extern LPVOID rpmalloc(nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpcalloc(size_t num, size_t size); + static extern LPVOID rpcalloc(nuint num, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rprealloc(LPVOID ptr, size_t size); + static extern LPVOID rprealloc(LPVOID ptr, nuint size); [DllImport(DLL_NAME, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] static extern void rpfree(LPVOID ptr); @@ -111,9 +110,9 @@ namespace VNLib.Utils.Memory private sealed class RpMallocGlobalHeap : IUnmangedHeap { - IntPtr IUnmangedHeap.Alloc(ulong elements, ulong size, bool zero) + IntPtr IUnmangedHeap.Alloc(nuint elements, nuint size, bool zero) { - return RpMalloc(elements, (nuint)size, zero); + return RpMalloc(elements, size, zero); } //Global heap does not need to be disposed @@ -127,17 +126,13 @@ namespace VNLib.Utils.Memory return true; } - void IUnmangedHeap.Resize(ref IntPtr block, ulong elements, ulong size, bool zero) + void IUnmangedHeap.Resize(ref IntPtr block, nuint elements, nuint size, bool zero) { //Try to resize the block - IntPtr resize = RpRealloc(block, elements, (nuint)size); - - if (resize == IntPtr.Zero) - { - throw new NativeMemoryOutOfMemoryException("Failed to resize the block"); - } + IntPtr resize = RpRealloc(block, elements, size); + //assign ptr - block = resize; + block = resize != IntPtr.Zero ? resize : throw new NativeMemoryOutOfMemoryException("Failed to resize the block"); } } @@ -164,7 +159,7 @@ namespace VNLib.Utils.Memory /// <param name="size">The number of bytes per element type (aligment)</param> /// <param name="zero">Zero the block of memory before returning</param> /// <returns>A pointer to the block, (zero if failed)</returns> - public static LPVOID RpMalloc(size_t elements, nuint size, bool zero) + public static LPVOID RpMalloc(nuint elements, nuint size, bool zero) { //See if the current thread has been initialized if (rpmalloc_is_thread_initialized() == 0) @@ -172,8 +167,10 @@ namespace VNLib.Utils.Memory //Initialize the current thread rpmalloc_thread_initialize(); } + //Alloc block LPVOID block; + if (zero) { block = rpcalloc(elements, size); @@ -181,7 +178,8 @@ namespace VNLib.Utils.Memory else { //Calculate the block size - ulong blockSize = checked(elements * size); + nuint blockSize = checked(elements * size); + block = rpmalloc(blockSize); } return block; @@ -209,14 +207,16 @@ namespace VNLib.Utils.Memory /// <returns>A pointer to the new block if the reallocation succeeded, null if the resize failed</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OverflowException"></exception> - public static LPVOID RpRealloc(LPVOID block, size_t elements, nuint size) + public static LPVOID RpRealloc(LPVOID block, nuint elements, nuint size) { if(block == IntPtr.Zero) { throw new ArgumentException("The supplied block is not valid", nameof(block)); } + //Calc new block size - size_t blockSize = checked(elements * size); + nuint blockSize = checked(elements * size); + return rprealloc(block, blockSize); } @@ -239,6 +239,7 @@ namespace VNLib.Utils.Memory Trace.WriteLine($"RPMalloc heap {handle:x} created"); #endif } + ///<inheritdoc/> protected override bool ReleaseHandle() { @@ -252,13 +253,15 @@ namespace VNLib.Utils.Memory //Release base return base.ReleaseHandle(); } + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected sealed override LPVOID AllocBlock(ulong elements, ulong size, bool zero) + protected sealed override LPVOID AllocBlock(nuint elements, nuint size, bool zero) { //Alloc or calloc and initalize return zero ? rpmalloc_heap_calloc(handle, elements, size) : rpmalloc_heap_alloc(handle, checked(size * elements)); } + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] protected sealed override bool FreeBlock(LPVOID block) @@ -267,13 +270,15 @@ namespace VNLib.Utils.Memory rpmalloc_heap_free(handle, block); return true; } + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected sealed override LPVOID ReAllocBlock(LPVOID block, ulong elements, ulong size, bool zero) + protected sealed override LPVOID ReAllocBlock(LPVOID block, nuint elements, nuint size, bool zero) { //Realloc return rpmalloc_heap_realloc(handle, block, checked(elements * size), 0); } + #endregion } } diff --git a/lib/Utils/src/Memory/SubSequence.cs b/lib/Utils/src/Memory/SubSequence.cs index 3800fb5..87e369b 100644 --- a/lib/Utils/src/Memory/SubSequence.cs +++ b/lib/Utils/src/Memory/SubSequence.cs @@ -35,10 +35,7 @@ namespace VNLib.Utils.Memory public readonly struct SubSequence<T> : IEquatable<SubSequence<T>> where T: unmanaged { private readonly MemoryHandle<T> _handle; - /// <summary> - /// The number of elements in the current window - /// </summary> - public readonly int Size { get; } + private readonly nuint _offset; /// <summary> /// Creates a new <see cref="SubSequence{T}"/> to the handle to get a window of the block @@ -46,33 +43,23 @@ namespace VNLib.Utils.Memory /// <param name="block"></param> /// <param name="offset"></param> /// <param name="size"></param> -#if TARGET_64_BIT - public SubSequence(MemoryHandle<T> block, ulong offset, int size) -#else - public SubSequence(MemoryHandle<T> block, int offset, int size) -#endif + public SubSequence(MemoryHandle<T> block, nuint offset, int size) { _offset = offset; Size = size >= 0 ? size : throw new ArgumentOutOfRangeException(nameof(size)); _handle = block ?? throw new ArgumentNullException(nameof(block)); } + /// <summary> + /// The number of elements in the current window + /// </summary> + public readonly int Size { get; } -#if TARGET_64_BIT - private readonly ulong _offset; -#else - private readonly int _offset; -#endif /// <summary> /// Gets a <see cref="Span{T}"/> that is offset from the base of the handle /// </summary> /// <exception cref="ArgumentOutOfRangeException"></exception> - -#if TARGET_64_BIT - public readonly Span<T> Span => Size > 0 ? _handle.GetOffsetSpan(_offset, Size) : Span<T>.Empty; -#else - public readonly Span<T> Span => Size > 0 ? _handle.Span.Slice(_offset, Size) : Span<T>.Empty; -#endif + public readonly Span<T> Span => Size > 0 ? _handle.GetOffsetSpan(_offset, Size) : Span<T>.Empty; /// <summary> /// Slices the current sequence into a smaller <see cref="SubSequence{T}"/> @@ -80,7 +67,7 @@ namespace VNLib.Utils.Memory /// <param name="offset">The relative offset from the current window offset</param> /// <param name="size">The size of the block</param> /// <returns>A <see cref="SubSequence{T}"/> of the current sequence</returns> - public readonly SubSequence<T> Slice(uint offset, int size) => new (_handle, _offset + checked((int)offset), size); + public readonly SubSequence<T> Slice(nuint offset, int size) => new (_handle, checked(_offset + offset), size); /// <summary> /// Returns the signed 32-bit hashcode diff --git a/lib/Utils/src/Memory/SysBufferMemoryManager.cs b/lib/Utils/src/Memory/SysBufferMemoryManager.cs index 040467f..aca2543 100644 --- a/lib/Utils/src/Memory/SysBufferMemoryManager.cs +++ b/lib/Utils/src/Memory/SysBufferMemoryManager.cs @@ -40,7 +40,7 @@ namespace VNLib.Utils.Memory private readonly bool _ownsHandle; /// <summary> - /// Consumes an exisitng <see cref="MemoryHandle{T}"/> to provide <see cref="Memory"/> wrappers. + /// Consumes an exisitng <see cref="MemoryHandle{T}"/> to provide <see cref="MemoryUtil"/> wrappers. /// The handle should no longer be referrenced directly /// </summary> /// <param name="existingHandle">The existing handle to consume</param> @@ -52,12 +52,12 @@ namespace VNLib.Utils.Memory } /// <summary> - /// Allocates a fized size buffer from the specified unmanaged <see cref="PrivateHeap"/> + /// Allocates a fized size buffer from the specified unmanaged <see cref="Win32PrivateHeap"/> /// </summary> /// <param name="heap">The heap to perform allocations from</param> /// <param name="elements">The number of elements to allocate</param> /// <param name="zero">Zero allocations</param> - public SysBufferMemoryManager(IUnmangedHeap heap, ulong elements, bool zero) + public SysBufferMemoryManager(IUnmangedHeap heap, nuint elements, bool zero) { BackingMemory = heap.Alloc<T>(elements, zero); _ownsHandle = true; diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 5c92aff..1f7dc7f 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -28,7 +28,6 @@ using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; -using size_t = System.UInt64; using LPVOID = System.IntPtr; namespace VNLib.Utils.Memory @@ -43,6 +42,7 @@ namespace VNLib.Utils.Memory /// The heap synchronization handle /// </summary> protected readonly SemaphoreSlim HeapLock; + /// <summary> /// The global heap zero flag /// </summary> @@ -63,7 +63,7 @@ namespace VNLib.Utils.Memory ///<remarks>Increments the handle count</remarks> ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> - public LPVOID Alloc(size_t elements, size_t size, bool zero) + public LPVOID Alloc(nuint elements, nuint size, bool zero) { //Force zero if global flag is set zero |= GlobalZero; @@ -93,6 +93,7 @@ namespace VNLib.Utils.Memory throw; } } + ///<inheritdoc/> ///<remarks>Decrements the handle count</remarks> public bool Free(ref LPVOID block) @@ -116,10 +117,11 @@ namespace VNLib.Utils.Memory block = IntPtr.Zero; return result; } + ///<inheritdoc/> ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> - public void Resize(ref LPVOID block, size_t elements, size_t size, bool zero) + public void Resize(ref LPVOID block, nuint elements, nuint size, bool zero) { //wait for lock HeapLock.Wait(); @@ -155,12 +157,14 @@ namespace VNLib.Utils.Memory /// <param name="size">The size of the element type (in bytes)</param> /// <param name="zero">A flag to zero the allocated block</param> /// <returns>A pointer to the allocated block</returns> - protected abstract LPVOID AllocBlock(size_t elements, size_t size, bool zero); + protected abstract LPVOID AllocBlock(nuint elements, nuint size, bool zero); + /// <summary> /// Frees a previously allocated block of memory /// </summary> /// <param name="block">The block to free</param> protected abstract bool FreeBlock(LPVOID block); + /// <summary> /// Resizes the previously allocated block of memory on the current heap /// </summary> @@ -173,9 +177,11 @@ namespace VNLib.Utils.Memory /// Heap base relies on the block pointer to remain unchanged if the resize fails so the /// block is still valid, and the return value is used to determine if the resize was successful /// </remarks> - protected abstract LPVOID ReAllocBlock(LPVOID block, size_t elements, size_t size, bool zero); + protected abstract LPVOID ReAllocBlock(LPVOID block, nuint elements, nuint size, bool zero); + ///<inheritdoc/> public override int GetHashCode() => handle.GetHashCode(); + ///<inheritdoc/> public override bool Equals(object? obj) { diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index b05ad40..72edb26 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -40,7 +40,7 @@ namespace VNLib.Utils.Memory [StructLayout(LayoutKind.Sequential)] public readonly struct UnsafeMemoryHandle<T> : IMemoryHandle<T>, IEquatable<UnsafeMemoryHandle<T>> where T : unmanaged { - private enum HandleType + private enum HandleType : byte { None, Pool, @@ -60,10 +60,12 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, IntLength) : new (_memoryPtr.ToPointer(), IntLength); } - ///<inheritdoc/> + /// <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 ulong Length => (ulong)_length; + public readonly nuint Length => (nuint)_length; /// <summary> /// Creates an empty <see cref="UnsafeMemoryHandle{T}"/> @@ -153,11 +155,18 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> public readonly unsafe MemoryHandle Pin(int elementIndex) { - //Guard + //guard empty handle + if (_handleType == HandleType.None) + { + throw new InvalidOperationException("The handle is empty, and cannot be pinned"); + } + + //Guard size if (elementIndex < 0 || elementIndex >= IntLength) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); } + if (_handleType == HandleType.Pool) { diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index 7fa0c5a..8bb0bb6 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -52,6 +52,7 @@ namespace VNLib.Utils.Memory /// The number of unicode characters the current instance can reference /// </summary> public int Length => _stringSequence.Size; + /// <summary> /// Gets a value indicating if the current instance is empty /// </summary> @@ -62,14 +63,7 @@ namespace VNLib.Utils.Memory _stringSequence = sequence; } - private VnString( - MemoryHandle<char> handle, -#if TARGET_64_BIT - ulong start, -#else - int start, -#endif - int length) + private VnString(MemoryHandle<char> handle, nuint start, int length) { Handle = handle ?? throw new ArgumentNullException(nameof(handle)); //get sequence @@ -83,6 +77,7 @@ namespace VNLib.Utils.Memory { //Default string sequence is empty and does not hold any memory } + /// <summary> /// Creates a new <see cref="VnString"/> around a <see cref="ReadOnlySpan{T}"/> or a <see cref="string"/> of data /// </summary> @@ -90,13 +85,13 @@ namespace VNLib.Utils.Memory /// <exception cref="OutOfMemoryException"></exception> public VnString(ReadOnlySpan<char> data) { - //Create new handle with enough size (heap) - Handle = Memory.Shared.Alloc<char>(data.Length); - //Copy - Memory.Copy(data, Handle, 0); + //Create new handle and copy incoming data to it + Handle = MemoryUtil.Shared.AllocAndCopy(data); + //Get subsequence over the whole copy of data _stringSequence = Handle.GetSubSequence(0, data.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 @@ -122,7 +117,7 @@ namespace VNLib.Utils.Memory //Get the number of characters int numChars = encoding.GetCharCount(vnms.AsSpan()); //New handle - MemoryHandle<char> charBuffer = Memory.Shared.Alloc<char>(numChars); + MemoryHandle<char> charBuffer = MemoryUtil.Shared.Alloc<char>(numChars); try { //Write characters to character buffer @@ -141,9 +136,9 @@ namespace VNLib.Utils.Memory else { //Create a new char bufer that will expand dyanmically - MemoryHandle<char> charBuffer = Memory.Shared.Alloc<char>(bufferSize); + MemoryHandle<char> charBuffer = MemoryUtil.Shared.Alloc<char>(bufferSize); //Allocate a binary buffer - MemoryHandle<byte> binBuffer = Memory.Shared.Alloc<byte>(bufferSize); + MemoryHandle<byte> binBuffer = MemoryUtil.Shared.Alloc<byte>(bufferSize); try { int length = 0; @@ -194,6 +189,7 @@ namespace VNLib.Utils.Memory } } } + /// <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. @@ -203,27 +199,24 @@ namespace VNLib.Utils.Memory /// <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, - -#if TARGET_64_BIT - ulong start, -#else - int start, -#endif - - int length) + public static VnString ConsumeHandle(MemoryHandle<char> handle, nuint start, int length) { - if(length < 0) + if (handle is null) { - throw new ArgumentOutOfRangeException(nameof(length)); + throw new ArgumentNullException(nameof(handle)); } - if((uint)length > handle.Length) + + 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 /// to decode the binary data to a new <see cref="VnString"/> heap character buffer. @@ -354,10 +347,11 @@ namespace VNLib.Utils.Memory throw new ArgumentOutOfRangeException(nameof(count)); } //get sub-sequence slice for the current string - SubSequence<char> sub = _stringSequence.Slice((uint)start, count); + SubSequence<char> sub = _stringSequence.Slice((nuint)start, count); //Create new string with offsets pointing to same internal referrence return new VnString(sub); } + /// <summary> /// Creates a <see cref="VnString"/> that is a window within the current string, /// the referrence points to the same memory as the first instnace. @@ -403,13 +397,12 @@ namespace VNLib.Utils.Memory /// </summary> /// <returns><see cref="string"/> representation of internal data</returns> /// <exception cref="ObjectDisposedException"></exception> - public override unsafe string ToString() + public override string ToString() { - //Check - Check(); //Create a new return AsSpan().ToString(); } + /// <summary> /// Gets the value of the character at the specified index /// </summary> @@ -474,7 +467,21 @@ namespace VNLib.Utils.Memory /// a character span etc /// </remarks> /// <exception cref="ObjectDisposedException"></exception> - public override int GetHashCode() => string.GetHashCode(AsSpan()); + public override int GetHashCode() => GetHashCode(StringComparison.Ordinal); + + /// <summary> + /// Gets a hashcode for the underyling string by using the .NET <see cref="string.GetHashCode()"/> + /// method on the character representation of the data + /// </summary> + /// <param name="stringComparison">The string comperison mode</param> + /// <returns></returns> + /// <remarks> + /// It is safe to compare hashcodes of <see cref="VnString"/> to the <see cref="string"/> class or + /// a character span etc + /// </remarks> + /// <exception cref="ObjectDisposedException"></exception> + public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison); + ///<inheritdoc/> protected override void Free() { diff --git a/lib/Utils/src/Memory/VnTable.cs b/lib/Utils/src/Memory/VnTable.cs index 1d5c0a6..2c6ce74 100644 --- a/lib/Utils/src/Memory/VnTable.cs +++ b/lib/Utils/src/Memory/VnTable.cs @@ -35,33 +35,38 @@ namespace VNLib.Utils.Memory public sealed class VnTable<T> : VnDisposeable, IIndexable<uint, T> where T : unmanaged { private readonly MemoryHandle<T>? BufferHandle; + /// <summary> /// A value that indicates if the table does not contain any values /// </summary> public bool Empty { get; } + /// <summary> /// The number of rows in the table /// </summary> - public int Rows { get; } + public uint Rows { get; } + /// <summary> /// The nuber of columns in the table /// </summary> - public int Cols { get; } + public uint Cols { get; } + /// <summary> - /// Creates a new 2 dimensional table in unmanaged heap memory, using the <see cref="Memory.Shared"/> heap. + /// Creates a new 2 dimensional table in unmanaged heap memory, using the <see cref="MemoryUtil.Shared"/> heap. /// User should dispose of the table when no longer in use /// </summary> /// <param name="rows">Number of rows in the table</param> /// <param name="cols">Number of columns in the table</param> - public VnTable(int rows, int cols) : this(Memory.Shared, rows, cols) { } + public VnTable(uint rows, uint cols) : this(MemoryUtil.Shared, rows, cols) { } + /// <summary> /// Creates a new 2 dimensional table in unmanaged heap memory, using the specified heap. /// User should dispose of the table when no longer in use /// </summary> - /// <param name="heap"><see cref="PrivateHeap"/> to allocate table memory from</param> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate table memory from</param> /// <param name="rows">Number of rows in the table</param> /// <param name="cols">Number of columns in the table</param> - public VnTable(IUnmangedHeap heap, int rows, int cols) + public VnTable(IUnmangedHeap heap, uint rows, uint cols) { if (rows < 0 || cols < 0) { @@ -71,19 +76,28 @@ namespace VNLib.Utils.Memory if (rows == 0 && cols == 0) { Empty = true; - return; } + else + { + _ = heap ?? throw new ArgumentNullException(nameof(heap)); - _ = heap ?? throw new ArgumentNullException(nameof(heap)); + this.Rows = rows; + this.Cols = cols; - this.Rows = rows; - this.Cols = cols; + ulong tableSize = checked((ulong) rows * (ulong) cols); - long tableSize = Math.BigMul(rows, cols); + if (tableSize > nuint.MaxValue) + { +#pragma warning disable CA2201 // Do not raise reserved exception types + throw new OutOfMemoryException("Table size is too large"); +#pragma warning restore CA2201 // Do not raise reserved exception types + } - //Alloc a buffer with zero memory enabled, with Rows * Cols number of elements - BufferHandle = heap.Alloc<T>(tableSize, true); + //Alloc a buffer with zero memory enabled, with Rows * Cols number of elements + BufferHandle = heap.Alloc<T>((nuint)tableSize, true); + } } + /// <summary> /// Gets the value of an item in the table at the given indexes /// </summary> @@ -93,7 +107,7 @@ namespace VNLib.Utils.Memory /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidOperationException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public T Get(int row, int col) + public T Get(uint row, uint col) { Check(); if (this.Empty) @@ -114,15 +128,18 @@ namespace VNLib.Utils.Memory } //Calculate the address in memory for the item //Calc row offset - long address = Cols * row; + ulong address = checked(row * this.Cols); + //Calc column offset address += col; + unsafe { //Get the value item - return *(BufferHandle!.GetOffset(address)); + return *(BufferHandle!.GetOffset((nuint)address)); } } + /// <summary> /// Sets the value of an item in the table at the given address /// </summary> @@ -133,7 +150,7 @@ namespace VNLib.Utils.Memory /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidOperationException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public void Set(int row, int col, T item) + public void Set(uint row, uint col, T item) { Check(); if (this.Empty) @@ -152,28 +169,34 @@ namespace VNLib.Utils.Memory { throw new ArgumentOutOfRangeException(nameof(col), "Column address out of range of current table"); } + //Calculate the address in memory for the item + //Calc row offset - long address = Cols * row; + ulong address = checked(Cols * row); + //Calc column offset address += col; + //Set the value item unsafe { - *BufferHandle!.GetOffset(address) = item; + *BufferHandle!.GetOffset((nuint)address) = item; } } + /// <summary> - /// Equivalent to <see cref="VnTable{T}.Get(int, int)"/> and <see cref="VnTable{T}.Set(int, int, T)"/> + /// Equivalent to <see cref="VnTable{T}.Get(uint, uint)"/> and <see cref="VnTable{T}.Set(uint, uint, T)"/> /// </summary> /// <param name="row">Row address of item</param> /// <param name="col">Column address of item</param> /// <returns>The value of the item</returns> - public T this[int row, int col] + public T this[uint row, uint col] { get => Get(row, col); set => Set(row, col, value); } + /// <summary> /// Allows for direct addressing in the table. /// </summary> @@ -200,10 +223,11 @@ namespace VNLib.Utils.Memory *(BufferHandle!.GetOffset(index)) = value; } } + ///<inheritdoc/> protected override void Free() { - if (!this.Empty) + if (!Empty) { //Dispose the buffer BufferHandle!.Dispose(); diff --git a/lib/Utils/src/Memory/VnTempBuffer.cs b/lib/Utils/src/Memory/VnTempBuffer.cs index 7726fe1..1d8e42f 100644 --- a/lib/Utils/src/Memory/VnTempBuffer.cs +++ b/lib/Utils/src/Memory/VnTempBuffer.cs @@ -28,7 +28,6 @@ using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using VNLib.Utils.Extensions; -using System.Security.Cryptography; namespace VNLib.Utils.Memory { @@ -52,12 +51,7 @@ namespace VNLib.Utils.Memory /// <summary> /// Actual length of internal buffer /// </summary> - public ulong Length => (ulong)Buffer.LongLength; - - /// <summary> - /// Actual length of internal buffer - /// </summary> - public int IntLength => Buffer.Length; + public nuint Length => (nuint)Buffer.LongLength; ///<inheritdoc/> ///<exception cref="ObjectDisposedException"></exception> @@ -77,6 +71,7 @@ namespace VNLib.Utils.Memory /// <param name="zero">Set the zero memory flag on close</param> public VnTempBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero) {} + /// <summary> /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from specified array-pool /// </summary> @@ -89,6 +84,7 @@ namespace VNLib.Utils.Memory Buffer = pool.Rent(minSize, zero); InitSize = minSize; } + /// <summary> /// Gets an offset wrapper around the current buffer /// </summary> @@ -101,6 +97,7 @@ namespace VNLib.Utils.Memory //Let arraysegment throw exceptions for checks return new ArraySegment<T>(Buffer, offset, count); } + ///<inheritdoc/> public T this[int index] { @@ -127,6 +124,7 @@ namespace VNLib.Utils.Memory Check(); return new Memory<T>(Buffer, 0, InitSize); } + /// <summary> /// Gets a memory structure around the internal buffer /// </summary> @@ -140,6 +138,7 @@ namespace VNLib.Utils.Memory Check(); return new Memory<T>(Buffer, start, count); } + /// <summary> /// Gets a memory structure around the internal buffer /// </summary> @@ -181,17 +180,20 @@ namespace VNLib.Utils.Memory unsafe MemoryHandle IPinnable.Pin(int elementIndex) { //Guard - if (elementIndex < 0 || elementIndex >= IntLength) + if (elementIndex < 0 || elementIndex >= Buffer.Length) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); } //Pin the array GCHandle arrHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned); + //Get array base address void* basePtr = (void*)arrHandle.AddrOfPinnedObject(); + //Get element offset void* indexOffet = Unsafe.Add<T>(basePtr, elementIndex); + return new(indexOffet, arrHandle, this); } diff --git a/lib/Utils/src/Memory/PrivateHeap.cs b/lib/Utils/src/Memory/Win32PrivateHeap.cs index 5d97506..fe214f4 100644 --- a/lib/Utils/src/Memory/PrivateHeap.cs +++ b/lib/Utils/src/Memory/Win32PrivateHeap.cs @@ -28,7 +28,6 @@ using System.Runtime.Versioning; using System.Runtime.InteropServices; using DWORD = System.Int64; -using SIZE_T = System.UInt64; using LPVOID = System.IntPtr; namespace VNLib.Utils.Memory @@ -39,13 +38,13 @@ namespace VNLib.Utils.Memory ///</para> ///</summary> ///<remarks> - /// <see cref="PrivateHeap"/> implements <see cref="SafeHandle"/> and tracks allocated blocks by its + /// <see cref="Win32PrivateHeap"/> implements <see cref="SafeHandle"/> and tracks allocated blocks by its /// referrence counter. Allocations increment the count, and free's decrement the count, so the heap may /// be disposed safely /// </remarks> [ComVisible(false)] [SupportedOSPlatform("Windows")] - public sealed class PrivateHeap : UnmanagedHeapBase + public sealed class Win32PrivateHeap : UnmanagedHeapBase { private const string KERNEL_DLL = "Kernel32"; @@ -59,10 +58,12 @@ namespace VNLib.Utils.Memory [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern LPVOID HeapAlloc(IntPtr hHeap, DWORD flags, SIZE_T dwBytes); + private static extern LPVOID HeapAlloc(IntPtr hHeap, DWORD flags, nuint dwBytes); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern LPVOID HeapReAlloc(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes); + private static extern LPVOID HeapReAlloc(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem, nuint dwBytes); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return: MarshalAs(UnmanagedType.Bool)] @@ -70,32 +71,35 @@ namespace VNLib.Utils.Memory [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern LPVOID HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); + private static extern LPVOID HeapCreate(DWORD flOptions, nuint dwInitialSize, nuint dwMaximumSize); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool HeapDestroy(IntPtr hHeap); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] private static extern bool HeapValidate(IntPtr hHeap, DWORD dwFlags, LPVOID lpMem); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U8)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern SIZE_T HeapSize(IntPtr hHeap, DWORD flags, LPVOID lpMem); + private static extern nuint HeapSize(IntPtr hHeap, DWORD flags, LPVOID lpMem); #endregion /// <summary> - /// Create a new <see cref="PrivateHeap"/> with the specified sizes and flags + /// Create a new <see cref="Win32PrivateHeap"/> with the specified sizes and flags /// </summary> /// <param name="initialSize">Intial size of the heap</param> /// <param name="maxHeapSize">Maximum size allowed for the heap (disabled = 0, default)</param> /// <param name="flags">Defalt heap flags to set globally for all blocks allocated by the heap (default = 0)</param> - public static PrivateHeap Create(SIZE_T initialSize, SIZE_T maxHeapSize = 0, DWORD flags = HEAP_NO_FLAGS) + public static Win32PrivateHeap Create(nuint initialSize, nuint maxHeapSize = 0, DWORD flags = HEAP_NO_FLAGS) { //Call create, throw exception if the heap falled to allocate IntPtr heapHandle = HeapCreate(flags, initialSize, maxHeapSize); + if (heapHandle == IntPtr.Zero) { throw new NativeMemoryException("Heap could not be created"); @@ -112,16 +116,16 @@ namespace VNLib.Utils.Memory /// </summary> /// <param name="win32HeapHandle">An open and valid handle to a win32 private heap</param> /// <returns>A wrapper around the specified heap</returns> - public static PrivateHeap ConsumeExisting(IntPtr win32HeapHandle) => new (win32HeapHandle); + public static Win32PrivateHeap ConsumeExisting(IntPtr win32HeapHandle) => new (win32HeapHandle); - private PrivateHeap(IntPtr heapPtr) : base(false, true) => handle = heapPtr; + private Win32PrivateHeap(IntPtr heapPtr) : base(false, true) => handle = heapPtr; /// <summary> /// Retrieves the size of a memory block allocated from the current heap. /// </summary> /// <param name="block">The pointer to a block of memory to get the size of</param> /// <returns>The size of the block of memory, (SIZE_T)-1 if the operation fails</returns> - public SIZE_T HeapSize(ref LPVOID block) => HeapSize(handle, HEAP_NO_FLAGS, block); + public nuint HeapSize(ref LPVOID block) => HeapSize(handle, HEAP_NO_FLAGS, block); /// <summary> /// Validates the specified block of memory within the current heap instance. This function will block hte @@ -167,17 +171,20 @@ namespace VNLib.Utils.Memory return HeapDestroy(handle) && base.ReleaseHandle(); } ///<inheritdoc/> - protected override sealed LPVOID AllocBlock(ulong elements, ulong size, bool zero) + protected override sealed LPVOID AllocBlock(nuint elements, nuint size, bool zero) { - ulong bytes = checked(elements * size); + nuint bytes = checked(elements * size); + return HeapAlloc(handle, zero ? HEAP_ZERO_MEMORY : HEAP_NO_FLAGS, bytes); } ///<inheritdoc/> protected override sealed bool FreeBlock(LPVOID block) => HeapFree(handle, HEAP_NO_FLAGS, block); + ///<inheritdoc/> - protected override sealed LPVOID ReAllocBlock(LPVOID block, ulong elements, ulong size, bool zero) + protected override sealed LPVOID ReAllocBlock(LPVOID block, nuint elements, nuint size, bool zero) { - ulong bytes = checked(elements * size); + nuint bytes = checked(elements * size); + return HeapReAlloc(handle, zero ? HEAP_ZERO_MEMORY : HEAP_NO_FLAGS, block, bytes); } } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index 94d8a1a..8359f8f 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -61,7 +61,7 @@ namespace VNLib.Utils //get number of bytes int byteCount = encoding.GetByteCount(data); //resize the handle to fit the data - handle = Memory.Memory.Shared.Alloc<byte>(byteCount); + handle = Memory.MemoryUtil.Shared.Alloc<byte>(byteCount); //encode int size = encoding.GetBytes(data, handle); //Consume the handle into a new vnmemstream and return it @@ -479,7 +479,7 @@ namespace VNLib.Utils //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 = Memory.Memory.UnsafeAlloc<char>(entropy)) + using (UnsafeMemoryHandle<char> charBuffer = Memory.MemoryUtil.UnsafeAlloc<char>(entropy)) { //Encode ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span); @@ -512,7 +512,7 @@ namespace VNLib.Utils //calc size of bin buffer int size = base32.Length; //Rent a bin buffer - using UnsafeMemoryHandle<byte> binBuffer = Memory.Memory.UnsafeAlloc<byte>(size); + using UnsafeMemoryHandle<byte> binBuffer = Memory.MemoryUtil.UnsafeAlloc<byte>(size); //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, binBuffer.Span); //Marshal back to a struct @@ -532,7 +532,7 @@ namespace VNLib.Utils return null; } //Buffer size of the base32 string will always be enough buffer space - using UnsafeMemoryHandle<byte> tempBuffer = Memory.Memory.UnsafeAlloc<byte>(base32.Length); + using UnsafeMemoryHandle<byte> tempBuffer = Memory.MemoryUtil.UnsafeAlloc<byte>(base32.Length); //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span); @@ -903,7 +903,7 @@ namespace VNLib.Utils int decodedSize = encoding.GetByteCount(chars); //alloc buffer - using UnsafeMemoryHandle<byte> decodeHandle = Memory.Memory.UnsafeAlloc<byte>(decodedSize); + using UnsafeMemoryHandle<byte> decodeHandle = Memory.MemoryUtil.UnsafeAlloc<byte>(decodedSize); //Get the utf8 binary data int count = encoding.GetBytes(chars, decodeHandle); return Base64UrlDecode(decodeHandle.Span[..count], output); diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 02ef1f1..34dbb60 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -25,10 +25,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using VNLib.Utils; using VNLib.Utils.Extensions; -using static VNLib.Utils.Memory.Memory; +using static VNLib.Utils.Memory.MemoryUtil; namespace VNLib.Utils.Memory.Tests { @@ -43,7 +42,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentOutOfRangeException>(() => Shared.Alloc<byte>(-1)); //Make sure over-alloc throws - Assert.ThrowsException<NativeMemoryOutOfMemoryException>(() => Shared.Alloc<byte>(ulong.MaxValue, false)); + Assert.ThrowsException<NativeMemoryOutOfMemoryException>(() => Shared.Alloc<byte>(nuint.MaxValue, false)); } #if TARGET_64_BIT [TestMethod] @@ -54,9 +53,9 @@ namespace VNLib.Utils.Memory.Tests using MemoryHandle<byte> handle = Shared.Alloc<byte>(bigHandleSize); //verify size - Assert.AreEqual(handle.ByteLength, (ulong)bigHandleSize); + Assert.IsTrue(handle.ByteLength, (ulong)bigHandleSize); //Since handle is byte, should also match - Assert.AreEqual(handle.Length, (ulong)bigHandleSize); + Assert.IsTrue(handle.Length, (ulong)bigHandleSize); //Should throw overflow Assert.ThrowsException<OverflowException>(() => _ = handle.Span); @@ -68,8 +67,6 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.GetOffsetSpan((long)int.MaxValue + 1, 1024)); } -#else - #endif [TestMethod] @@ -77,15 +74,15 @@ namespace VNLib.Utils.Memory.Tests { using MemoryHandle<byte> handle = Shared.Alloc<byte>(128, true); - Assert.AreEqual(handle.IntLength, 128); + Assert.IsTrue(handle.Length == 128); - Assert.AreEqual(handle.Length, (ulong)128); + Assert.IsTrue(handle.Length == 128); //Check span against base pointer deref handle.Span[120] = 10; - Assert.AreEqual(*handle.GetOffset(120), 10); + Assert.IsTrue(*handle.GetOffset(120) == 10); } @@ -153,14 +150,14 @@ namespace VNLib.Utils.Memory.Tests { using MemoryHandle<byte> handle = Shared.Alloc<byte>(1024); - Assert.AreEqual(handle.IntLength, 1024); + Assert.IsTrue(handle.Length == 1024); Assert.ThrowsException<ArgumentOutOfRangeException>(() => handle.Resize(-1)); //Resize the handle handle.Resize(2048); - Assert.AreEqual(handle.IntLength, 2048); + Assert.IsTrue(handle.Length == 2048); Assert.IsTrue(handle.AsSpan(2048).IsEmpty); @@ -173,11 +170,11 @@ namespace VNLib.Utils.Memory.Tests //test resize handle.ResizeIfSmaller(100); //Handle should be unmodified - Assert.AreEqual(handle.IntLength, 2048); + Assert.IsTrue(handle.Length == 2048); //test working handle.ResizeIfSmaller(4096); - Assert.AreEqual(handle.IntLength, 4096); + Assert.IsTrue(handle.Length == 4096); } } } diff --git a/lib/Utils/tests/Memory/MemoryTests.cs b/lib/Utils/tests/Memory/MemoryTests.cs deleted file mode 100644 index 5b68cf5..0000000 --- a/lib/Utils/tests/Memory/MemoryTests.cs +++ /dev/null @@ -1,244 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.UtilsTests -* File: MemoryTests.cs -* -* MemoryTests.cs is part of VNLib.UtilsTests which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.UtilsTests 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.UtilsTests 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.UtilsTests. If not, see http://www.gnu.org/licenses/. -*/ - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Runtime.InteropServices; - -using VNLib.Utils.Extensions; - -namespace VNLib.Utils.Memory.Tests -{ - [TestClass()] - public class MemoryTests - { - [TestMethod] - public void MemorySharedHeapLoadedTest() - { - Assert.IsNotNull(Memory.Shared); - } - - [TestMethod()] - public void UnsafeAllocTest() - { - //test against negative number - Assert.ThrowsException<ArgumentException>(() => Memory.UnsafeAlloc<byte>(-1)); - - //Alloc large block test (100mb) - const int largTestSize = 100000 * 1024; - //Alloc super small block - const int smallTestSize = 5; - - using (UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(largTestSize, false)) - { - Assert.AreEqual(largTestSize, buffer.IntLength); - Assert.AreEqual(largTestSize, buffer.Span.Length); - - buffer.Span[0] = 254; - Assert.AreEqual(buffer.Span[0], 254); - } - - using (UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(smallTestSize, false)) - { - Assert.AreEqual(smallTestSize, buffer.IntLength); - Assert.AreEqual(smallTestSize, buffer.Span.Length); - - buffer.Span[0] = 254; - Assert.AreEqual(buffer.Span[0], 254); - } - - //Different data type - - using(UnsafeMemoryHandle<long> buffer = Memory.UnsafeAlloc<long>(largTestSize, false)) - { - Assert.AreEqual(largTestSize, buffer.IntLength); - Assert.AreEqual(largTestSize, buffer.Span.Length); - - buffer.Span[0] = long.MaxValue; - Assert.AreEqual(buffer.Span[0], long.MaxValue); - } - - using (UnsafeMemoryHandle<long> buffer = Memory.UnsafeAlloc<long>(smallTestSize, false)) - { - Assert.AreEqual(smallTestSize, buffer.IntLength); - Assert.AreEqual(smallTestSize, buffer.Span.Length); - - buffer.Span[0] = long.MaxValue; - Assert.AreEqual(buffer.Span[0], long.MaxValue); - } - } - - [TestMethod()] - public void UnsafeZeroMemoryAsSpanTest() - { - //Alloc test buffer - Span<byte> test = new byte[1024]; - test.Fill(0); - //test other empty span - Span<byte> verify = new byte[1024]; - verify.Fill(0); - - //Fill test buffer with random values - Random.Shared.NextBytes(test); - - //make sure buffers are not equal - Assert.IsFalse(test.SequenceEqual(verify)); - - //Zero buffer - Memory.UnsafeZeroMemory<byte>(test); - - //Make sure buffers are equal - Assert.IsTrue(test.SequenceEqual(verify)); - } - - [TestMethod()] - public void UnsafeZeroMemoryAsMemoryTest() - { - //Alloc test buffer - Memory<byte> test = new byte[1024]; - test.Span.Fill(0); - //test other empty span - Memory<byte> verify = new byte[1024]; - verify.Span.Fill(0); - - //Fill test buffer with random values - Random.Shared.NextBytes(test.Span); - - //make sure buffers are not equal - Assert.IsFalse(test.Span.SequenceEqual(verify.Span)); - - //Zero buffer - Memory.UnsafeZeroMemory<byte>(test); - - //Make sure buffers are equal - Assert.IsTrue(test.Span.SequenceEqual(verify.Span)); - } - - [TestMethod()] - public void InitializeBlockAsSpanTest() - { - //Alloc test buffer - Span<byte> test = new byte[1024]; - test.Fill(0); - //test other empty span - Span<byte> verify = new byte[1024]; - verify.Fill(0); - - //Fill test buffer with random values - Random.Shared.NextBytes(test); - - //make sure buffers are not equal - Assert.IsFalse(test.SequenceEqual(verify)); - - //Zero buffer - Memory.InitializeBlock(test); - - //Make sure buffers are equal - Assert.IsTrue(test.SequenceEqual(verify)); - } - - [TestMethod()] - public void InitializeBlockMemoryTest() - { - //Alloc test buffer - Memory<byte> test = new byte[1024]; - test.Span.Fill(0); - //test other empty span - Memory<byte> verify = new byte[1024]; - verify.Span.Fill(0); - - //Fill test buffer with random values - Random.Shared.NextBytes(test.Span); - - //make sure buffers are not equal - Assert.IsFalse(test.Span.SequenceEqual(verify.Span)); - - //Zero buffer - Memory.InitializeBlock(test); - - //Make sure buffers are equal - Assert.IsTrue(test.Span.SequenceEqual(verify.Span)); - } - - #region structmemory tests - - [StructLayout(LayoutKind.Sequential)] - struct TestStruct - { - public int X; - public int Y; - } - - [TestMethod()] - public unsafe void ZeroStructAsPointerTest() - { - TestStruct* s = Memory.Shared.StructAlloc<TestStruct>(); - s->X = 10; - s->Y = 20; - Assert.AreEqual(10, s->X); - Assert.AreEqual(20, s->Y); - //zero struct - Memory.ZeroStruct(s); - //Verify data was zeroed - Assert.AreEqual(0, s->X); - Assert.AreEqual(0, s->Y); - //Free struct - Memory.Shared.StructFree(s); - } - - [TestMethod()] - public unsafe void ZeroStructAsVoidPointerTest() - { - TestStruct* s = Memory.Shared.StructAlloc<TestStruct>(); - s->X = 10; - s->Y = 20; - Assert.AreEqual(10, s->X); - Assert.AreEqual(20, s->Y); - //zero struct - Memory.ZeroStruct<TestStruct>((void*)s); - //Verify data was zeroed - Assert.AreEqual(0, s->X); - Assert.AreEqual(0, s->Y); - //Free struct - Memory.Shared.StructFree(s); - } - - [TestMethod()] - public unsafe void ZeroStructAsIntPtrTest() - { - TestStruct* s = Memory.Shared.StructAlloc<TestStruct>(); - s->X = 10; - s->Y = 20; - Assert.AreEqual(10, s->X); - Assert.AreEqual(20, s->Y); - //zero struct - Memory.ZeroStruct<TestStruct>((IntPtr)s); - //Verify data was zeroed - Assert.AreEqual(0, s->X); - Assert.AreEqual(0, s->Y); - //Free struct - Memory.Shared.StructFree(s); - } - #endregion - } -}
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs new file mode 100644 index 0000000..fb3700e --- /dev/null +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -0,0 +1,333 @@ +using System.Buffers; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Memory.Tests +{ + [TestClass()] + public class MemoryUtilTests + { + const int ZERO_TEST_LOOP_ITERATIONS = 1000000; + const int ZERO_TEST_MAX_BUFFER_SIZE = 10 * 1024; + + [TestMethod()] + public void InitializeNewHeapForProcessTest() + { + //Check if rpmalloc is loaded + if (MemoryUtil.IsRpMallocLoaded) + { + //Initialize the heap + using IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); + + //Confirm that the heap is actually a rpmalloc heap + Assert.IsInstanceOfType(heap, typeof(RpMallocPrivateHeap)); + } + else + { + //Confirm that Rpmalloc will throw DLLNotFound if the lib is not loaded + Assert.ThrowsException<DllNotFoundException>(() => _ = RpMallocPrivateHeap.GlobalHeap.Alloc(1, 1, false)); + } + } + + [TestMethod()] + public void UnsafeZeroMemoryTest() + { + //Get random data buffer as a readonly span + ReadOnlyMemory<byte> buffer = RandomNumberGenerator.GetBytes(1024); + + //confirm buffer is not all zero + Assert.IsFalse(AllZero(buffer.Span)); + + //Zero readonly memory + MemoryUtil.UnsafeZeroMemory(buffer); + + //Confirm all zero + Assert.IsTrue(AllZero(buffer.Span)); + } + + private static bool AllZero(ReadOnlySpan<byte> span) + { + for (int i = 0; i < span.Length; i++) + { + if (span[i] != 0) + { + return false; + } + } + return true; + } + + [TestMethod()] + public void UnsafeZeroMemoryTest1() + { + //Get random data buffer as a readonly span + ReadOnlySpan<byte> buffer = RandomNumberGenerator.GetBytes(1024); + + //confirm buffer is not all zero + Assert.IsFalse(AllZero(buffer)); + + //Zero readonly span + MemoryUtil.UnsafeZeroMemory(buffer); + + //Confirm all zero + Assert.IsTrue(AllZero(buffer)); + } + + + [TestMethod()] + public void InitializeBlockAsSpanTest() + { + //Get random data buffer as a readonly span + Span<byte> buffer = RandomNumberGenerator.GetBytes(1024); + + //confirm buffer is not all zero + Assert.IsFalse(AllZero(buffer)); + + //Zero readonly span + MemoryUtil.InitializeBlock(buffer); + + //Confirm all zero + Assert.IsTrue(AllZero(buffer)); + } + + [TestMethod()] + public void InitializeBlockMemoryTest() + { + //Get random data buffer as a readonly span + Memory<byte> buffer = RandomNumberGenerator.GetBytes(1024); + + //confirm buffer is not all zero + Assert.IsFalse(AllZero(buffer.Span)); + + //Zero readonly span + MemoryUtil.InitializeBlock(buffer); + + //Confirm all zero + Assert.IsTrue(AllZero(buffer.Span)); + } + + + [TestMethod()] + public unsafe void UnsafeAllocTest() + { + //No fail + using (UnsafeMemoryHandle<byte> handle = MemoryUtil.UnsafeAlloc<byte>(1024)) + { + _ = handle.Span; + _ = handle.Length; + _ = handle.IntLength; + + //Test span pointer against pinned handle + using (MemoryHandle pinned = handle.Pin(0)) + { + fixed (void* ptr = &MemoryMarshal.GetReference(handle.Span)) + { + Assert.IsTrue(ptr == pinned.Pointer); + } + } + + //Test negative pin + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(-1)); + + //Test pinned outsie handle size + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(1024)); + } + + //test against negative number + Assert.ThrowsException<ArgumentException>(() => MemoryUtil.UnsafeAlloc<byte>(-1)); + + //Alloc large block test (100mb) + const int largTestSize = 100000 * 1024; + //Alloc super small block + const int smallTestSize = 5; + + using (UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(largTestSize, false)) + { + Assert.IsTrue(largTestSize == buffer.IntLength); + Assert.IsTrue(largTestSize == buffer.Span.Length); + + buffer.Span[0] = 254; + Assert.IsTrue(buffer.Span[0] == 254); + } + + using (UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(smallTestSize, false)) + { + Assert.IsTrue(smallTestSize == buffer.IntLength); + Assert.IsTrue(smallTestSize == buffer.Span.Length); + + buffer.Span[0] = 254; + Assert.IsTrue(buffer.Span[0] == 254); + } + + //Different data type + using (UnsafeMemoryHandle<long> buffer = MemoryUtil.UnsafeAlloc<long>(largTestSize, false)) + { + Assert.IsTrue(largTestSize == buffer.IntLength); + Assert.IsTrue(largTestSize == buffer.Span.Length); + + buffer.Span[0] = long.MaxValue; + Assert.IsTrue(buffer.Span[0] == long.MaxValue); + } + + using (UnsafeMemoryHandle<long> buffer = MemoryUtil.UnsafeAlloc<long>(smallTestSize, false)) + { + Assert.IsTrue(smallTestSize == buffer.IntLength); + Assert.IsTrue(smallTestSize == buffer.Span.Length); + + buffer.Span[0] = long.MaxValue; + Assert.IsTrue(buffer.Span[0] == long.MaxValue); + } + + //Test empty handle + using (UnsafeMemoryHandle<byte> empty = new()) + { + Assert.IsTrue(0 == empty.Length); + Assert.IsTrue(0 == empty.IntLength); + + //Test pinning while empty + Assert.ThrowsException<InvalidOperationException>(() => _ = empty.Pin(0)); + } + + //Negative value + Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.UnsafeAlloc<byte>(-1)); + + + /* + * Alloc random sized blocks in a loop, confirm they are empty + * then fill the block with random data before freeing it back to + * the pool. This confirms that if blocks are allocated from a shared + * pool are properly zeroed when requestd + */ + + for (int i = 0; i < ZERO_TEST_LOOP_ITERATIONS; i++) + { + int randBufferSize = Random.Shared.Next(1024, ZERO_TEST_MAX_BUFFER_SIZE); + + //Alloc block, check if all zero, then free + using UnsafeMemoryHandle<byte> handle = MemoryUtil.UnsafeAlloc<byte>(randBufferSize, true); + + //Confirm all zero + Assert.IsTrue(AllZero(handle.Span)); + + //Fill with random data + Random.Shared.NextBytes(handle.Span); + } + } + + [TestMethod()] + public unsafe void SafeAllocTest() + { + //No fail + using (IMemoryHandle<byte> handle = MemoryUtil.SafeAlloc<byte>(1024)) + { + _ = handle.Span; + _ = handle.Length; + _ = handle.GetIntLength(); + + //Test span pointer against pinned handle + using (MemoryHandle pinned = handle.Pin(0)) + { + fixed (void* ptr = &MemoryMarshal.GetReference(handle.Span)) + { + Assert.IsTrue(ptr == pinned.Pointer); + } + } + + //Test negative pin + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(-1)); + + //Test pinned outsie handle size + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(1024)); + } + + + //Negative value + Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1)); + + + /* + * Alloc random sized blocks in a loop, confirm they are empty + * then fill the block with random data before freeing it back to + * the pool. This confirms that if blocks are allocated from a shared + * pool are properly zeroed when requestd + */ + + for (int i = 0; i < ZERO_TEST_LOOP_ITERATIONS; i++) + { + int randBufferSize = Random.Shared.Next(1024, ZERO_TEST_MAX_BUFFER_SIZE); + + //Alloc block, check if all zero, then free + using IMemoryHandle<byte> handle = MemoryUtil.SafeAlloc<byte>(randBufferSize, true); + + //Confirm all zero + Assert.IsTrue(AllZero(handle.Span)); + + //Fill with random data + Random.Shared.NextBytes(handle.Span); + } + } + + + [StructLayout(LayoutKind.Sequential)] + struct TestStruct + { + public int X; + public int Y; + } + + [TestMethod()] + public unsafe void ZeroStructAsPointerTest() + { + TestStruct* s = MemoryUtil.Shared.StructAlloc<TestStruct>(); + s->X = 10; + s->Y = 20; + Assert.IsTrue(10 == s->X); + Assert.IsTrue(20 == s->Y); + //zero struct + MemoryUtil.ZeroStruct(s); + //Verify data was zeroed + Assert.IsTrue(0 == s->X); + Assert.IsTrue(0 == s->Y); + //Free struct + MemoryUtil.Shared.StructFree(s); + } + + [TestMethod()] + public unsafe void ZeroStructAsVoidPointerTest() + { + TestStruct* s = MemoryUtil.Shared.StructAlloc<TestStruct>(); + s->X = 10; + s->Y = 20; + Assert.IsTrue(10 == s->X); + Assert.IsTrue(20 == s->Y); + //zero struct + MemoryUtil.ZeroStruct<TestStruct>((void*)s); + //Verify data was zeroed + Assert.IsTrue(0 == s->X); + Assert.IsTrue(0 == s->Y); + //Free struct + MemoryUtil.Shared.StructFree(s); + } + + [TestMethod()] + public unsafe void ZeroStructAsIntPtrTest() + { + TestStruct* s = MemoryUtil.Shared.StructAlloc<TestStruct>(); + s->X = 10; + s->Y = 20; + Assert.IsTrue(10 == s->X); + Assert.IsTrue(20 == s->Y); + //zero struct + MemoryUtil.ZeroStruct<TestStruct>((IntPtr)s); + //Verify data was zeroed + Assert.IsTrue(0 == s->X); + Assert.IsTrue(0 == s->Y); + //Free struct + MemoryUtil.Shared.StructFree(s); + } + } +}
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/VnTableTests.cs b/lib/Utils/tests/Memory/VnTableTests.cs index 11350d4..c9f99ea 100644 --- a/lib/Utils/tests/Memory/VnTableTests.cs +++ b/lib/Utils/tests/Memory/VnTableTests.cs @@ -33,26 +33,13 @@ namespace VNLib.Utils.Memory.Tests [TestMethod()] public void VnTableTest() { - Assert.ThrowsException<ArgumentOutOfRangeException>(() => - { - using VnTable<int> table = new(-1, 0); - }); - Assert.ThrowsException<ArgumentOutOfRangeException>(() => - { - using VnTable<int> table = new(0, -1); - }); - Assert.ThrowsException<ArgumentOutOfRangeException>(() => - { - using VnTable<int> table = new(-1, -1); - }); - //Empty table using (VnTable<int> empty = new(0, 0)) { Assert.IsTrue(empty.Empty); //Test 0 rows/cols - Assert.AreEqual(0, empty.Rows); - Assert.AreEqual(0, empty.Cols); + Assert.IsTrue(0 == empty.Rows); + Assert.IsTrue(0 == empty.Cols); } using (VnTable<int> table = new(40000, 10000)) @@ -60,8 +47,8 @@ namespace VNLib.Utils.Memory.Tests Assert.IsFalse(table.Empty); //Test table size - Assert.AreEqual(40000, table.Rows); - Assert.AreEqual(10000, table.Cols); + Assert.IsTrue(40000 == table.Rows); + Assert.IsTrue(10000 == table.Cols); } @@ -89,41 +76,41 @@ namespace VNLib.Utils.Memory.Tests [TestMethod()] public void GetSetTest() { - static void TestIndexAt(VnTable<int> table, int row, int col, int value) + static void TestIndexAt(VnTable<int> table, uint row, uint col, int value) { table[row, col] = value; - Assert.AreEqual(value, table[row, col]); - Assert.AreEqual(value, table.Get(row, col)); + Assert.IsTrue(value == table[row, col]); + Assert.IsTrue(value == table.Get(row, col)); } - static void TestSetAt(VnTable<int> table, int row, int col, int value) + static void TestSetAt(VnTable<int> table, uint row, uint col, int value) { table.Set(row, col, value); - Assert.AreEqual(value, table[row, col]); - Assert.AreEqual(value, table.Get(row, col)); + Assert.IsTrue(value == table[row, col]); + Assert.IsTrue(value == table.Get(row, col)); } - static void TestSetDirectAccess(VnTable<int> table, int row, int col, int value) + static void TestSetDirectAccess(VnTable<int> table, uint row, uint col, int value) { - int address = row * table.Cols + col; - table[(uint)address] = value; + uint address = row * table.Cols + col; + table[address] = value; //Get value using indexer - Assert.AreEqual(value, table[row, col]); + Assert.IsTrue(value == table[row, col]); } - static void TestGetDirectAccess(VnTable<int> table, int row, int col, int value) + static void TestGetDirectAccess(VnTable<int> table, uint row, uint col, int value) { table[row, col] = value; - int address = row * table.Cols + col; + uint address = row * table.Cols + col; //Test direct access - Assert.AreEqual(value, table[(uint)address]); + Assert.IsTrue(value == table[address]); //Get value using indexer - Assert.AreEqual(value, table[row, col]); - Assert.AreEqual(value, table.Get(row, col)); + Assert.IsTrue(value == table[row, col]); + Assert.IsTrue(value == table.Get(row, col)); } diff --git a/lib/Utils/tests/VnEncodingTests.cs b/lib/Utils/tests/VnEncodingTests.cs index a4e52f0..373b834 100644 --- a/lib/Utils/tests/VnEncodingTests.cs +++ b/lib/Utils/tests/VnEncodingTests.cs @@ -23,16 +23,12 @@ */ using System; +using System.Text; using System.Buffers; using System.Buffers.Text; -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using VNLib.Utils; namespace VNLib.Utils.Tests { |