feat: add WebSocket protocol constants (RFC 6455)
Port WsConstants from golang/nats-server/server/websocket.go lines 41-106. Includes opcodes, frame header bits, close status codes, compression constants, header names, path routing, and the WsClientKind enum.
This commit is contained in:
78
src/NATS.Server/WebSocket/WsConstants.cs
Normal file
78
src/NATS.Server/WebSocket/WsConstants.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace NATS.Server.WebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket protocol constants (RFC 6455).
|
||||
/// Ported from golang/nats-server/server/websocket.go lines 41-106.
|
||||
/// </summary>
|
||||
public static class WsConstants
|
||||
{
|
||||
// Opcodes (RFC 6455 Section 5.2)
|
||||
public const int TextMessage = 1;
|
||||
public const int BinaryMessage = 2;
|
||||
public const int CloseMessage = 8;
|
||||
public const int PingMessage = 9;
|
||||
public const int PongMessage = 10;
|
||||
public const int ContinuationFrame = 0;
|
||||
|
||||
// Frame header bits
|
||||
public const byte FinalBit = 0x80; // 1 << 7
|
||||
public const byte Rsv1Bit = 0x40; // 1 << 6 (compression, RFC 7692)
|
||||
public const byte Rsv2Bit = 0x20; // 1 << 5
|
||||
public const byte Rsv3Bit = 0x10; // 1 << 4
|
||||
public const byte MaskBit = 0x80; // 1 << 7 (in second byte)
|
||||
|
||||
// Frame size limits
|
||||
public const int MaxFrameHeaderSize = 14;
|
||||
public const int MaxControlPayloadSize = 125;
|
||||
public const int FrameSizeForBrowsers = 4096;
|
||||
public const int CompressThreshold = 64;
|
||||
public const int CloseStatusSize = 2;
|
||||
|
||||
// Close status codes (RFC 6455 Section 11.7)
|
||||
public const int CloseStatusNormalClosure = 1000;
|
||||
public const int CloseStatusGoingAway = 1001;
|
||||
public const int CloseStatusProtocolError = 1002;
|
||||
public const int CloseStatusUnsupportedData = 1003;
|
||||
public const int CloseStatusNoStatusReceived = 1005;
|
||||
public const int CloseStatusInvalidPayloadData = 1007;
|
||||
public const int CloseStatusPolicyViolation = 1008;
|
||||
public const int CloseStatusMessageTooBig = 1009;
|
||||
public const int CloseStatusInternalSrvError = 1011;
|
||||
public const int CloseStatusTlsHandshake = 1015;
|
||||
|
||||
// Compression constants (RFC 7692)
|
||||
public const string PmcExtension = "permessage-deflate";
|
||||
public const string PmcSrvNoCtx = "server_no_context_takeover";
|
||||
public const string PmcCliNoCtx = "client_no_context_takeover";
|
||||
public static readonly string PmcReqHeaderValue = $"{PmcExtension}; {PmcSrvNoCtx}; {PmcCliNoCtx}";
|
||||
public static readonly string PmcFullResponse = $"Sec-WebSocket-Extensions: {PmcExtension}; {PmcSrvNoCtx}; {PmcCliNoCtx}\r\n";
|
||||
|
||||
// Header names
|
||||
public const string NoMaskingHeader = "Nats-No-Masking";
|
||||
public const string NoMaskingValue = "true";
|
||||
public static readonly string NoMaskingFullResponse = $"{NoMaskingHeader}: {NoMaskingValue}\r\n";
|
||||
public const string XForwardedForHeader = "X-Forwarded-For";
|
||||
|
||||
// Path routing
|
||||
public const string ClientPath = "/";
|
||||
public const string LeafNodePath = "/leafnode";
|
||||
public const string MqttPath = "/mqtt";
|
||||
|
||||
// WebSocket GUID (RFC 6455 Section 1.3)
|
||||
public static readonly byte[] WsGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"u8.ToArray();
|
||||
|
||||
// Compression trailer (RFC 7692 Section 7.2.2)
|
||||
public static readonly byte[] CompressLastBlock = [0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff];
|
||||
|
||||
// Decompression trailer appended before decompressing
|
||||
public static readonly byte[] DecompressTrailer = [0x00, 0x00, 0xff, 0xff];
|
||||
|
||||
public static bool IsControlFrame(int opcode) => opcode >= CloseMessage;
|
||||
}
|
||||
|
||||
public enum WsClientKind
|
||||
{
|
||||
Client,
|
||||
Leaf,
|
||||
Mqtt,
|
||||
}
|
||||
53
tests/NATS.Server.Tests/WebSocket/WsConstantsTests.cs
Normal file
53
tests/NATS.Server.Tests/WebSocket/WsConstantsTests.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
|
||||
public class WsConstantsTests
|
||||
{
|
||||
[Fact]
|
||||
public void OpCodes_MatchRfc6455()
|
||||
{
|
||||
WsConstants.TextMessage.ShouldBe(1);
|
||||
WsConstants.BinaryMessage.ShouldBe(2);
|
||||
WsConstants.CloseMessage.ShouldBe(8);
|
||||
WsConstants.PingMessage.ShouldBe(9);
|
||||
WsConstants.PongMessage.ShouldBe(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FrameBits_MatchRfc6455()
|
||||
{
|
||||
WsConstants.FinalBit.ShouldBe((byte)0x80);
|
||||
WsConstants.Rsv1Bit.ShouldBe((byte)0x40);
|
||||
WsConstants.MaskBit.ShouldBe((byte)0x80);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseStatusCodes_MatchRfc6455()
|
||||
{
|
||||
WsConstants.CloseStatusNormalClosure.ShouldBe(1000);
|
||||
WsConstants.CloseStatusGoingAway.ShouldBe(1001);
|
||||
WsConstants.CloseStatusProtocolError.ShouldBe(1002);
|
||||
WsConstants.CloseStatusPolicyViolation.ShouldBe(1008);
|
||||
WsConstants.CloseStatusMessageTooBig.ShouldBe(1009);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WsConstants.CloseMessage)]
|
||||
[InlineData(WsConstants.PingMessage)]
|
||||
[InlineData(WsConstants.PongMessage)]
|
||||
public void IsControlFrame_True(int opcode)
|
||||
{
|
||||
WsConstants.IsControlFrame(opcode).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WsConstants.TextMessage)]
|
||||
[InlineData(WsConstants.BinaryMessage)]
|
||||
[InlineData(0)]
|
||||
public void IsControlFrame_False(int opcode)
|
||||
{
|
||||
WsConstants.IsControlFrame(opcode).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user