diff --git a/source/NetCoreServer/Buffer.cs b/source/NetCoreServer/Buffer.cs
index 6beaf66..12ee756 100644
--- a/source/NetCoreServer/Buffer.cs
+++ b/source/NetCoreServer/Buffer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics;
using System.Text;
@@ -123,8 +123,10 @@ public void Reserve(long capacity)
if (capacity > Capacity)
{
- byte[] data = new byte[Math.Max(capacity, 2 * Capacity)];
- Array.Copy(_data, 0, data, 0, _size);
+ long newCapacity = Math.Max(capacity, Math.Max(256, 2 * Capacity));
+ byte[] data = new byte[newCapacity];
+ if (_size > 0)
+ Array.Copy(_data, 0, data, 0, _size);
_data = data;
}
}
@@ -173,6 +175,8 @@ public long Append(byte value)
/// Count of append bytes
public long Append(byte[] buffer)
{
+ if (buffer == null || buffer.Length == 0)
+ return 0;
Reserve(_size + buffer.Length);
Array.Copy(buffer, 0, _data, _size, buffer.Length);
_size += buffer.Length;
@@ -188,8 +192,10 @@ public long Append(byte[] buffer)
/// Count of append bytes
public long Append(byte[] buffer, long offset, long size)
{
+ if (buffer == null || size <= 0)
+ return 0;
Reserve(_size + size);
- Array.Copy(buffer, offset, _data, _size, size);
+ Array.Copy(buffer, (int)offset, _data, (int)_size, (int)size);
_size += size;
return size;
}
@@ -201,6 +207,8 @@ public long Append(byte[] buffer, long offset, long size)
/// Count of append bytes
public long Append(ReadOnlySpan buffer)
{
+ if (buffer.IsEmpty)
+ return 0;
Reserve(_size + buffer.Length);
buffer.CopyTo(new Span(_data, (int)_size, buffer.Length));
_size += buffer.Length;
@@ -212,7 +220,7 @@ public long Append(ReadOnlySpan buffer)
///
/// Buffer to append
/// Count of append bytes
- public long Append(Buffer buffer) => Append(buffer.AsSpan());
+ public long Append(Buffer buffer) => buffer != null ? Append(buffer.AsSpan()) : 0;
///
/// Append the given text in UTF-8 encoding
@@ -221,6 +229,8 @@ public long Append(ReadOnlySpan buffer)
/// Count of append bytes
public long Append(string text)
{
+ if (string.IsNullOrEmpty(text))
+ return 0;
int length = Encoding.UTF8.GetMaxByteCount(text.Length);
Reserve(_size + length);
long result = Encoding.UTF8.GetBytes(text, 0, text.Length, _data, (int)_size);
@@ -235,6 +245,8 @@ public long Append(string text)
/// Count of append bytes
public long Append(ReadOnlySpan text)
{
+ if (text.IsEmpty)
+ return 0;
int length = Encoding.UTF8.GetMaxByteCount(text.Length);
Reserve(_size + length);
long result = Encoding.UTF8.GetBytes(text, new Span(_data, (int)_size, length));
diff --git a/source/NetCoreServer/HttpRequest.cs b/source/NetCoreServer/HttpRequest.cs
index 8006308..207b69b 100644
--- a/source/NetCoreServer/HttpRequest.cs
+++ b/source/NetCoreServer/HttpRequest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
@@ -87,7 +87,15 @@ public HttpRequest(string method, string url, string protocol = "HTTP/1.1")
///
/// Get the HTTP request body as byte array
///
- public byte[] BodyBytes { get { return _cache.Data[_bodyIndex..(_bodyIndex + _bodySize)]; } }
+ public byte[] BodyBytes
+ {
+ get
+ {
+ var result = new byte[_bodySize];
+ Array.Copy(_cache.Data, _bodyIndex, result, 0, _bodySize);
+ return result;
+ }
+ }
///
/// Get the HTTP request body as byte span
///
diff --git a/source/NetCoreServer/HttpResponse.cs b/source/NetCoreServer/HttpResponse.cs
index 56355f0..d3270c8 100644
--- a/source/NetCoreServer/HttpResponse.cs
+++ b/source/NetCoreServer/HttpResponse.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
@@ -156,7 +156,15 @@ public HttpResponse(int status, string statusPhrase, string protocol)
///
/// Get the HTTP request body as byte array
///
- public byte[] BodyBytes { get { return _cache.Data[_bodyIndex..(_bodyIndex + _bodySize)]; } }
+ public byte[] BodyBytes
+ {
+ get
+ {
+ var result = new byte[_bodySize];
+ Array.Copy(_cache.Data, _bodyIndex, result, 0, _bodySize);
+ return result;
+ }
+ }
///
/// Get the HTTP request body as read-only byte span
///
diff --git a/source/NetCoreServer/NetCoreServer.csproj b/source/NetCoreServer/NetCoreServer.csproj
index fa73e8b..36e2eb6 100644
--- a/source/NetCoreServer/NetCoreServer.csproj
+++ b/source/NetCoreServer/NetCoreServer.csproj
@@ -2,7 +2,7 @@
net10.0
- 10.0.0.0
+ 10.0.1.0
Ivan Shynkarenka
Copyright (c) 2019-2026 Ivan Shynkarenka
https://github.com/chronoxor/NetCoreServer
diff --git a/source/NetCoreServer/WebSocket.cs b/source/NetCoreServer/WebSocket.cs
index 17eea10..6445d22 100644
--- a/source/NetCoreServer/WebSocket.cs
+++ b/source/NetCoreServer/WebSocket.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Generic;
@@ -45,6 +45,8 @@ public class WebSocket : IWebSocket
///
public const byte WS_PONG = 0x0A;
+ private const string WsGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
///
/// Perform WebSocket client upgrade
///
@@ -93,18 +95,16 @@ public bool PerformClientUpgrade(HttpResponse response, Guid id)
else if (string.Compare(key, "Sec-WebSocket-Accept", StringComparison.OrdinalIgnoreCase) == 0)
{
// Calculate the original WebSocket hash
- string wskey = Convert.ToBase64String(WsNonce) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- string wshash;
+ string wskey = Convert.ToBase64String(WsNonce) + WsGuid;
+ string expectedAccept;
using (SHA1 sha1 = SHA1.Create())
{
- wshash = Encoding.UTF8.GetString(sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey)));
+ byte[] wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
+ expectedAccept = Convert.ToBase64String(wshash);
}
- // Get the received WebSocket hash
- wskey = Encoding.UTF8.GetString(Convert.FromBase64String(value));
-
// Compare original and received hashes
- if (string.Compare(wskey, wshash, StringComparison.InvariantCulture) != 0)
+ if (string.Compare(value.Trim(), expectedAccept, StringComparison.Ordinal) != 0)
{
error = true;
_wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Sec-WebSocket-Accept' value validation failed");
@@ -189,15 +189,13 @@ public bool PerformServerUpgrade(HttpRequest request, HttpResponse response)
}
// Calculate the original WebSocket hash
- string wskey = value + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- byte[] wshash;
+ string wskey = value.Trim() + WsGuid;
using (SHA1 sha1 = SHA1.Create())
{
- wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
+ byte[] wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
+ accept = Convert.ToBase64String(wshash);
}
- accept = Convert.ToBase64String(wshash);
-
wsKey = true;
}
else if (string.Compare(key, "Sec-WebSocket-Version", StringComparison.OrdinalIgnoreCase) == 0)
@@ -245,12 +243,45 @@ public bool PerformServerUpgrade(HttpRequest request, HttpResponse response)
// WebSocket successfully handshaked!
WsHandshaked = true;
- Array.Fill(WsSendMask, (byte)0);
+ Array.Clear(WsSendMask, 0, WsSendMask.Length);
_wsHandler.OnWsConnected(request);
return true;
}
+ ///
+ /// Fast masking / unmasking helper
+ ///
+ public static void MaskBuffer(ReadOnlySpan source, byte[] destination, int destOffset, byte[] mask, int maskOffset = 0)
+ {
+ int length = source.Length;
+ if (length == 0)
+ return;
+
+ int i = 0;
+ // Process in 4-byte chunks when mask offset aligns with 4
+ if ((maskOffset % 4) == 0 && length >= 4)
+ {
+ uint mask32 = (uint)(mask[0] | (mask[1] << 8) | (mask[2] << 16) | (mask[3] << 24));
+ int count4 = length & ~3;
+ while (i < count4)
+ {
+ uint val = (uint)(source[i] | (source[i + 1] << 8) | (source[i + 2] << 16) | (source[i + 3] << 24));
+ val ^= mask32;
+ destination[destOffset + i + 0] = (byte)(val & 0xFF);
+ destination[destOffset + i + 1] = (byte)((val >> 8) & 0xFF);
+ destination[destOffset + i + 2] = (byte)((val >> 16) & 0xFF);
+ destination[destOffset + i + 3] = (byte)((val >> 24) & 0xFF);
+ i += 4;
+ }
+ }
+
+ for (; i < length; i++)
+ {
+ destination[destOffset + i] = (byte)(source[i] ^ mask[(i + maskOffset) % 4]);
+ }
+ }
+
///
/// Prepare WebSocket send frame
///
@@ -305,13 +336,31 @@ public void PrepareSendFrame(byte opcode, bool mask, ReadOnlySpan buffer,
if (storeStatus)
{
index += 2;
- WsSendBuffer.Data[offset + 0] = (byte)(((status >> 8) & 0xFF) ^ WsSendMask[0]);
- WsSendBuffer.Data[offset + 1] = (byte)((status & 0xFF) ^ WsSendMask[1]);
+ if (mask)
+ {
+ WsSendBuffer.Data[offset + 0] = (byte)(((status >> 8) & 0xFF) ^ WsSendMask[0]);
+ WsSendBuffer.Data[offset + 1] = (byte)((status & 0xFF) ^ WsSendMask[1]);
+ }
+ else
+ {
+ WsSendBuffer.Data[offset + 0] = (byte)((status >> 8) & 0xFF);
+ WsSendBuffer.Data[offset + 1] = (byte)(status & 0xFF);
+ }
}
- // Mask WebSocket frame content
- for (int i = index; i < size; i++)
- WsSendBuffer.Data[offset + i] = (byte)(buffer[i - index] ^ WsSendMask[i % 4]);
+ if (!mask)
+ {
+ // Fast path for server-to-client frames (unmasked): direct memory copy
+ if (buffer.Length > 0)
+ {
+ buffer.CopyTo(new Span(WsSendBuffer.Data, (int)(offset + index), buffer.Length));
+ }
+ }
+ else
+ {
+ // Mask WebSocket frame content
+ MaskBuffer(buffer, WsSendBuffer.Data, (int)(offset + index), WsSendMask, index % 4);
+ }
}
///
@@ -361,12 +410,13 @@ public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
// Prepare WebSocket frame opcode and mask flag
if (WsReceiveFrameBuffer.Size < 2)
{
- for (long i = 0; i < 2; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
+ long need = 2 - WsReceiveFrameBuffer.Size;
+ long take = Math.Min(need, size);
+ WsReceiveFrameBuffer.Append(buffer, (int)offset + index, (int)take);
+ index += (int)take;
+ size -= take;
+ if (WsReceiveFrameBuffer.Size < 2)
+ return;
}
byte opcode = (byte)(WsReceiveFrameBuffer[0] & 0x0F);
@@ -387,12 +437,13 @@ public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
{
if (WsReceiveFrameBuffer.Size < 4)
{
- for (long i = 0; i < 2; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
+ long need = 4 - WsReceiveFrameBuffer.Size;
+ long take = Math.Min(need, size);
+ WsReceiveFrameBuffer.Append(buffer, (int)offset + index, (int)take);
+ index += (int)take;
+ size -= take;
+ if (WsReceiveFrameBuffer.Size < 4)
+ return;
}
payload = (((long)WsReceiveFrameBuffer[2] << 8) | ((long)WsReceiveFrameBuffer[3] << 0));
@@ -403,12 +454,13 @@ public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
{
if (WsReceiveFrameBuffer.Size < 10)
{
- for (long i = 0; i < 8; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- }
+ long need = 10 - WsReceiveFrameBuffer.Size;
+ long take = Math.Min(need, size);
+ WsReceiveFrameBuffer.Append(buffer, (int)offset + index, (int)take);
+ index += (int)take;
+ size -= take;
+ if (WsReceiveFrameBuffer.Size < 10)
+ return;
}
payload = (((long)WsReceiveFrameBuffer[2] << 56) | ((long)WsReceiveFrameBuffer[3] << 48) | ((long)WsReceiveFrameBuffer[4] << 40) | ((long)WsReceiveFrameBuffer[5] << 32) | ((long)WsReceiveFrameBuffer[6] << 24) | ((long)WsReceiveFrameBuffer[7] << 16) | ((long)WsReceiveFrameBuffer[8] << 8) | ((long)WsReceiveFrameBuffer[9] << 0));
@@ -421,21 +473,26 @@ public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
{
if (WsReceiveFrameBuffer.Size < WsHeaderSize)
{
- for (long i = 0; i < 4; i++, index++, size--)
- {
- if (size == 0)
- return;
- WsReceiveFrameBuffer.Append(buffer[offset + index]);
- WsReceiveMask[i] = buffer[offset + index];
- }
+ long need = WsHeaderSize - WsReceiveFrameBuffer.Size;
+ long take = Math.Min(need, size);
+ WsReceiveFrameBuffer.Append(buffer, (int)offset + index, (int)take);
+ index += (int)take;
+ size -= take;
+ if (WsReceiveFrameBuffer.Size < WsHeaderSize)
+ return;
}
+ int maskStart = (int)WsHeaderSize - 4;
+ WsReceiveMask[0] = WsReceiveFrameBuffer[maskStart + 0];
+ WsReceiveMask[1] = WsReceiveFrameBuffer[maskStart + 1];
+ WsReceiveMask[2] = WsReceiveFrameBuffer[maskStart + 2];
+ WsReceiveMask[3] = WsReceiveFrameBuffer[maskStart + 3];
}
long total = WsHeaderSize + WsPayloadSize;
long length = Math.Min(total - WsReceiveFrameBuffer.Size, size);
// Prepare WebSocket frame payload
- WsReceiveFrameBuffer.Append(buffer[((int)offset + index)..((int)offset + index + (int)length)]);
+ WsReceiveFrameBuffer.Append(buffer, (int)offset + index, (int)length);
index += (int)length;
size -= length;
@@ -445,11 +502,20 @@ public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
// Unmask WebSocket frame content
if (mask)
{
- for (long i = 0; i < WsPayloadSize; i++)
- WsReceiveFinalBuffer.Append((byte)(WsReceiveFrameBuffer[WsHeaderSize + i] ^ WsReceiveMask[i % 4]));
+ int destOffset = (int)WsReceiveFinalBuffer.Size;
+ WsReceiveFinalBuffer.Resize(destOffset + WsPayloadSize);
+ MaskBuffer(
+ WsReceiveFrameBuffer.AsSpan().Slice((int)WsHeaderSize, (int)WsPayloadSize),
+ WsReceiveFinalBuffer.Data,
+ destOffset,
+ WsReceiveMask,
+ 0
+ );
}
else
+ {
WsReceiveFinalBuffer.Append(WsReceiveFrameBuffer.AsSpan().Slice((int)WsHeaderSize, (int)WsPayloadSize));
+ }
WsFrameReceived = true;
@@ -581,7 +647,7 @@ public void ClearWsBuffers()
///
/// Handshaked flag
///
- internal bool WsHandshaked;
+ public bool WsHandshaked;
///
/// Received frame flag
///
@@ -623,11 +689,11 @@ public void ClearWsBuffers()
///
/// Send buffer lock
///
- internal readonly object WsSendLock = new object();
+ public readonly object WsSendLock = new object();
///
/// Send buffer
///
- internal readonly Buffer WsSendBuffer = new Buffer();
+ public readonly Buffer WsSendBuffer = new Buffer();
///
/// Send mask
///