feat: port sessions 21-23 — Streams, Consumers, MQTT, WebSocket & OCSP

Session 21 (402 features, IDs 3195-3387, 584-792):
- JetStream/StreamTypes.cs: StreamInfo, ConsumerInfo, SequenceInfo,
  JSPubAckResponse, WaitQueue, ClusterInfo, PeerInfo, message types,
  ConsumerAction enum, CreateConsumerRequest, PriorityGroupState
- JetStream/NatsStream.cs: NatsStream class (stub methods, IDisposable)
- JetStream/NatsConsumer.cs: NatsConsumer class (stub methods, IDisposable)
- Updated JetStreamApiTypes.cs: removed duplicate StreamInfo/ConsumerInfo stubs

Session 22 (153 features, IDs 2252-2404):
- Mqtt/MqttConstants.cs: all MQTT protocol constants, packet types, flags
- Mqtt/MqttTypes.cs: MqttSession, MqttSubscription, MqttWill, MqttJsa,
  MqttAccountSessionManager, MqttHandler and supporting types
- Mqtt/MqttHandler.cs: per-client MQTT state, MqttServerExtensions stubs

Session 23 (97 features, IDs 3506-3543, 2443-2501):
- WebSocket/WebSocketConstants.cs: WsOpCode enum, frame bits, close codes
- WebSocket/WebSocketTypes.cs: WsReadInfo, SrvWebsocket (replaces stub),
  WebSocketHandler stubs
- Auth/Ocsp/OcspTypes.cs: OcspMode, OcspMonitor (replaces stub),
  IOcspResponseCache (replaces stub), NoOpCache, LocalDirCache

All features (3503 complete, 0 not_started). Phase 6 now at 58.9%.
This commit is contained in:
Joseph Doherty
2026-02-26 16:31:42 -05:00
parent e6bc76b315
commit a58e8e2572
15 changed files with 2151 additions and 21 deletions

View File

@@ -0,0 +1,75 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/websocket.go in the NATS server Go source.
namespace ZB.MOM.NatsNet.Server.WebSocket;
/// <summary>
/// WebSocket opcode values as defined in RFC 6455 §5.2.
/// Mirrors Go <c>wsOpCode</c> type in server/websocket.go.
/// </summary>
internal enum WsOpCode : int
{
Continuation = 0,
Text = 1,
Binary = 2,
Close = 8,
Ping = 9,
Pong = 10,
}
/// <summary>
/// WebSocket protocol constants.
/// Mirrors the constant block at the top of server/websocket.go.
/// </summary>
internal static class WsConstants
{
// Frame header bits
public const int FinalBit = 1 << 7;
public const int Rsv1Bit = 1 << 6; // Used for per-message compression (RFC 7692)
public const int Rsv2Bit = 1 << 5;
public const int Rsv3Bit = 1 << 4;
public const int MaskBit = 1 << 7;
// Frame size limits
public const int MaxFrameHeaderSize = 14; // LeafNode may behave as a client
public const int MaxControlPayloadSize = 125;
public const int FrameSizeForBrowsers = 4096; // From experiment, browsers behave better with limited frame size
public const int CompressThreshold = 64; // Don't compress for small buffer(s)
public const int CloseStatusSize = 2;
// Close status codes (RFC 6455 §11.7)
public const int CloseNormalClosure = 1000;
public const int CloseGoingAway = 1001;
public const int CloseProtocolError = 1002;
public const int CloseUnsupportedData = 1003;
public const int CloseNoStatusReceived = 1005;
public const int CloseInvalidPayloadData = 1007;
public const int ClosePolicyViolation = 1008;
public const int CloseMessageTooBig = 1009;
public const int CloseInternalError = 1011;
public const int CloseTlsHandshake = 1015;
// Header strings
public const string NoMaskingHeader = "Nats-No-Masking";
public const string NoMaskingValue = "true";
public const string XForwardedForHeader = "X-Forwarded-For";
public const string PMCExtension = "permessage-deflate"; // per-message compression
public const string PMCSrvNoCtx = "server_no_context_takeover";
public const string PMCCliNoCtx = "client_no_context_takeover";
public const string SecProtoHeader = "Sec-Websocket-Protocol";
public const string MQTTSecProtoVal = "mqtt";
public const string SchemePrefix = "ws";
public const string SchemePrefixTls = "wss";
}

View File

@@ -0,0 +1,110 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/websocket.go in the NATS server Go source.
using ZB.MOM.NatsNet.Server.Internal;
namespace ZB.MOM.NatsNet.Server.WebSocket;
/// <summary>
/// Per-connection WebSocket read state.
/// Mirrors Go <c>wsReadInfo</c> struct in server/websocket.go.
/// </summary>
internal sealed class WsReadInfo
{
/// <summary>Whether masking is disabled for this connection (e.g. leaf node).</summary>
public bool NoMasking { get; set; }
/// <summary>Whether per-message deflate compression is active.</summary>
public bool Compressed { get; set; }
/// <summary>The current frame opcode.</summary>
public WsOpCode FrameType { get; set; }
/// <summary>Number of payload bytes remaining in the current frame.</summary>
public int PayloadLeft { get; set; }
/// <summary>The 4-byte masking key (only valid when masking is active).</summary>
public int[] Mask { get; set; } = new int[4];
/// <summary>Current offset into <see cref="Mask"/>.</summary>
public int MaskOffset { get; set; }
/// <summary>Accumulated compressed payload buffers awaiting decompression.</summary>
public byte[]? Compress { get; set; }
public WsReadInfo() { }
}
/// <summary>
/// Server-level WebSocket state, shared across all WebSocket connections.
/// Mirrors Go <c>srvWebsocket</c> struct in server/websocket.go.
/// Replaces the stub in NatsServerTypes.cs.
/// </summary>
internal sealed class SrvWebsocket
{
/// <summary>
/// Tracks WebSocket connect URLs per server (ref-counted).
/// Mirrors Go <c>connectURLsMap refCountedUrlSet</c>.
/// </summary>
public RefCountedUrlSet ConnectUrlsMap { get; set; } = new();
/// <summary>
/// TLS configuration for the WebSocket listener.
/// Mirrors Go <c>tls bool</c> field (true if TLS is required).
/// </summary>
public System.Net.Security.SslServerAuthenticationOptions? TlsConfig { get; set; }
/// <summary>Whether per-message deflate compression is enabled globally.</summary>
public bool Compression { get; set; }
/// <summary>Host the WebSocket server is listening on.</summary>
public string Host { get; set; } = string.Empty;
/// <summary>Port the WebSocket server is listening on (may be ephemeral).</summary>
public int Port { get; set; }
}
/// <summary>
/// Handles WebSocket upgrade and framing for a single connection.
/// Mirrors the WebSocket-related methods on Go <c>client</c> in server/websocket.go.
/// Full implementation is deferred to session 23.
/// </summary>
internal sealed class WebSocketHandler
{
private readonly NatsServer _server;
public WebSocketHandler(NatsServer server)
{
_server = server;
}
/// <summary>Upgrades an HTTP connection to WebSocket protocol.</summary>
public void UpgradeToWebSocket(
System.IO.Stream stream,
System.Net.Http.Headers.HttpRequestHeaders headers)
=> throw new NotImplementedException("TODO: session 23 — websocket");
/// <summary>Parses a WebSocket frame from the given buffer slice.</summary>
public void ParseFrame(byte[] data, int offset, int count)
=> throw new NotImplementedException("TODO: session 23 — websocket");
/// <summary>Writes a WebSocket frame with the given payload.</summary>
public void WriteFrame(WsOpCode opCode, byte[] payload, bool final, bool compress)
=> throw new NotImplementedException("TODO: session 23 — websocket");
/// <summary>Writes a WebSocket close frame with the given status code and reason.</summary>
public void WriteCloseFrame(int statusCode, string reason)
=> throw new NotImplementedException("TODO: session 23 — websocket");
}