diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Net.Http/src/Core/HttpServerBase.cs | 4 | ||||
-rw-r--r-- | lib/Net.Http/src/Core/Response/HttpResponse.cs | 4 | ||||
-rw-r--r-- | lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs | 101 | ||||
-rw-r--r-- | lib/Net.Transport.SimpleTCP/src/TcpServer.cs | 30 | ||||
-rw-r--r-- | lib/Plugins/src/Attributes/ConfigurationInitalizerAttribute.cs | 1 | ||||
-rw-r--r-- | lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs | 58 | ||||
-rw-r--r-- | lib/Plugins/src/IPlugin.cs | 7 | ||||
-rw-r--r-- | lib/Plugins/src/VNLib.Plugins.csproj | 4 | ||||
-rw-r--r-- | lib/Utils/src/Extensions/JsonExtensions.cs | 26 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 16 | ||||
-rw-r--r-- | lib/Utils/src/VnEncoding.cs | 59 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 89 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/VnTableTests.cs | 2 |
13 files changed, 328 insertions, 73 deletions
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs index d3f5e00..3daa3cc 100644 --- a/lib/Net.Http/src/Core/HttpServerBase.cs +++ b/lib/Net.Http/src/Core/HttpServerBase.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Http @@ -216,7 +216,7 @@ namespace VNLib.Net.Http { StopToken = CancellationTokenSource.CreateLinkedTokenSource(token); //Start servers with the new token source - Transport.Start(token); + Transport.Start(StopToken.Token); //Start the listen task return Task.Run(ListenWorkerDoWork, token); } diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs index ab0971d..69ef2f1 100644 --- a/lib/Net.Http/src/Core/Response/HttpResponse.cs +++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Http @@ -220,7 +220,7 @@ namespace VNLib.Net.Http.Core } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void Check() + void Check() { if (HeadersSent) { diff --git a/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs b/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs index 89c46e1..e08ee76 100644 --- a/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs +++ b/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Transport.SimpleTCP @@ -29,6 +29,7 @@ using System.Threading; using System.Net.Sockets; using System.IO.Pipelines; using System.Threading.Tasks; +using System.Runtime.CompilerServices; using VNLib.Utils.Memory; using VNLib.Utils.Memory.Caching; @@ -188,7 +189,8 @@ namespace VNLib.Net.Transport.Tcp while (true) { //wait for data from the write pipe and write it to the socket - ReadResult result = await SendPipe.Reader.ReadAsync(); + ReadResult result = await SendPipe.Reader.ReadAsync(CancellationToken.None); + //Catch error/cancel conditions and break the loop if (result.IsCanceled || !sock.Connected || result.Buffer.IsEmpty) { @@ -296,7 +298,7 @@ namespace VNLib.Net.Transport.Tcp //Advance/notify the pipe RecvPipe.Writer.Advance(count); - //Flush data at top of loop, since data is available from initial accept + //Publish read data FlushResult res = await RecvPipe.Writer.FlushAsync(CancellationToken.None); //Writing has completed, time to exit @@ -359,7 +361,7 @@ namespace VNLib.Net.Transport.Tcp if (result.IsCanceled) { - throw new OperationCanceledException("The operation was canceled by the underlying PipeWriter"); + ThrowHelpers.ThrowWriterCanceled(); } } finally @@ -378,13 +380,14 @@ namespace VNLib.Net.Transport.Tcp ValueTask<FlushResult> result = SendPipe.Writer.WriteAsync(data, cancellation); //Task completed successfully, so - if (result.IsCompletedSuccessfully) + if (result.IsCompleted) { //Stop timer SendTimer.Stop(); - //Safe to get the rseult - FlushResult fr = result.Result; + //safe to get the flush result sync, may throw, so preserve the call stack + FlushResult fr = result.GetAwaiter().GetResult(); + //Check for canceled and throw return fr.IsCanceled ? throw new OperationCanceledException("The write operation was canceled by the underlying PipeWriter") @@ -410,10 +413,14 @@ namespace VNLib.Net.Transport.Tcp ValueTask<FlushResult> result = SendPipe.Writer.WriteAsync(data, cancellation); //Task completed successfully, so - if (result.IsCompletedSuccessfully) + if (result.IsCompleted) { - //Safe to get the rseult - FlushResult fr = result.Result; + /* + * We can get the flush result synchronously, it may throw + * so preserve the call stack + */ + FlushResult fr = result.GetAwaiter().GetResult(); + //Check for canceled and throw return fr.IsCanceled ? throw new OperationCanceledException("The write operation was canceled by the underlying PipeWriter") @@ -431,7 +438,6 @@ namespace VNLib.Net.Transport.Tcp //Use timer if timeout is set, dont otherwise return SendTimeoutMs < 1 ? SendWithoutTimerInternalAsync(data, cancellation) : SendWithTimerInternalAsync(data, cancellation); } - void ITransportInterface.Send(ReadOnlySpan<byte> data) { @@ -449,27 +455,12 @@ namespace VNLib.Net.Transport.Tcp //Send the segment ValueTask<FlushResult> result = SendPipe.Writer.FlushAsync(CancellationToken.None); - //Task completed successfully, so - if (result.IsCompletedSuccessfully) - { - //Safe to get the rseult - FlushResult fr = result.Result; - - //Check for canceled and throw - if (fr.IsCanceled) - { - throw new OperationCanceledException("The write operation was canceled by the underlying PipeWriter"); - } - } - else - { - //Await the result - FlushResult fr = result.ConfigureAwait(false).GetAwaiter().GetResult(); + //Await the result synchronously + FlushResult fr = result.ConfigureAwait(false).GetAwaiter().GetResult(); - if (fr.IsCanceled) - { - throw new OperationCanceledException("The write operation was canceled by the underlying PipeWriter"); - } + if (fr.IsCanceled) + { + ThrowHelpers.ThrowWriterCanceled(); } } finally @@ -479,17 +470,48 @@ namespace VNLib.Net.Transport.Tcp } } - async ValueTask<int> ITransportInterface.RecvAsync(Memory<byte> buffer, CancellationToken cancellation) + + ValueTask<int> ITransportInterface.RecvAsync(Memory<byte> buffer, CancellationToken cancellation) { - //Restart timer + static async Task<int> AwaitAsyncRead(ValueTask<int> task, Timer recvTimer) + { + try + { + return await task.ConfigureAwait(false); + } + finally + { + recvTimer.Stop(); + } + } + + //Restart recv timer RecvTimer.Restart(RecvTimeoutMs); try { - return await RecvStream.ReadAsync(buffer, cancellation); + //Read async and get the value task + ValueTask<int> result = RecvStream.ReadAsync(buffer, cancellation); + + if (result.IsCompleted) + { + //Completed sync, may throw, if not return the results + int read = result.GetAwaiter().GetResult(); + + //Stop the timer + RecvTimer.Stop(); + + return ValueTask.FromResult(read); + } + else + { + //return async as value task + return new(AwaitAsyncRead(result, RecvTimer)); + } } - finally + catch { RecvTimer.Stop(); + throw; } } @@ -517,5 +539,14 @@ namespace VNLib.Net.Transport.Tcp return Task.WhenAll(vt.AsTask(), rv.AsTask(), _recvTask!, _sendTask!); } + + private static class ThrowHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowWriterCanceled() + { + throw new OperationCanceledException("The write operation was canceled by the underlying PipeWriter"); + } + } } } diff --git a/lib/Net.Transport.SimpleTCP/src/TcpServer.cs b/lib/Net.Transport.SimpleTCP/src/TcpServer.cs index fc0bcc5..0782158 100644 --- a/lib/Net.Transport.SimpleTCP/src/TcpServer.cs +++ b/lib/Net.Transport.SimpleTCP/src/TcpServer.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Transport.SimpleTCP @@ -107,8 +107,6 @@ namespace VNLib.Net.Transport.Tcp private AsyncQueue<VnSocketAsyncArgs>? WaitingSockets; private Socket? ServerSock; - //private CancellationToken Token; - private bool _canceledFlag; /// <summary> @@ -126,6 +124,7 @@ namespace VNLib.Net.Transport.Tcp { throw new InvalidOperationException("The server thread is currently listening and cannot be re-started"); } + //make sure the token isnt already canceled if (token.IsCancellationRequested) { @@ -189,21 +188,22 @@ namespace VNLib.Net.Transport.Tcp } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private VnSocketAsyncArgs ArgsConstructor() + private void ReturnCb(VnSocketAsyncArgs args) { - void ReturnCb(VnSocketAsyncArgs args) + //If the server has exited, dispose the args and dont return to pool + if (_canceledFlag) { - //If the server has exited, dispose the args and dont return to pool - if (_canceledFlag) - { - args.Dispose(); - } - else - { - SockAsyncArgPool.Return(args); - } + args.Dispose(); } + else + { + SockAsyncArgPool.Return(args); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private VnSocketAsyncArgs ArgsConstructor() + { //Socket args accept callback functions for this VnSocketAsyncArgs args = new(AcceptCompleted, ReturnCb, PipeOptions); return args; @@ -217,8 +217,10 @@ namespace VNLib.Net.Transport.Tcp { return; } + //Rent new args VnSocketAsyncArgs acceptArgs = SockAsyncArgPool!.Rent(); + //Accept another socket if (!acceptArgs.BeginAccept(ServerSock!)) { diff --git a/lib/Plugins/src/Attributes/ConfigurationInitalizerAttribute.cs b/lib/Plugins/src/Attributes/ConfigurationInitalizerAttribute.cs index f214d2b..6903902 100644 --- a/lib/Plugins/src/Attributes/ConfigurationInitalizerAttribute.cs +++ b/lib/Plugins/src/Attributes/ConfigurationInitalizerAttribute.cs @@ -27,6 +27,7 @@ using System.Text.Json; namespace VNLib.Plugins.Attributes { + /// <summary> /// Set this attribute on an <see cref="IPlugin"/> instance method to define the configuration initializer. /// This attribute can only be defined on a single instance method and cannot be overloaded. diff --git a/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs b/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs new file mode 100644 index 0000000..e922e9d --- /dev/null +++ b/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs @@ -0,0 +1,58 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: ServiceConfiguratorAttribute.cs +* +* ServiceConfiguratorAttribute.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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.Plugins 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.Plugins. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.ComponentModel.Design; + +namespace VNLib.Plugins.Attributes +{ + /// <summary> + /// <para> + /// Set this attribute on an <see cref="IPlugin"/> instance method to define the service configuration + /// method. When declated, allows the plugin to expose shared types to the host + /// </para> + /// <para> + /// This method may be runtime dependant, it may not be called on all platforms, and it + /// may not be required. + /// </para> + /// <para> + /// Lifecycle: This method may be called by the runtime anytime after the <see cref="IPlugin.Load"/> + /// method, its exposed services are considered invalid after the <see cref="IPlugin.Unload"/> + /// method is called. + /// </para> + /// <para> + /// Method signature: <code>void OnServiceConfiguration(<see cref="IServiceContainer"/> container)</code> + /// </para> + /// </summary> + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ServiceConfiguratorAttribute : Attribute + {} + + /// <summary> + /// A safe delegate that matches the signature of the <see cref="ServiceConfiguratorAttribute"/> + /// method exposed + /// </summary> + /// <param name="sender"></param> + public delegate void ServiceConfigurator(IServiceContainer sender); +} diff --git a/lib/Plugins/src/IPlugin.cs b/lib/Plugins/src/IPlugin.cs index 16bd403..d872232 100644 --- a/lib/Plugins/src/IPlugin.cs +++ b/lib/Plugins/src/IPlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins @@ -48,6 +48,11 @@ namespace VNLib.Plugins /// Returns all endpoints within the plugin to load into the current root /// </summary> /// <returns>An enumeration of endpoints to load</returns> + /// <remarks> + /// Lifecycle: Results returned from this method should be consistant (although its only + /// likely to be called once) anytime after the <see cref="Load"/> method, and undefined + /// after the <see cref="Unload"/> method is called. + /// </remarks> IEnumerable<IEndpoint> GetEndpoints(); } }
\ No newline at end of file diff --git a/lib/Plugins/src/VNLib.Plugins.csproj b/lib/Plugins/src/VNLib.Plugins.csproj index 062e7a4..6bb5cee 100644 --- a/lib/Plugins/src/VNLib.Plugins.csproj +++ b/lib/Plugins/src/VNLib.Plugins.csproj @@ -45,4 +45,8 @@ </None> </ItemGroup> + <ItemGroup> + <Folder Include="Services\" /> + </ItemGroup> + </Project> diff --git a/lib/Utils/src/Extensions/JsonExtensions.cs b/lib/Utils/src/Extensions/JsonExtensions.cs index a27dcc0..523f772 100644 --- a/lib/Utils/src/Extensions/JsonExtensions.cs +++ b/lib/Utils/src/Extensions/JsonExtensions.cs @@ -35,11 +35,29 @@ namespace VNLib.Utils.Extensions /// </summary> public enum TimeParseType { + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as milliseconds + /// </summary> Milliseconds, + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as seconds + /// </summary> Seconds, + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as milliseconds + /// </summary> Minutes, + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as milliseconds + /// </summary> Hours, + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as milliseconds + /// </summary> Days, + /// <summary> + /// Parses the value for <see cref="TimeSpan"/> as milliseconds + /// </summary> Ticks } @@ -67,7 +85,7 @@ namespace VNLib.Utils.Extensions /// <returns>The new object or default if the string is null or empty</returns> /// <exception cref="JsonException"></exception> /// <exception cref="NotSupportedException"></exception> - public static T? AsJsonObject<T>(this in ReadOnlySpan<byte> utf8bin, JsonSerializerOptions? options = null) + public static T? AsJsonObject<T>(this ReadOnlySpan<byte> utf8bin, JsonSerializerOptions? options = null) { return utf8bin.IsEmpty ? default : JsonSerializer.Deserialize<T>(utf8bin, options); } @@ -80,7 +98,7 @@ namespace VNLib.Utils.Extensions /// <returns>The new object or default if the string is null or empty</returns> /// <exception cref="JsonException"></exception> /// <exception cref="NotSupportedException"></exception> - public static T? AsJsonObject<T>(this in ReadOnlyMemory<byte> utf8bin, JsonSerializerOptions? options = null) + public static T? AsJsonObject<T>(this ReadOnlyMemory<byte> utf8bin, JsonSerializerOptions? options = null) { return utf8bin.IsEmpty ? default : JsonSerializer.Deserialize<T>(utf8bin.Span, options); } @@ -114,7 +132,7 @@ namespace VNLib.Utils.Extensions /// <param name="element"></param> /// <param name="propertyName">The name of the property to get the string value of</param> /// <returns>If the property exists, returns the string stored at that property</returns> - public static string? GetPropString(this in JsonElement element, string propertyName) + public static string? GetPropString(this JsonElement element, string propertyName) { return element.TryGetProperty(propertyName, out JsonElement el) ? el.GetString() : null; } @@ -198,7 +216,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ArgumentException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="InvalidOperationException"></exception> - public static TimeSpan GetTimeSpan(this in JsonElement el, TimeParseType type) + public static TimeSpan GetTimeSpan(this JsonElement el, TimeParseType type) { return type switch { diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index ee2677f..5fa381a 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -701,8 +701,12 @@ namespace VNLib.Utils.Memory { throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } - //Round to nearest page - nint np = NearestPage(elements); + //Round to nearest page (in bytes) + nint np = NearestPage(elements * sizeof(T)); + + //Resize to element size + np /= sizeof(T); + return UnsafeAlloc<T>((int)np, zero); } @@ -754,8 +758,12 @@ namespace VNLib.Utils.Memory throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } - //Round to nearest page - nint np = NearestPage(elements); + //Round to nearest page (in bytes) + nint np = NearestPage(elements * sizeof(T)); + + //Resize to element size + np /= sizeof(T); + return SafeAlloc<T>((int)np, zero); } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index 8359f8f..89863aa 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -51,7 +51,7 @@ namespace VNLib.Utils /// <param name="data">Data to be encoded</param> /// <param name="encoding"><see cref="Encoding"/> to encode data with</param> /// <returns>A <see cref="Stream"/> contating the encoded data</returns> - public static VnMemoryStream GetMemoryStream(in ReadOnlySpan<char> data, Encoding encoding) + public static VnMemoryStream GetMemoryStream(ReadOnlySpan<char> data, Encoding encoding) { _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); //Create new memory handle to copy data to @@ -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.MemoryUtil.Shared.Alloc<byte>(byteCount); + handle = MemoryUtil.Shared.Alloc<byte>(byteCount); //encode int size = encoding.GetBytes(data, handle); //Consume the handle into a new vnmemstream and return it @@ -218,6 +218,7 @@ namespace VNLib.Utils ForwardOnlyWriter<char> writer = new(output); return TryToBase32Chars(input, ref writer); } + /// <summary> /// Attempts to convert the specified byte sequence in Base32 encoding /// and writing the encoded data to the output buffer. @@ -229,11 +230,13 @@ namespace VNLib.Utils { //calculate char size int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + //Make sure there is enough room if(charCount > writer.RemainingSize) { return false; } + //sliding window over input buffer ForwardOnlyReader<byte> reader = new(input); @@ -241,17 +244,21 @@ namespace VNLib.Utils { //Convert the current window WriteChars(reader.Window, ref writer); + //shift the window reader.Advance(Math.Min(5, reader.WindowSize)); } return writer.Written; } + private unsafe static void WriteChars(ReadOnlySpan<byte> input, ref ForwardOnlyWriter<char> writer) { //Get the input buffer as long ulong inputAsLong = 0; + //Get a byte pointer over the ulong to index it as a byte buffer byte* buffer = (byte*)&inputAsLong; + //Check proc endianness if (BitConverter.IsLittleEndian) { @@ -271,6 +278,12 @@ namespace VNLib.Utils buffer[i] = input[i]; } } + + /* + * We need to determine how many bytes can be encoded + * and if padding needs to be added + */ + int rounds = (input.Length) switch { 1 => 2, @@ -279,20 +292,26 @@ namespace VNLib.Utils 4 => 7, _ => 8 }; + //Convert each byte segment up to the number of bytes encoded for (int i = 0; i < rounds; i++) { //store the leading byte byte val = buffer[7]; + //right shift the value to lower 5 bits val >>= 3; + //Lookup charcode char base32Char = RFC_4648_BASE32_CHARS[val]; + //append the character to the writer writer.Append(base32Char); + //Shift input left by 5 bits so the next 5 bits can be read inputAsLong <<= 5; } + //Fill remaining bytes with padding chars for(; rounds < 8; rounds++) { @@ -313,6 +332,7 @@ namespace VNLib.Utils ForwardOnlyWriter<byte> writer = new(output); return TryFromBase32Chars(input, ref writer); } + /// <summary> /// Attempts to decode the Base32 encoded string /// </summary> @@ -326,8 +346,10 @@ namespace VNLib.Utils //trim padding characters input = input.Trim('='); + //Calc the number of bytes to write int outputSize = (input.Length * 5) / 8; + //make sure the output buffer is large enough if(writer.RemainingSize < outputSize) { @@ -341,13 +363,17 @@ namespace VNLib.Utils byte* buffer = (byte*)&bufferLong; int count = 0, len = input.Length; + while(count < len) { //Convert the character to its char code byte charCode = GetCharCode(input[count]); + //write byte to buffer buffer[0] |= charCode; + count++; + //If 8 characters have been decoded, reset the buffer if((count % 8) == 0) { @@ -359,18 +385,23 @@ namespace VNLib.Utils //reset bufferLong = 0; } + //left shift the buffer up by 5 bits bufferLong <<= 5; } + //If remaining data has not be written, but has been buffed, finalize it if (writer.Written < outputSize) { //calculate how many bits the buffer still needs to be shifted by (will be 5 bits off because of the previous loop) int remainingShift = (7 - (count % 8)) * 5; + //right shift the buffer by the remaining bit count bufferLong <<= remainingShift; + //calc remaining bytes int remaining = (outputSize - writer.Written); + //Write remaining bytes to the output for(int i = 0; i < remaining; i++) { @@ -379,6 +410,8 @@ namespace VNLib.Utils } return writer.Written; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte GetCharCode(char c) { //cast to byte to get its base 10 value @@ -592,7 +625,7 @@ namespace VNLib.Utils /// <param name="utf8Bytes">The buffer to examine</param> /// <param name="allowedChars">A sequence of characters that are excluded from encoding</param> /// <returns>The size of the buffer required to encode</returns> - public static unsafe int PercentEncodeCalcBufferSize(ReadOnlySpan<byte> utf8Bytes, in ReadOnlySpan<byte> allowedChars = default) + public static unsafe int PercentEncodeCalcBufferSize(ReadOnlySpan<byte> utf8Bytes, ReadOnlySpan<byte> allowedChars = default) { /* * For every illegal character, the percent encoding adds 3 bytes of @@ -607,7 +640,7 @@ namespace VNLib.Utils //Find all unsafe characters and add the entropy size for (int i = 0; i < len; i++) { - if (!IsUrlSafeChar(utfBase[i], in allowedChars)) + if (!IsUrlSafeChar(utfBase[i], allowedChars)) { count += 2; } @@ -625,15 +658,16 @@ namespace VNLib.Utils /// <param name="utf8Output">The buffer to write the encoded characters to</param> /// <param name="allowedChars">A sequence of characters that are excluded from encoding</param> /// <returns>The number of characters encoded and written to the output buffer</returns> - public static ERRNO PercentEncode(ReadOnlySpan<byte> utf8Bytes, Span<byte> utf8Output, in ReadOnlySpan<byte> allowedChars = default) + public static ERRNO PercentEncode(ReadOnlySpan<byte> utf8Bytes, Span<byte> utf8Output, ReadOnlySpan<byte> allowedChars = default) { int outPos = 0, len = utf8Bytes.Length; ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span; + for (int i = 0; i < len; i++) { byte value = utf8Bytes[i]; //Check if value is url safe - if(IsUrlSafeChar(value, in allowedChars)) + if(IsUrlSafeChar(value, allowedChars)) { //Skip utf8Output[outPos++] = value; @@ -652,7 +686,7 @@ namespace VNLib.Utils return outPos; } - private static bool IsUrlSafeChar(byte value, in ReadOnlySpan<byte> allowedChars) + private static bool IsUrlSafeChar(byte value, ReadOnlySpan<byte> allowedChars) { return // base10 digits @@ -683,26 +717,33 @@ namespace VNLib.Utils { int outPos = 0, len = utf8Encoded.Length; ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span; + for (int i = 0; i < len; i++) { byte value = utf8Encoded[i]; + //Begining of percent encoding character if(value == 0x25) { //Calculate the base16 multiplier from the upper half of the int multiplier = lookupTable.IndexOf(utf8Encoded[i + 1]); + //get the base16 lower half to add int lower = lookupTable.IndexOf(utf8Encoded[i + 2]); + //Check format if(multiplier < 0 || lower < 0) { throw new FormatException($"Encoded buffer contains invalid hexadecimal characters following the % character at position {i}"); } + //Calculate the new value, shift multiplier to the upper 4 bits, then mask + or the lower 4 bits value = (byte)(((byte)(multiplier << 4)) | ((byte)lower & 0x0f)); + //Advance the encoded index by the two consumed chars i += 2; } + utf8Output[outPos++] = value; } return outPos; @@ -903,7 +944,7 @@ namespace VNLib.Utils int decodedSize = encoding.GetByteCount(chars); //alloc buffer - using UnsafeMemoryHandle<byte> decodeHandle = Memory.MemoryUtil.UnsafeAlloc<byte>(decodedSize); + using UnsafeMemoryHandle<byte> decodeHandle = 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/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 879c51e..10e5d31 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -1,4 +1,5 @@ -using System; + +using System; using System.Buffers; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -405,5 +406,91 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(postFree.MaxBlockSize == 1024); Assert.IsTrue(postFree.MinBlockSize == 1024); } + + [TestMethod()] + public void NearestPageTest() + { + //Test less than 1 page + const nint TEST_1 = 458; + + nint pageSize = MemoryUtil.NearestPage(TEST_1); + + //Confirm output is the system page size + Assert.IsTrue(pageSize == Environment.SystemPageSize); + + //Test over 1 page + nint TEST_2 = Environment.SystemPageSize + 1; + + pageSize = MemoryUtil.NearestPage(TEST_2); + + //Should be 2 pages + Assert.IsTrue(pageSize == 2 * Environment.SystemPageSize); + + //Exactly one page + pageSize = MemoryUtil.NearestPage(Environment.SystemPageSize); + + Assert.IsTrue(pageSize == Environment.SystemPageSize); + } + + + [TestMethod()] + public void AllocNearestPage() + { + //Simple alloc test + + const int TEST_1 = 1; + + //Unsafe byte test + using (UnsafeMemoryHandle<byte> byteBuffer = MemoryUtil.UnsafeAllocNearestPage<byte>(TEST_1, false)) + { + nuint byteSize = MemoryUtil.ByteSize(byteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize == byteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); + } + + using(IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage<byte>(TEST_1, false)) + { + nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize == safeByteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); + } + + /* + * When using the Int32 a page size of 4096 would yield a space of 1024 Int32, + * so allocating 1025 int32s should cause an overflow to the next page size + */ + const int TEST_2 = 1025; + + //Test for different types + using (UnsafeMemoryHandle<int> byteBuffer = MemoryUtil.UnsafeAllocNearestPage<int>(TEST_2, false)) + { + nuint byteSize = MemoryUtil.ByteSize(byteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize != byteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)(Environment.SystemPageSize * 2)); + } + + using (IMemoryHandle<int> safeByteBuffer = MemoryUtil.SafeAllocNearestPage<int>(TEST_2, false)) + { + nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize != safeByteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)(Environment.SystemPageSize * 2)); + } + } } }
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/VnTableTests.cs b/lib/Utils/tests/Memory/VnTableTests.cs index 06bcb13..474a201 100644 --- a/lib/Utils/tests/Memory/VnTableTests.cs +++ b/lib/Utils/tests/Memory/VnTableTests.cs @@ -56,7 +56,7 @@ namespace VNLib.Utils.Memory.Tests //Test oom, should be native Assert.ThrowsException<OutOfMemoryException>(() => { - using VnTable<int> table = new(uint.MaxValue, 3); + using VnTable<int> table = new(uint.MaxValue, 20); }); } |