diff options
author | vnugent <public@vaughnnugent.com> | 2023-01-27 21:13:05 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-01-27 21:13:05 -0500 |
commit | a5d88f2cf08ea3aad2c8802bdc416e7b40c0f204 (patch) | |
tree | e7b9347717c096ef0ea517769b5136ef8abf9571 /lib | |
parent | 185afcee727027c60257ddda4da974dccb808e5a (diff) |
Object cache overhaul and logger updates
Diffstat (limited to 'lib')
28 files changed, 701 insertions, 283 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index e3822d0..55e31fc 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -47,24 +47,31 @@ namespace VNLib.Hashing.IdentityUtility /// <summary> /// Parses a JWT from a Base64URL encoded character buffer /// </summary> - /// <param name="urlEncJwtString"></param> + /// <param name="urlEncJwtString">The JWT characters to decode</param> /// <param name="heap">An optional <see cref="IUnmangedHeap"/> instance to alloc buffers from</param> + /// <param name="textEncoding">The encoding used to decode the text to binary</param> /// <returns>The parses <see cref="JsonWebToken"/></returns> /// <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, Encoding? textEncoding = null) { heap ??= MemoryUtil.Shared; + textEncoding ??= Encoding.UTF8; + + if (urlEncJwtString.IsEmpty) + { + throw new ArgumentException("The JWT string is empty", nameof(urlEncJwtString)); + } //Calculate the decoded size of the characters to alloc a buffer - int utf8Size = Encoding.UTF8.GetByteCount(urlEncJwtString); + int utf8Size = textEncoding.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); + utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer); //Parse and return the jwt return ParseRaw(binBuffer.Span[..utf8Size], heap); @@ -93,18 +100,23 @@ namespace VNLib.Hashing.IdentityUtility JsonWebToken jwt = new(heap, new (heap, utf8JWTData)); try { - ReadOnlySpan<byte> buffer = jwt.DataBuffer; + ForwardOnlyReader<byte> reader = new(utf8JWTData); + //Search for the first period to indicate the end of the header section - jwt.HeaderEnd = buffer.IndexOf(SAEF_PERIOD); + jwt.HeaderEnd = reader.Window.IndexOf(SAEF_PERIOD); + //Make sure a '.' was found if (jwt.HeaderEnd < 0) { throw new FormatException("The supplied data is not a valid Json Web Token, header end symbol could not be found"); } + //Shift buffer window - buffer = buffer[jwt.PayloadStart..]; + reader.Advance(jwt.PayloadStart); + //Search for next period to end the payload - jwt.PayloadEnd = jwt.PayloadStart + buffer.LastIndexOf(SAEF_PERIOD); + jwt.PayloadEnd = jwt.PayloadStart + reader.Window.LastIndexOf(SAEF_PERIOD); + //Make sure a '.' was found if (jwt.PayloadEnd < 0) { @@ -130,7 +142,7 @@ namespace VNLib.Hashing.IdentityUtility /// The size (in bytes) of the encoded data that makes /// up the current JWT. /// </summary> - public int ByteSize => (int)DataStream.Position; + public int ByteSize => Convert.ToInt32(DataStream.Position); /// <summary> /// A buffer that represents the current state of the JWT buffer. /// Utf8Base64Url encoded data. diff --git a/lib/Net.Http/src/Core/Response/ChunkedStream.cs b/lib/Net.Http/src/Core/Response/ChunkedStream.cs index 953d763..724e28d 100644 --- a/lib/Net.Http/src/Core/Response/ChunkedStream.cs +++ b/lib/Net.Http/src/Core/Response/ChunkedStream.cs @@ -70,7 +70,7 @@ namespace VNLib.Net.Http.Core LastChunk = encoding.GetBytes(LAST_CHUNK_STRING); //get the min buffer by rounding to the nearest page - int actualBufSize = (chunkBufferSize / 4096 + 1) * 4096; + int actualBufSize = (int)MemoryUtil.NearestPage(chunkBufferSize); //Init accumulator ChunckAccumulator = new(encoding, actualBufSize); diff --git a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs index 36ebb66..fb20e68 100644 --- a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs +++ b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs @@ -27,8 +27,6 @@ using System.Runtime.InteropServices; using VNLib.Utils.Memory; - - namespace VNLib.Net.Http.Core { sealed class SharedHeaderReaderBuffer : IHttpLifeCycle @@ -46,6 +44,9 @@ namespace VNLib.Net.Http.Core { _bufferSize = length + (length * sizeof(char)); + //Round to nearest page + _bufferSize = (int)MemoryUtil.NearestPage(_bufferSize); + //Bin buffer is the specified size BinLength = length; } diff --git a/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs b/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs index 102b6c9..9b67143 100644 --- a/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs +++ b/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs @@ -25,6 +25,8 @@ using System; using System.Runtime.CompilerServices; +using VNLib.Utils; + namespace VNLib.Net.Messaging.FBM.Client { @@ -57,6 +59,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// </summary> /// <param name="response"></param> /// <exception cref="InvalidResponseException"></exception> + /// <exception cref="InternalBufferTooSmallException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ThrowIfNotSet(this in FBMResponse response) { @@ -64,6 +67,12 @@ namespace VNLib.Net.Messaging.FBM.Client { throw new InvalidResponseException("The response state is undefined (no response received)"); } + + //Also throw if buffer header buffer size was too small + if(response.StatusFlags == HeaderParseError.HeaderOutOfMem) + { + throw new InternalBufferTooSmallException("The internal header buffer was too small to store response headers"); + } } } } diff --git a/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs new file mode 100644 index 0000000..95a5bb8 --- /dev/null +++ b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs @@ -0,0 +1,169 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Messaging.FBM +* File: FBMBuffer.cs +* +* FBMBuffer.cs is part of VNLib.Net.Messaging.FBM which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Net.Messaging.FBM 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.Net.Messaging.FBM 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 System.Buffers; +using VNLib.Utils.IO; + +namespace VNLib.Net.Messaging.FBM.Client +{ + /// <summary> + /// Represents a shared internal character and bianry buffer for + /// </summary> + internal sealed class FBMBuffer : IDisposable + { + private readonly IMemoryOwner<byte> Handle; + + private readonly BufferWriter _writer; + private readonly BinaryRequestAccumulator _requestAccumulator; + + + internal FBMBuffer(IMemoryOwner<byte> handle) + { + Handle = handle; + _writer = new(this); + _requestAccumulator = new(handle.Memory); + } + + + /// <summary> + /// Gets the internal request data accumulator + /// </summary> + public IDataAccumulator<byte> RequestBuffer => _requestAccumulator; + + /// <summary> + /// Gets the accumulated request data for reading + /// </summary> + /// <returns>The accumulated request data as memory</returns> + public ReadOnlyMemory<byte> RequestData => _requestAccumulator.AccumulatedMemory; + + /// <summary> + /// Completes the header segment and prepares the body writer + /// </summary> + /// <returns>A <see cref="IBufferWriter{T}"/> for writing an FBM message body to</returns> + public IBufferWriter<byte> GetBodyWriter() + { + //complete the header segments by writing an empty line + Helpers.WriteTermination(RequestBuffer); + + //Return the internal writer + return _writer; + } + + /// <summary> + /// Gets the buffer manager for managing response headers + /// </summary> + /// <returns>The <see cref="FBMHeaderBuffer"/> for managing response header buffers</returns> + public FBMHeaderBuffer GetResponseHeaderBuffer() + { + //Get a buffer wrapper around the memory handle + return new FBMHeaderBuffer(Handle.Memory); + } + + public void Dispose() + { + //Dispose handle + Handle.Dispose(); + } + + /// <summary> + /// Resets the request accumulator and writes the initial message id + /// </summary> + /// <param name="messageId">The message id</param> + public void Reset(int messageId) + { + //Reset request header accumulator when complete + _requestAccumulator.Reset(); + + //Write message id to accumulator, it should already be reset + Helpers.WriteMessageid(RequestBuffer, messageId); + } + + private sealed class BinaryRequestAccumulator : IDataAccumulator<byte> + { + private readonly int Size; + private readonly Memory<byte> Buffer; + + internal BinaryRequestAccumulator(Memory<byte> buffer) + { + Buffer = buffer; + Size = buffer.Length; + } + + ///<inheritdoc/> + public int AccumulatedSize { get; private set; } + + ///<inheritdoc/> + public int RemainingSize => Size - AccumulatedSize; + + ///<inheritdoc/> + public Span<byte> Remaining => Buffer.Span.Slice(AccumulatedSize, RemainingSize); + ///<inheritdoc/> + public Span<byte> Accumulated => Buffer.Span[..AccumulatedSize]; + + /// <summary> + /// Gets the accumulated data as a memory segment + /// </summary> + public Memory<byte> AccumulatedMemory => Buffer[..AccumulatedSize]; + + /// <summary> + /// Gets the remaining buffer segment as a memory segment + /// </summary> + public Memory<byte> RemainingMemory => Buffer.Slice(AccumulatedSize, RemainingSize); + + ///<inheritdoc/> + public void Advance(int count) => AccumulatedSize += count; + ///<inheritdoc/> + public void Reset() => AccumulatedSize = 0; + } + + private sealed class BufferWriter : IBufferWriter<byte> + { + private readonly FBMBuffer Buffer; + + public BufferWriter(FBMBuffer buffer) + { + Buffer = buffer; + } + + public void Advance(int count) + { + //Advance the writer + Buffer.RequestBuffer.Advance(count); + } + + public Memory<byte> GetMemory(int sizeHint = 0) + { + //Get the remaining memory segment + return Buffer._requestAccumulator.RemainingMemory; + } + + public Span<byte> GetSpan(int sizeHint = 0) + { + //Get the remaining data segment + return Buffer.RequestBuffer.Remaining; + } + } + } +} diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs index db52c03..c4f9f54 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs @@ -199,7 +199,10 @@ namespace VNLib.Net.Messaging.FBM.Client } try { - Debug("Sending {bytes} with id {id}", request.RequestData.Length, request.MessageId); + //Get the request data segment + ReadOnlyMemory<byte> requestData = request.GetRequestData(); + + Debug("Sending {bytes} with id {id}", requestData.Length, request.MessageId); //Reset the wait handle request.ResponseWaitEvent.Reset(); @@ -208,7 +211,7 @@ namespace VNLib.Net.Messaging.FBM.Client using (SemSlimReleaser releaser = await SendLock.GetReleaserAsync(cancellationToken)) { //Send the data to the server - await ClientSocket.SendAsync(request.RequestData, WebSocketMessageType.Binary, true, cancellationToken); + await ClientSocket.SendAsync(requestData, WebSocketMessageType.Binary, true, cancellationToken); } //wait for the response to be set @@ -253,16 +256,22 @@ namespace VNLib.Net.Messaging.FBM.Client } try { - Debug("Streaming {bytes} with id {id}", request.RequestData.Length, request.MessageId); + //Get the request data segment + ReadOnlyMemory<byte> requestData = request.GetRequestData(); + + Debug("Streaming {bytes} with id {id}", requestData.Length, request.MessageId); + //Reset the wait handle request.ResponseWaitEvent.Reset(); + //Write an empty body in the request request.WriteBody(ReadOnlySpan<byte>.Empty, ct); + //Wait for send-lock using (SemSlimReleaser releaser = await SendLock.GetReleaserAsync(cancellationToken)) { //Send the initial request packet - await ClientSocket.SendAsync(request.RequestData, WebSocketMessageType.Binary, false, cancellationToken); + await ClientSocket.SendAsync(requestData, WebSocketMessageType.Binary, false, cancellationToken); //Calc buffer size int bufSize = (int)Math.Clamp(payload.Length, Config.MessageBufferSize, Config.MaxMessageSize); //Alloc a streaming buffer diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs index 229eb76..c6082f0 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs @@ -36,7 +36,8 @@ namespace VNLib.Net.Messaging.FBM.Client public readonly struct FBMClientConfig { /// <summary> - /// The size (in bytes) of the internal buffer to use when receiving messages from the server + /// The size (in bytes) of the internal buffer used to buffer incomming messages, + /// this value will also be sent to the server to synchronous recv buffer sizes /// </summary> public readonly int RecvBufferSize { get; init; } /// <summary> @@ -48,7 +49,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// </remarks> public readonly int MessageBufferSize { get; init; } /// <summary> - /// The size (in chars) of the client/server message header buffer + /// The size (in bytes) of the client/server message header buffer /// </summary> public readonly int MaxHeaderBufferSize { get; init; } /// <summary> diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs index 0e46582..aaf3926 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs @@ -28,6 +28,7 @@ using System.Buffers; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using System.Runtime.CompilerServices; using VNLib.Net.Http; using VNLib.Utils; @@ -38,61 +39,34 @@ using VNLib.Utils.Memory.Caching; namespace VNLib.Net.Messaging.FBM.Client { + /// <summary> + /// <para> /// A reusable Fixed Buffer Message request container. This class is not thread-safe + /// </para> + /// <para> + /// The internal buffer is used for storing headers, body data (unless streaming) + /// </para> /// </summary> public sealed class FBMRequest : VnDisposeable, IReusable, IFBMMessage, IStringSerializeable { - private sealed class BufferWriter : IBufferWriter<byte> - { - private readonly FBMRequest _request; - - public BufferWriter(FBMRequest request) - { - _request = request; - } - - public void Advance(int count) - { - _request.Position += count; - } - - public Memory<byte> GetMemory(int sizeHint = 0) - { - return sizeHint > 0 ? _request.RemainingBuffer[0..sizeHint] : _request.RemainingBuffer; - } - - public Span<byte> GetSpan(int sizeHint = 0) - { - return sizeHint > 0 ? _request.RemainingBuffer.Span[0..sizeHint] : _request.RemainingBuffer.Span; - } - } - - private readonly IMemoryOwner<byte> HeapBuffer; - - - private readonly BufferWriter _writer; - private int Position; - +#pragma warning disable CA2213 // Disposable fields should be disposed + private readonly FBMBuffer Buffer; +#pragma warning restore CA2213 // Disposable fields should be disposed private readonly Encoding HeaderEncoding; - private readonly int ResponseHeaderBufferSize; - private readonly List<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> ResponseHeaderList = new(); - private char[]? ResponseHeaderBuffer; + + private readonly List<FBMMessageHeader> ResponseHeaderList = new(); /// <summary> /// The size (in bytes) of the request message /// </summary> - public int Length => Position; - private Memory<byte> RemainingBuffer => HeapBuffer.Memory[Position..]; + public int Length => Buffer.RequestBuffer.AccumulatedSize; /// <summary> /// The id of the current request message /// </summary> public int MessageId { get; } - /// <summary> - /// The request message packet - /// </summary> - public ReadOnlyMemory<byte> RequestData => HeapBuffer.Memory[..Position]; + /// <summary> /// An <see cref="ManualResetEvent"/> to signal request/response /// event completion @@ -100,6 +74,7 @@ namespace VNLib.Net.Messaging.FBM.Client internal ManualResetEvent ResponseWaitEvent { get; } internal VnMemoryStream? Response { get; private set; } + /// <summary> /// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size, /// and a random messageid @@ -107,77 +82,71 @@ namespace VNLib.Net.Messaging.FBM.Client /// <param name="config">The fbm client config storing required config variables</param> public FBMRequest(in FBMClientConfig config) : this(Helpers.RandomMessageId, in config) { } + /// <summary> /// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size and a custom MessageId /// </summary> /// <param name="messageId">The custom message id</param> /// <param name="config">The fbm client config storing required config variables</param> public FBMRequest(int messageId, in FBMClientConfig config) - { - //Setup response wait handle but make sure the contuation runs async - ResponseWaitEvent = new(true); - - //Alloc the buffer as a memory owner so a memory buffer can be used - HeapBuffer = config.BufferHeap.DirectAlloc<byte>(config.MessageBufferSize); - - MessageId = messageId; - - HeaderEncoding = config.HeaderEncoding; - ResponseHeaderBufferSize = config.MaxHeaderBufferSize; + :this(messageId, config.BufferHeap, config.MessageBufferSize, config.HeaderEncoding) + { } - WriteMessageId(); - _writer = new(this); - } /// <summary> - /// Resets the internal buffer and writes the message-id header to the begining - /// of the buffer + /// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size and a custom MessageId /// </summary> - private void WriteMessageId() + /// <param name="messageId">The custom message id</param> + /// <param name="heap">The heap to allocate the internal buffer from</param> + /// <param name="bufferSize">The size of the internal buffer</param> + /// <param name="headerEncoding">The encoding instance used for header character encoding</param> + public FBMRequest(int messageId, IUnmangedHeap heap, int bufferSize, Encoding headerEncoding) { - //Get writer over buffer - ForwardOnlyWriter<byte> buffer = new(HeapBuffer.Memory.Span); - //write messageid header to the buffer - buffer.Append((byte)HeaderCommand.MessageId); - buffer.Append(MessageId); - buffer.WriteTermination(); - //Store intial position - Position = buffer.Written; + MessageId = messageId; + HeaderEncoding = headerEncoding; + + //Alloc the buffer as a memory owner so a memory buffer can be used + IMemoryOwner<byte> HeapBuffer = heap.DirectAlloc<byte>(bufferSize); + Buffer = new(HeapBuffer); + + //Setup response wait handle but make sure the contuation runs async + ResponseWaitEvent = new(true); + + //Prepare the message incase the request is fresh + Reset(); } ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) => WriteHeader((byte)header, value); ///<inheritdoc/> - public void WriteHeader(byte header, ReadOnlySpan<char> value) - { - ForwardOnlyWriter<byte> buffer = new(RemainingBuffer.Span); - buffer.WriteHeader(header, value, Helpers.DefaultEncoding); - //Update position - Position += buffer.Written; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteHeader(byte header, ReadOnlySpan<char> value) => Helpers.WriteHeader(Buffer.RequestBuffer, header, value, Helpers.DefaultEncoding); ///<inheritdoc/> public void WriteBody(ReadOnlySpan<byte> body, ContentType contentType = ContentType.Binary) { //Write content type header WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(contentType)); - //Get writer over buffer - ForwardOnlyWriter<byte> buffer = new(RemainingBuffer.Span); //Now safe to write body - buffer.WriteBody(body); - //Update position - Position += buffer.Written; + Helpers.WriteBody(Buffer.RequestBuffer, body); } + /// <summary> /// Returns buffer writer for writing the body data to the internal message buffer /// </summary> - /// <returns>A <see cref="BufferWriter"/> to write message body to</returns> - public IBufferWriter<byte> GetBodyWriter() + /// <returns>A <see cref="IBufferWriter{T}"/> to write message body to</returns> + /// <remarks>Calling this method ends the headers section of the request</remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IBufferWriter<byte> GetBodyWriter() => Buffer.GetBodyWriter(); + + + /// <summary> + /// The request message packet, this may cause side effects + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory<byte> GetRequestData() { - //Write body termination - Helpers.Termination.CopyTo(RemainingBuffer); - Position += Helpers.Termination.Length; - //Return buffer writer - return _writer; + return Buffer.RequestData; } /// <summary> @@ -186,8 +155,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// </summary> public void Reset() { - //Re-writing the message-id will reset the buffer - WriteMessageId(); + Buffer.Reset(MessageId); } internal void SetResponse(VnMemoryStream? vms) @@ -204,20 +172,25 @@ namespace VNLib.Net.Messaging.FBM.Client ///<inheritdoc/> protected override void Free() { - HeapBuffer.Dispose(); + Buffer.Dispose(); ResponseWaitEvent.Dispose(); - OnResponseDisposed(); + Response?.Dispose(); } void IReusable.Prepare() => Reset(); bool IReusable.Release() { + //Make sure response header list is clear + ResponseHeaderList.Clear(); + //Clear old response data if error occured Response?.Dispose(); Response = null; - + return true; } + #region Response + /// <summary> /// Gets the response of the sent message /// </summary> @@ -237,13 +210,12 @@ namespace VNLib.Net.Messaging.FBM.Client * The headers are read into a list of key-value pairs and the stream * is positioned to the start of the message body */ - - - //Alloc rseponse buffer - ResponseHeaderBuffer ??= ArrayPool<char>.Shared.Rent(ResponseHeaderBufferSize); + + //Get the response header buffer for parsing + FBMHeaderBuffer headerBuf = Buffer.GetResponseHeaderBuffer(); //Parse message headers - HeaderParseError statusFlags = Helpers.ParseHeaders(Response, ResponseHeaderBuffer, ResponseHeaderList, HeaderEncoding); + HeaderParseError statusFlags = Helpers.ParseHeaders(Response, in headerBuf, ResponseHeaderList, HeaderEncoding); //return response structure return new(Response, statusFlags, ResponseHeaderList, OnResponseDisposed); @@ -263,19 +235,17 @@ namespace VNLib.Net.Messaging.FBM.Client //Clear old response Response?.Dispose(); Response = null; - - if (ResponseHeaderBuffer != null) - { - //Free response buffer - ArrayPool<char>.Shared.Return(ResponseHeaderBuffer!); - ResponseHeaderBuffer = null; - } } + #endregion + + #region Diagnostics ///<inheritdoc/> public string Compile() { - int charSize = Helpers.DefaultEncoding.GetCharCount(RequestData.Span); + ReadOnlyMemory<byte> requestData = GetRequestData(); + + int charSize = Helpers.DefaultEncoding.GetCharCount(requestData.Span); using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(charSize + 128); @@ -286,10 +256,11 @@ namespace VNLib.Net.Messaging.FBM.Client ///<inheritdoc/> public void Compile(ref ForwardOnlyWriter<char> writer) { + ReadOnlyMemory<byte> requestData = GetRequestData(); writer.Append("Message ID:"); writer.Append(MessageId); writer.Append(Environment.NewLine); - Helpers.DefaultEncoding.GetChars(RequestData.Span, ref writer); + Helpers.DefaultEncoding.GetChars(requestData.Span, ref writer); } ///<inheritdoc/> public ERRNO Compile(in Span<char> buffer) @@ -300,6 +271,7 @@ namespace VNLib.Net.Messaging.FBM.Client } ///<inheritdoc/> public override string ToString() => Compile(); - + + #endregion } } diff --git a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs index da36956..6f8fec4 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs @@ -48,7 +48,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// <summary> /// A collection of response message headers /// </summary> - public readonly IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> Headers { get; } + public readonly IReadOnlyList<FBMMessageHeader> Headers { get; } /// <summary> /// Status flags of the message parse operation /// </summary> @@ -66,7 +66,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// <param name="status">The size of the buffer to alloc for header value storage</param> /// <param name="headerList">The collection of headerse</param> /// <param name="onDispose">A method that will be invoked when the message response body is disposed</param> - public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> headerList, Action onDispose) + public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList<FBMMessageHeader> headerList, Action onDispose) { MessagePacket = vms; StatusFlags = status; @@ -82,7 +82,7 @@ namespace VNLib.Net.Messaging.FBM.Client { MessagePacket = null; StatusFlags = HeaderParseError.InvalidHeaderRead; - Headers = Array.Empty<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>>(); + Headers = Array.Empty<FBMMessageHeader>(); IsSet = false; _onDispose = null; } diff --git a/lib/Net.Messaging.FBM/src/Client/Helpers.cs b/lib/Net.Messaging.FBM/src/Client/Helpers.cs index 054a30b..0b9e7bd 100644 --- a/lib/Net.Messaging.FBM/src/Client/Helpers.cs +++ b/lib/Net.Messaging.FBM/src/Client/Helpers.cs @@ -25,15 +25,16 @@ using System; using System.IO; using System.Text; +using System.Buffers.Binary; using System.Collections.Generic; using System.Security.Cryptography; +using System.Runtime.CompilerServices; using VNLib.Utils; using VNLib.Utils.IO; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; - namespace VNLib.Net.Messaging.FBM { /// <summary> @@ -46,8 +47,15 @@ namespace VNLib.Net.Messaging.FBM /// </summary> public const int CONTROL_FRAME_MID = -500; - public static readonly Encoding DefaultEncoding = Encoding.UTF8; - public static readonly ReadOnlyMemory<byte> Termination = new byte[] { 0xFF, 0xF1 }; + /// <summary> + /// Gets the default header character encoding instance + /// </summary> + public static Encoding DefaultEncoding { get; } = Encoding.UTF8; + + /// <summary> + /// The FBM protocol header line termination symbols + /// </summary> + public static ReadOnlyMemory<byte> Termination { get; } = new byte[] { 0xFF, 0xF1 }; /// <summary> /// Parses the header line for a message-id @@ -61,29 +69,57 @@ namespace VNLib.Net.Messaging.FBM { return -1; } + //The first byte should be the header id HeaderCommand headerId = (HeaderCommand)line[0]; + //Make sure the headerid is set if (headerId != HeaderCommand.MessageId) { return -2; } + //Get the messageid after the header byte ReadOnlySpan<byte> messageIdSegment = line.Slice(1, sizeof(int)); + //get the messageid from the messageid segment - return BitConverter.ToInt32(messageIdSegment); + return BinaryPrimitives.ReadInt32BigEndian(messageIdSegment); } /// <summary> + /// Appends the message id header to the accumulator + /// </summary> + /// <param name="accumulator">The accumulatore to write the message id to</param> + /// <param name="messageid">The message id to write to the accumulator</param> + public static void WriteMessageid(IDataAccumulator<byte> accumulator, int messageid) + { + //Alloc buffer for message id + the message id header + Span<byte> buffer = stackalloc byte[sizeof(int) + 1]; + + //Set 1st byte as message id + buffer[0] = (byte)HeaderCommand.MessageId; + + //Write the message id as a big-endian message + BinaryPrimitives.WriteInt32BigEndian(buffer[1..], messageid); + + //Write the header and message id + the trailing termination + accumulator.Append(buffer); + + WriteTermination(accumulator); + } + + + /// <summary> /// Alloctes a random integer to use as a message id /// </summary> public static int RandomMessageId => RandomNumberGenerator.GetInt32(1, int.MaxValue); - + /// <summary> /// Gets the remaining data after the current position of the stream. /// </summary> /// <param name="response">The stream to segment</param> /// <returns>The remaining data segment</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan<byte> GetRemainingData(VnMemoryStream response) { return response.AsSpan()[(int)response.Position..]; @@ -109,6 +145,7 @@ namespace VNLib.Net.Messaging.FBM //slice up line and exclude the termination return line[..index]; } + /// <summary> /// Parses headers from the request stream, stores headers from the buffer into the /// header collection @@ -118,24 +155,31 @@ namespace VNLib.Net.Messaging.FBM /// <param name="headers">The collection to store headers in</param> /// <param name="encoding">The encoding type used to deocde header values</param> /// <returns>The results of the parse operation</returns> - public static HeaderParseError ParseHeaders(VnMemoryStream vms, char[] buffer, ICollection<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> headers, Encoding encoding) + internal static HeaderParseError ParseHeaders(VnMemoryStream vms, in FBMHeaderBuffer buffer, ICollection<FBMMessageHeader> headers, Encoding encoding) { HeaderParseError status = HeaderParseError.None; - //sliding window - Memory<char> currentWindow = buffer; + + //Get a sliding window writer over the enitre buffer + ForwardOnlyWriter<char> writer = new(buffer.GetSpan()); + //Accumulate headers while (true) { //Read the next line from the current stream ReadOnlySpan<byte> line = ReadLine(vms); + if (line.IsEmpty) { //Done reading headers break; } + + //Read the header command from the next line HeaderCommand cmd = GetHeaderCommand(line); - //Get header value - ERRNO charsRead = GetHeaderValue(line, currentWindow.Span, encoding); + + //Write the next header value from the line to the remaining space in the buffer + ERRNO charsRead = GetHeaderValue(line, writer.Remaining, encoding); + if (charsRead < 0) { //Out of buffer space @@ -149,10 +193,14 @@ namespace VNLib.Net.Messaging.FBM } else { + //Use the writer to capture the offset, and the character size + FBMMessageHeader header = new(buffer, cmd, writer.Written, (int)charsRead); + //Store header as a read-only sequence - headers.Add(new(cmd, currentWindow[..(int)charsRead])); - //Shift buffer window - currentWindow = currentWindow[(int)charsRead..]; + headers.Add(header); + + //Advance the writer + writer.Advance(charsRead); } } return status; @@ -163,10 +211,12 @@ namespace VNLib.Net.Messaging.FBM /// </summary> /// <param name="line"></param> /// <returns>The <see cref="HeaderCommand"/> enum value from hte first byte of the message</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HeaderCommand GetHeaderCommand(ReadOnlySpan<byte> line) { return (HeaderCommand)line[0]; } + /// <summary> /// Gets the value of the header following the colon bytes in the specifed /// data message data line @@ -174,7 +224,7 @@ namespace VNLib.Net.Messaging.FBM /// <param name="line">The message header line to get the value of</param> /// <param name="output">The output character buffer to write characters to</param> /// <param name="encoding">The encoding to decode the specified data with</param> - /// <returns>The number of characters encoded</returns> + /// <returns>The number of characters encoded or -1 if the output buffer is too small</returns> public static ERRNO GetHeaderValue(ReadOnlySpan<byte> line, Span<char> output, Encoding encoding) { //Get the data following the header byte @@ -190,31 +240,7 @@ namespace VNLib.Net.Messaging.FBM _ = encoding.GetChars(value, output); return charCount; } - - /// <summary> - /// Appends an arbitrary header to the current request buffer - /// </summary> - /// <param name="buffer"></param> - /// <param name="header">The <see cref="HeaderCommand"/> of the header</param> - /// <param name="value">The value of the header</param> - /// <param name="encoding">Encoding to use when writing character message</param> - /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void WriteHeader(ref this ForwardOnlyWriter<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding) - { - //get char count - int byteCount = encoding.GetByteCount(value); - //make sure there is enough room in the buffer - if (buffer.RemainingSize < byteCount) - { - throw new ArgumentOutOfRangeException(nameof(value),"The internal buffer is too small to write header"); - } - //Write header command enum value - buffer.Append(header); - //Convert the characters to binary and write to the buffer - encoding.GetBytes(value, ref buffer); - //Write termination (0) - buffer.WriteTermination(); - } + /// <summary> /// Ends the header section of the request and appends the message body to @@ -223,32 +249,38 @@ namespace VNLib.Net.Messaging.FBM /// <param name="buffer"></param> /// <param name="body">The message body to send with request</param> /// <exception cref="OutOfMemoryException"></exception> - public static void WriteBody(ref this ForwardOnlyWriter<byte> buffer, ReadOnlySpan<byte> body) + public static void WriteBody(IDataAccumulator<byte> buffer, ReadOnlySpan<byte> body) { //start with termination - buffer.WriteTermination(); + WriteTermination(buffer); //Write the body buffer.Append(body); } + + /// <summary> - /// Writes a line termination to the message buffer + /// Rounds the requested byte size up to the 1kb + /// number of bytes /// </summary> - /// <param name="buffer"></param> - public static void WriteTermination(ref this ForwardOnlyWriter<byte> buffer) + /// <param name="byteSize">The number of bytes to get the rounded 1kb size of</param> + /// <returns>The number of bytes equivalent to the requested byte size rounded to the next 1kb size</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint ToNearestKb(nint byteSize) { - //write termination - buffer.Append(Termination.Span); + //Get page count by dividing count by number of pages + nint kbs = (nint)Math.Ceiling(byteSize / (double)1024); + + //Multiply back to page sizes + return kbs * 1024; } + /// <summary> /// Writes a line termination to the message buffer /// </summary> /// <param name="buffer"></param> - public static void WriteTermination(this IDataAccumulator<byte> buffer) - { - //write termination - buffer.Append(Termination.Span); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTermination(IDataAccumulator<byte> buffer) => buffer.Append(Termination.Span); /// <summary> /// Appends an arbitrary header to the current request buffer @@ -258,7 +290,7 @@ namespace VNLib.Net.Messaging.FBM /// <param name="value">The value of the header</param> /// <param name="encoding">Encoding to use when writing character message</param> /// <exception cref="ArgumentException"></exception> - public static void WriteHeader(this IDataAccumulator<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding) + public static void WriteHeader(IDataAccumulator<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding) { //Write header command enum value buffer.Append(header); @@ -267,7 +299,7 @@ namespace VNLib.Net.Messaging.FBM //Advance the buffer buffer.Advance(written); //Write termination (0) - buffer.WriteTermination(); + WriteTermination(buffer); } } } diff --git a/lib/Net.Messaging.FBM/src/Client/README.md b/lib/Net.Messaging.FBM/src/Client/README.md index 5aa8e76..35f49ff 100644 --- a/lib/Net.Messaging.FBM/src/Client/README.md +++ b/lib/Net.Messaging.FBM/src/Client/README.md @@ -18,7 +18,7 @@ messages. Headers are identified by a single byte, followed by a variable length encoded character sequence, followed by a termination of 0xFF, 0xF1 (may change). ### Message structure - 4 byte positive (signed 32-bit integer) message id + 4 byte positive (big endian signed 32-bit integer) message id 2 byte termination 1 byte header-id variable length UTF8 value @@ -140,6 +140,9 @@ alter the state of the connection (negotiation etc). A mechanism to do so is pro { //Extension method to raise exception if an invalid response was received (also use the response.IsSet flag) response.ThrowIfNotSet(); + + //Check header parse status + //response.StatusFlags //Check headers (using Linq to get first header) string header1 = response.Headers.First().Value.ToString(); diff --git a/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs b/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs new file mode 100644 index 0000000..42ec2af --- /dev/null +++ b/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Messaging.FBM +* File: FBMHeaderBuffer.cs +* +* FBMHeaderBuffer.cs is part of VNLib.Net.Messaging.FBM which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Net.Messaging.FBM 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.Net.Messaging.FBM 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 System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace VNLib.Net.Messaging.FBM +{ + internal readonly struct FBMHeaderBuffer + { + private readonly Memory<byte> _handle; + + internal FBMHeaderBuffer(Memory<byte> handle) => _handle = handle; + + /// <summary> + /// Gets a character squence within the binary buffer of the specified + /// character offset and length + /// </summary> + /// <param name="offset">The character offset within the internal buffer</param> + /// <param name="count">The number of characters within the desired span</param> + /// <returns>A span at the given character offset and of the specified length</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span<char> GetSpan(int offset, int count) + { + //Get the character span + Span<char> span = GetSpan(); + return span.Slice(offset, count); + } + + /// <summary> + /// Gets the entire internal buffer as a character span + /// </summary> + /// <returns>A span over the entire internal buffer</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span<char> GetSpan() => MemoryMarshal.Cast<byte, char>(_handle.Span); + } +} diff --git a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs new file mode 100644 index 0000000..e0a33ef --- /dev/null +++ b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs @@ -0,0 +1,94 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Net.Messaging.FBM +* File: FBMMessageHeader.cs +* +* FBMMessageHeader.cs is part of VNLib.Net.Messaging.FBM which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Net.Messaging.FBM 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.Net.Messaging.FBM is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + + +namespace VNLib.Net.Messaging.FBM +{ + /// <summary> + /// Represents a Key-Value pair FBM response message header + /// </summary> + public readonly struct FBMMessageHeader : IEquatable<FBMMessageHeader> + { + private readonly FBMHeaderBuffer _buffer; + private readonly int _headerOffset; + private readonly int _headerLength; + + /// <summary> + /// The header value or command + /// </summary> + public readonly HeaderCommand Header { get; } + + /// <summary> + /// The header value + /// </summary> + public readonly ReadOnlySpan<char> Value => _buffer.GetSpan(_headerOffset, _headerLength); + + /// <summary> + /// Gets the header value as a <see cref="string"/> + /// </summary> + /// <returns>The allocates string of the value</returns> + public readonly string GetValueString() => Value.ToString(); + + /// <summary> + /// Initializes a new <see cref="FBMMessageHeader"/> of the sepcified command + /// that utilizes memory from the specified buffer + /// </summary> + /// <param name="buffer">The buffer that owns the memory our header is stored in</param> + /// <param name="command">The command this header represents</param> + /// <param name="offset">The char offset within the buffer our header begins</param> + /// <param name="length">The character length of our header value</param> + internal FBMMessageHeader(FBMHeaderBuffer buffer, HeaderCommand command, int offset, int length) + { + Header = command; + _buffer = buffer; + _headerLength = length; + _headerOffset = offset; + } + + ///<inheritdoc/> + public override bool Equals(object? obj) => obj is FBMMessageHeader other && Equals(other); + + /// <summary> + /// Calculates a hash code from the <see cref="Value"/> parameter + /// using original string hashcode computation + /// </summary> + /// <returns>The unique hashcode for the <see cref="Value"/> character sequence</returns> + public override int GetHashCode() => string.GetHashCode(Value, StringComparison.Ordinal); + + ///<inheritdoc/> + public static bool operator ==(FBMMessageHeader left, FBMMessageHeader right) => left.Equals(right); + ///<inheritdoc/> + public static bool operator !=(FBMMessageHeader left, FBMMessageHeader right) => !(left == right); + + /// <summary> + /// Determines if the other response header is equal to the current header by + /// comparing its command and its value + /// </summary> + /// <param name="other">The other header to compare</param> + /// <returns>True if both headers have the same commad and value sequence</returns> + public bool Equals(FBMMessageHeader other) => Header == other.Header && Value.SequenceEqual(other.Value); + } +} diff --git a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs index fb39d1b..6d5f3bd 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs @@ -35,6 +35,9 @@ namespace VNLib.Net.Messaging.FBM.Server public sealed class FBMContext : IReusable { private readonly Encoding _headerEncoding; + + private readonly IReusable _request; + private readonly IReusable _response; /// <summary> /// The request message to process @@ -54,8 +57,8 @@ namespace VNLib.Net.Messaging.FBM.Server /// <param name="headerEncoding">The message header encoding instance</param> public FBMContext(int requestHeaderBufferSize, int responseBufferSize, Encoding headerEncoding) { - Request = new(requestHeaderBufferSize); - Response = new(responseBufferSize, headerEncoding); + _request = Request = new(requestHeaderBufferSize); + _response = Response = new(responseBufferSize, headerEncoding); _headerEncoding = headerEncoding; } @@ -73,13 +76,13 @@ namespace VNLib.Net.Messaging.FBM.Server void IReusable.Prepare() { - (Request as IReusable).Prepare(); - (Response as IReusable).Prepare(); + _request.Prepare(); + _response.Prepare(); } bool IReusable.Release() { - return (Request as IReusable).Release() & (Response as IReusable).Release(); + return _request.Release() & _response.Release(); } } } diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs index 1388774..3417abc 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs @@ -85,7 +85,6 @@ namespace VNLib.Net.Messaging.FBM.Server Cancellation = new(); Registration = session.Token.Register(Cancellation.Cancel); - ResponseLock = new(1); CtxStore = ObjectRental.CreateReusable(ContextCtor); } @@ -324,7 +323,11 @@ namespace VNLib.Net.Messaging.FBM.Server ValueTask sendTask; //Syncrhonize access to send data because we may need to stream data to the client - await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS); + if (!session.ResponseLock.Wait(0)) + { + await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS); + } + try { do diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs index c327475..ccf79db 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs @@ -38,7 +38,7 @@ namespace VNLib.Net.Messaging.FBM.Server /// </summary> public readonly int RecvBufferSize { get; init; } /// <summary> - /// The size of the character buffer to store FBMheader values in + /// The size of the buffer to store <see cref="FBMMessageHeader"/> values in /// the <see cref="FBMRequestMessage"/> /// </summary> public readonly int MaxHeaderBufferSize { get; init; } diff --git a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs index ed36571..d37ba84 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs @@ -28,9 +28,7 @@ using System.Buffers; using System.Text.Json; using System.Collections.Generic; -using VNLib.Utils; using VNLib.Utils.IO; -using VNLib.Utils.Memory; using VNLib.Utils.Extensions; using VNLib.Utils.Memory.Caching; @@ -41,19 +39,20 @@ namespace VNLib.Net.Messaging.FBM.Server /// </summary> public sealed class FBMRequestMessage : IReusable { - private readonly List<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> _headers; - private readonly int HeaderCharBufferSize; + private readonly List<FBMMessageHeader> _headers; + private readonly int HeaderBufferSize; + /// <summary> /// Creates a new resusable <see cref="FBMRequestMessage"/> /// </summary> /// <param name="headerBufferSize">The size of the buffer to alloc during initialization</param> internal FBMRequestMessage(int headerBufferSize) { - HeaderCharBufferSize = headerBufferSize; + HeaderBufferSize = headerBufferSize; _headers = new(); } - private char[]? _headerBuffer; + private byte[]? _headerBuffer; /// <summary> /// The ID of the current message @@ -70,7 +69,7 @@ namespace VNLib.Net.Messaging.FBM.Server /// <summary> /// A collection of headers for the current request /// </summary> - public IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> Headers => _headers; + public IReadOnlyList<FBMMessageHeader> Headers => _headers; /// <summary> /// Status flags set during the message parsing /// </summary> @@ -95,8 +94,10 @@ namespace VNLib.Net.Messaging.FBM.Server { //Store request body RequestBody = vms; + //Store message id MessageId = Helpers.GetMessageId(Helpers.ReadLine(vms)); + //Check mid for control frame if(MessageId == Helpers.CONTROL_FRAME_MID) { @@ -110,41 +111,11 @@ namespace VNLib.Net.Messaging.FBM.Server ConnectionId = socketId; - //sliding window over remaining data from internal buffer - ForwardOnlyMemoryWriter<char> writer = new(_headerBuffer); - - //Accumulate headers - while (true) - { - //Read the next line from the current stream - ReadOnlySpan<byte> line = Helpers.ReadLine(vms); - if (line.IsEmpty) - { - //Done reading headers - break; - } - HeaderCommand cmd = Helpers.GetHeaderCommand(line); - //Get header value - ERRNO charsRead = Helpers.GetHeaderValue(line, writer.Remaining.Span, dataEncoding); - if (charsRead < 0) - { - //Out of buffer space - ParseStatus |= HeaderParseError.HeaderOutOfMem; - break; - } - else if (!charsRead) - { - //Invalid header - ParseStatus |= HeaderParseError.InvalidHeaderRead; - } - else - { - //Store header as a read-only sequence - _headers.Add(new(cmd, writer.Remaining[..(int)charsRead])); - //Shift buffer window - writer.Advance(charsRead); - } - } + //Get mesage buffer wrapper around the header + FBMHeaderBuffer buffer = new(_headerBuffer); + + //Parse headers + ParseStatus = Helpers.ParseHeaders(vms, in buffer, _headers, dataEncoding); } /// <summary> @@ -158,6 +129,7 @@ namespace VNLib.Net.Messaging.FBM.Server { return BodyData.IsEmpty ? default : BodyData.AsJsonObject<T>(jso); } + /// <summary> /// Gets a <see cref="JsonDocument"/> of the request body /// </summary> @@ -173,7 +145,7 @@ namespace VNLib.Net.Messaging.FBM.Server { ParseStatus = HeaderParseError.None; //Alloc header buffer - _headerBuffer = ArrayPool<char>.Shared.Rent(HeaderCharBufferSize); + _headerBuffer = ArrayPool<byte>.Shared.Rent(HeaderBufferSize); } @@ -185,7 +157,7 @@ namespace VNLib.Net.Messaging.FBM.Server //Clear headers before freeing buffer _headers.Clear(); //Free header-buffer - ArrayPool<char>.Shared.Return(_headerBuffer!); + ArrayPool<byte>.Shared.Return(_headerBuffer!); _headerBuffer = null; ConnectionId = null; MessageId = 0; diff --git a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs index ac34dda..9ca6b4d 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs @@ -52,7 +52,7 @@ namespace VNLib.Net.Messaging.FBM.Server private readonly ISlindingWindowBuffer<byte> _headerAccumulator; private readonly Encoding _headerEncoding; - private IAsyncMessageBody? _messageBody; + private IAsyncMessageBody? MessageBody; ///<inheritdoc/> public int MessageId { get; private set; } @@ -67,7 +67,7 @@ namespace VNLib.Net.Messaging.FBM.Server //Release header accumulator _headerAccumulator.Close(); - _messageBody = null; + MessageBody = null; MessageId = 0; @@ -83,31 +83,26 @@ namespace VNLib.Net.Messaging.FBM.Server { //Reset accumulator when message id is written _headerAccumulator.Reset(); - //Write the messageid to the begining of the headers buffer + MessageId = messageId; - _headerAccumulator.Append((byte)HeaderCommand.MessageId); - _headerAccumulator.Append(messageId); - _headerAccumulator.WriteTermination(); + + //Write the messageid to the begining of the headers buffer + Helpers.WriteMessageid(_headerAccumulator, messageId); } ///<inheritdoc/> - public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) - { - WriteHeader((byte)header, value); - } + public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) => WriteHeader((byte)header, value); + ///<inheritdoc/> - public void WriteHeader(byte header, ReadOnlySpan<char> value) - { - _headerAccumulator.WriteHeader(header, value, _headerEncoding); - } - + public void WriteHeader(byte header, ReadOnlySpan<char> value) => Helpers.WriteHeader(_headerAccumulator, header, value, _headerEncoding); + ///<inheritdoc/> public void WriteBody(ReadOnlySpan<byte> body, ContentType contentType = ContentType.Binary) { //Append content type header WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(contentType)); //end header segment - _headerAccumulator.WriteTermination(); + Helpers.WriteTermination(_headerAccumulator); //Write message body _headerAccumulator.Append(body); } @@ -119,7 +114,9 @@ namespace VNLib.Net.Messaging.FBM.Server /// <exception cref="InvalidOperationException"></exception> public void AddMessageBody(IAsyncMessageBody messageBody) { - if(_messageBody != null) + _ = messageBody ?? throw new ArgumentNullException(nameof(messageBody)); + + if(MessageBody != null) { throw new InvalidOperationException("The message body is already set"); } @@ -128,11 +125,10 @@ namespace VNLib.Net.Messaging.FBM.Server WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(messageBody.ContentType)); //end header segment - _headerAccumulator.WriteTermination(); + Helpers.WriteTermination(_headerAccumulator); //Store message body - _messageBody = messageBody; - + MessageBody = messageBody; } /// <summary> @@ -143,10 +139,11 @@ namespace VNLib.Net.Messaging.FBM.Server internal async ValueTask<IAsyncMessageReader> GetResponseDataAsync(CancellationToken cancellationToken) { //try to buffer as much data in the header segment first - if(_messageBody?.RemainingSize > 0 && _headerAccumulator.RemainingSize > 0) + if(MessageBody?.RemainingSize > 0 && _headerAccumulator.RemainingSize > 0) { //Read data from the message - int read = await _messageBody.ReadAsync(_headerAccumulator.RemainingBuffer, cancellationToken); + int read = await MessageBody.ReadAsync(_headerAccumulator.RemainingBuffer, cancellationToken); + //Advance accumulator to the read bytes _headerAccumulator.Advance(read); } @@ -178,23 +175,23 @@ namespace VNLib.Net.Messaging.FBM.Server Current = _message._headerAccumulator.AccumulatedBuffer; //Update data remaining flag - DataRemaining = _message._messageBody?.RemainingSize > 0; + DataRemaining = _message.MessageBody?.RemainingSize > 0; //Set headers read flag HeadersRead = true; return true; } - else if (_message._messageBody?.RemainingSize > 0) + else if (_message.MessageBody?.RemainingSize > 0) { //Use the header buffer as the buffer for the message body Memory<byte> buffer = _message._headerAccumulator.Buffer; //Read body segment - int read = await _message._messageBody.ReadAsync(buffer); + int read = await _message.MessageBody.ReadAsync(buffer); //Update data remaining flag - DataRemaining = _message._messageBody.RemainingSize > 0; + DataRemaining = _message.MessageBody.RemainingSize > 0; if (read > 0) { @@ -206,7 +203,7 @@ namespace VNLib.Net.Messaging.FBM.Server return false; } - public async ValueTask DisposeAsync() + public ValueTask DisposeAsync() { //Clear current segment Current = default; @@ -215,9 +212,13 @@ namespace VNLib.Net.Messaging.FBM.Server HeadersRead = false; //Dispose the message body if set - if (_message._messageBody != null) + if (_message.MessageBody != null) + { + return _message.MessageBody.DisposeAsync(); + } + else { - await _message._messageBody.DisposeAsync(); + return ValueTask.CompletedTask; } } } diff --git a/lib/Net.Messaging.FBM/src/Server/readme.md b/lib/Net.Messaging.FBM/src/Server/readme.md index 489e58f..68eb505 100644 --- a/lib/Net.Messaging.FBM/src/Server/readme.md +++ b/lib/Net.Messaging.FBM/src/Server/readme.md @@ -14,7 +14,7 @@ messages. Headers are identified by a single byte, followed by a variable length encoded character sequence, followed by a termination of 0xFF, 0xF1 (may change). ### Message structure - 4 byte positive (signed 32-bit integer) message id + 4 byte positive (big endian signed 32-bit integer) message id 2 byte termination 1 byte header-id variable length UTF8 value diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs index 416b004..63e61f7 100644 --- a/lib/Plugins.Essentials/src/HttpEntity.cs +++ b/lib/Plugins.Essentials/src/HttpEntity.cs @@ -57,6 +57,7 @@ namespace VNLib.Plugins.Essentials /// The connection event entity /// </summary> private readonly IHttpEvent Entity; + public HttpEntity(IHttpEvent entity, EventProcessor root, in SessionHandle session, in CancellationToken cancellation) { Entity = entity; diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs index 6a974e0..fa7f8b7 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs @@ -138,7 +138,7 @@ namespace VNLib.Plugins.Essentials.Sessions /// Gets or sets the user-id for the current session. /// </para> /// <para> - /// Login code usually sets this value and it should be read-only + /// Login routines usually set this value and it should be read-only /// </para> /// </summary> public readonly string UserID diff --git a/lib/Plugins.PluginBase/src/VLogProvider.cs b/lib/Plugins.PluginBase/src/VLogProvider.cs index d95725f..4ec05c2 100644 --- a/lib/Plugins.PluginBase/src/VLogProvider.cs +++ b/lib/Plugins.PluginBase/src/VLogProvider.cs @@ -24,6 +24,7 @@ using System; using System.Linq; +using System.Runtime.CompilerServices; using Serilog; using Serilog.Core; @@ -46,21 +47,31 @@ namespace VNLib.Plugins /// </summary> /// <param name="config">Configuration to generate the logger from</param> public VLogProvider(LoggerConfiguration config) => LogCore = config.CreateLogger(); + ///<inheritdoc/> - public void Flush() { } + public void Flush() {} + ///<inheritdoc/> public object GetLogProvider() => LogCore; + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEnabled(LogLevel level) => LogCore.IsEnabled((LogEventLevel)level); + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(LogLevel level, string value) { LogCore.Write((LogEventLevel)level, value); } ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(LogLevel level, Exception exception, string value = "") { LogCore.Write((LogEventLevel)level, exception, value); } ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(LogLevel level, string value, params object[] args) { LogCore.Write((LogEventLevel)level, value, args); diff --git a/lib/Utils/src/Logging/ILogProvider.cs b/lib/Utils/src/Logging/ILogProvider.cs index 55dbd6f..3fce8b6 100644 --- a/lib/Utils/src/Logging/ILogProvider.cs +++ b/lib/Utils/src/Logging/ILogProvider.cs @@ -70,6 +70,14 @@ namespace VNLib.Utils.Logging /// </summary> /// <returns>The underlying log source</returns> object GetLogProvider(); + + /// <summary> + /// Checks if the desired log level is enabled. + /// </summary> + /// <param name="level">The log level to check</param> + /// <returns>True if the log level is available to write logs to, false if the level is not available to write to</returns> + bool IsEnabled(LogLevel level); + /// <summary> /// Gets the underlying log source /// </summary> diff --git a/lib/Utils/src/Memory/Caching/LRUCache.cs b/lib/Utils/src/Memory/Caching/LRUCache.cs index 7e96e0a..6cbb425 100644 --- a/lib/Utils/src/Memory/Caching/LRUCache.cs +++ b/lib/Utils/src/Memory/Caching/LRUCache.cs @@ -56,17 +56,17 @@ namespace VNLib.Utils.Memory.Caching /// Adds a new record to the LRU cache /// </summary> /// <param name="item">A <see cref="KeyValuePair{TKey, TValue}"/> to add to the cache store</param> - public override void Add(KeyValuePair<TKey, TValue> item) + public override void Add(in KeyValuePair<TKey, TValue> item) { //See if the store is at max capacity and an item needs to be evicted - if(Count == MaxCapacity) + if (Count == MaxCapacity) { //A record needs to be evicted before a new record can be added //Get the oldest node from the list to reuse its instance and remove the old value LinkedListNode<KeyValuePair<TKey, TValue>> oldNode = List.First!; //not null because count is at max capacity so an item must be at the end of the list //Store old node value field - KeyValuePair<TKey, TValue> oldRecord = oldNode.Value; + ref KeyValuePair<TKey, TValue> oldRecord = ref oldNode.ValueRef; //Remove from lookup LookupTable.Remove(oldRecord.Key); //Remove the node @@ -78,14 +78,15 @@ namespace VNLib.Utils.Memory.Caching //Add to end of list List.AddLast(oldNode); //Invoke evicted method - Evicted(oldRecord); + Evicted(ref oldRecord); } else { //Add new item to the list - base.Add(item); + base.Add(in item); } } + /// <summary> /// Attempts to get a value by the given key. /// </summary> @@ -115,7 +116,7 @@ namespace VNLib.Utils.Memory.Caching /// Invoked when a record is evicted from the cache /// </summary> /// <param name="evicted">The record that is being evicted</param> - protected abstract void Evicted(KeyValuePair<TKey, TValue> evicted); + protected abstract void Evicted(ref KeyValuePair<TKey, TValue> evicted); /// <summary> /// Invoked when an entry was requested and was not found in cache. /// </summary> diff --git a/lib/Utils/src/Memory/Caching/LRUDataStore.cs b/lib/Utils/src/Memory/Caching/LRUDataStore.cs index f564fcc..89d7c12 100644 --- a/lib/Utils/src/Memory/Caching/LRUDataStore.cs +++ b/lib/Utils/src/Memory/Caching/LRUDataStore.cs @@ -44,7 +44,7 @@ namespace VNLib.Utils.Memory.Caching protected Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> LookupTable { get; } /// <summary> /// A linked list that tracks the least recently used item. - /// New items (or recently access items) are moved to the end of the list. + /// New items (or recently accessed items) are moved to the end of the list. /// The head contains the least recently used item /// </summary> protected LinkedList<KeyValuePair<TKey, TValue>> List { get; } @@ -57,6 +57,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(); List = new(); } + /// <summary> /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and sets /// the lookup table's inital capacity @@ -67,6 +68,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(initialCapacity); List = new(); } + /// <summary> /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the /// specified keycomparison @@ -77,6 +79,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(keyComparer); List = new(); } + /// <summary> /// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the /// specified keycomparison, and sets the lookup table's initial capacity @@ -110,8 +113,11 @@ namespace VNLib.Utils.Memory.Caching { //Remove the node before re-adding it List.Remove(oldNode); - oldNode.Value = new KeyValuePair<TKey, TValue>(key, value); - //Move the item to the front of the list + + //Reuse the node + oldNode.ValueRef = new KeyValuePair<TKey, TValue>(key, value); + + //Move the item to the back of the list List.AddLast(oldNode); } else @@ -123,9 +129,12 @@ namespace VNLib.Utils.Memory.Caching } ///<inheritdoc/> public ICollection<TKey> Keys => LookupTable.Keys; - ///<inheritdoc/> + + ///<summary> + /// Not supported + /// </summary> ///<exception cref="NotImplementedException"></exception> - public ICollection<TValue> Values => throw new NotImplementedException(); + public virtual ICollection<TValue> Values => throw new NotSupportedException("Values are not stored in an independent collection, as they are not directly mutable"); IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => LookupTable.Keys; IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => List.Select(static node => node.Value); IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => List.Select(static node => node.Value).GetEnumerator(); @@ -134,6 +143,7 @@ namespace VNLib.Utils.Memory.Caching /// Gets the number of items within the LRU store /// </summary> public int Count => List.Count; + ///<inheritdoc/> public abstract bool IsReadOnly { get; } @@ -147,10 +157,11 @@ namespace VNLib.Utils.Memory.Caching //Create new kvp lookup ref KeyValuePair<TKey, TValue> lookupRef = new(key, value); //Insert the lookup - Add(lookupRef); + Add(in lookupRef); } + ///<inheritdoc/> - public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key); + public bool Remove(in KeyValuePair<TKey, TValue> item) => Remove(item.Key); ///<inheritdoc/> IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator(); ///<inheritdoc/> @@ -164,7 +175,7 @@ namespace VNLib.Utils.Memory.Caching /// Adds the specified record to the store and places it at the end of the LRU queue /// </summary> /// <param name="item">The item to add</param> - public virtual void Add(KeyValuePair<TKey, TValue> item) + public virtual void Add(in KeyValuePair<TKey, TValue> item) { //Init new ll node LinkedListNode<KeyValuePair<TKey, TValue>> newNode = new(item); @@ -173,6 +184,7 @@ namespace VNLib.Utils.Memory.Caching //Add to the end of the linked list List.AddLast(newNode); } + /// <summary> /// Removes all elements from the LRU store /// </summary> @@ -187,7 +199,7 @@ namespace VNLib.Utils.Memory.Caching /// </summary> /// <param name="item">The record to search for</param> /// <returns>True if the key was found in the store and the value equals the stored value, false otherwise</returns> - public virtual bool Contains(KeyValuePair<TKey, TValue> item) + public virtual bool Contains(in KeyValuePair<TKey, TValue> item) { if (LookupTable.TryGetValue(item.Key, out LinkedListNode<KeyValuePair<TKey, TValue>>? lookup)) { @@ -227,6 +239,17 @@ namespace VNLib.Utils.Memory.Caching value = default; return false; } - + + /// <summary> + /// Adds the specified record to the store and places it at the end of the LRU queue + /// </summary> + /// <param name="item">The item to add</param> + public virtual void Add(KeyValuePair<TKey, TValue> item) => Add(in item); + + ///<inheritdoc/> + public virtual bool Contains(KeyValuePair<TKey, TValue> item) => Contains(in item); + + ///<inheritdoc/> + public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(in item); } } diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index df2792b..7a6b4ef 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -92,7 +92,7 @@ namespace VNLib.Utils.Memory /// Number of bytes allocated to the current instance /// </summary> /// <exception cref="OverflowException"></exception> - public unsafe ulong ByteLength + public nuint ByteLength { //Check for overflows when converting to bytes (should run out of memory before this is an issue, but just incase) [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 56ccb7e..2d51d2f 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -588,6 +588,39 @@ namespace VNLib.Utils.Memory #region alloc + + /// <summary> + /// Rounds the requested byte size up to the nearest page + /// number of bytes + /// </summary> + /// <param name="byteSize">The number of bytes to get the rounded page size of</param> + /// <returns>The number of bytes equivalent to the requested byte size rounded to the next system memory page</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint NearestPage(nuint byteSize) + { + //Get page count by dividing count by number of pages + nuint pages = (uint)Math.Ceiling(byteSize / (double)Environment.SystemPageSize); + + //Multiply back to page sizes + return pages * (nuint)Environment.SystemPageSize; + } + + /// <summary> + /// Rounds the requested byte size up to the nearest page + /// number of bytes + /// </summary> + /// <param name="byteSize">The number of bytes to get the rounded page size of</param> + /// <returns>The number of bytes equivalent to the requested byte size rounded to the next system memory page</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint NearestPage(nint byteSize) + { + //Get page count by dividing count by number of pages + nint pages = (int)Math.Ceiling(byteSize / (double)Environment.SystemPageSize); + + //Multiply back to page sizes + return pages * Environment.SystemPageSize; + } + /// <summary> /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. diff --git a/lib/Utils/tests/VNLib.UtilsTests.csproj b/lib/Utils/tests/VNLib.UtilsTests.csproj index 89b3124..3a079c6 100644 --- a/lib/Utils/tests/VNLib.UtilsTests.csproj +++ b/lib/Utils/tests/VNLib.UtilsTests.csproj @@ -20,9 +20,9 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.10" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="3.0.2" /> + <PackageReference Include="MSTest.TestFramework" Version="3.0.2" /> <PackageReference Include="coverlet.collector" Version="3.2.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |