aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Messaging.FBM
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-27 21:13:05 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-27 21:13:05 -0500
commita5d88f2cf08ea3aad2c8802bdc416e7b40c0f204 (patch)
treee7b9347717c096ef0ea517769b5136ef8abf9571 /lib/Net.Messaging.FBM
parent185afcee727027c60257ddda4da974dccb808e5a (diff)
Object cache overhaul and logger updates
Diffstat (limited to 'lib/Net.Messaging.FBM')
-rw-r--r--lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs9
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs169
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClient.cs17
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs5
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMRequest.cs180
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMResponse.cs6
-rw-r--r--lib/Net.Messaging.FBM/src/Client/Helpers.cs138
-rw-r--r--lib/Net.Messaging.FBM/src/Client/README.md5
-rw-r--r--lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs60
-rw-r--r--lib/Net.Messaging.FBM/src/FBMMessageHeader.cs94
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMContext.cs13
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListener.cs7
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs2
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs60
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs59
-rw-r--r--lib/Net.Messaging.FBM/src/Server/readme.md2
16 files changed, 577 insertions, 249 deletions
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