feat: execute full-repo remaining parity closure plan

This commit is contained in:
Joseph Doherty
2026-02-23 13:08:52 -05:00
parent cbe1fa6121
commit 2b64d762f6
75 changed files with 2325 additions and 121 deletions

View File

@@ -0,0 +1,90 @@
using System.Net.Sockets;
using System.Text;
namespace NATS.Server.Mqtt;
public sealed class MqttConnection(TcpClient client, MqttListener listener) : IAsyncDisposable
{
private readonly TcpClient _client = client;
private readonly NetworkStream _stream = client.GetStream();
private readonly MqttListener _listener = listener;
private readonly MqttProtocolParser _parser = new();
private readonly SemaphoreSlim _writeGate = new(1, 1);
private string _clientId = string.Empty;
public async Task RunAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
string line;
try
{
line = await ReadLineAsync(ct);
}
catch
{
break;
}
var packet = _parser.ParseLine(line);
switch (packet.Type)
{
case MqttPacketType.Connect:
_clientId = packet.ClientId;
await WriteLineAsync("CONNACK", ct);
break;
case MqttPacketType.Subscribe:
_listener.RegisterSubscription(this, packet.Topic);
await WriteLineAsync($"SUBACK {packet.Topic}", ct);
break;
case MqttPacketType.Publish:
await _listener.PublishAsync(packet.Topic, packet.Payload, this, ct);
break;
}
}
}
public Task SendMessageAsync(string topic, string payload, CancellationToken ct)
=> WriteLineAsync($"MSG {topic} {payload}", ct);
public async ValueTask DisposeAsync()
{
_listener.Unregister(this);
_writeGate.Dispose();
await _stream.DisposeAsync();
_client.Dispose();
}
private async Task WriteLineAsync(string line, CancellationToken ct)
{
await _writeGate.WaitAsync(ct);
try
{
var bytes = Encoding.UTF8.GetBytes(line + "\n");
await _stream.WriteAsync(bytes, ct);
await _stream.FlushAsync(ct);
}
finally
{
_writeGate.Release();
}
}
private async Task<string> ReadLineAsync(CancellationToken ct)
{
var bytes = new List<byte>(64);
var single = new byte[1];
while (true)
{
var read = await _stream.ReadAsync(single.AsMemory(0, 1), ct);
if (read == 0)
throw new IOException("mqtt closed");
if (single[0] == (byte)'\n')
break;
if (single[0] != (byte)'\r')
bytes.Add(single[0]);
}
return Encoding.UTF8.GetString([.. bytes]);
}
}

View File

@@ -0,0 +1,104 @@
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
namespace NATS.Server.Mqtt;
public sealed class MqttListener(string host, int port) : IAsyncDisposable
{
private readonly string _host = host;
private int _port = port;
private readonly ConcurrentDictionary<MqttConnection, byte> _connections = new();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<MqttConnection, byte>> _subscriptions = new(StringComparer.Ordinal);
private TcpListener? _listener;
private Task? _acceptLoop;
private readonly CancellationTokenSource _cts = new();
public int Port => _port;
public Task StartAsync(CancellationToken ct)
{
var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token);
var ip = string.IsNullOrWhiteSpace(_host) || _host == "0.0.0.0"
? IPAddress.Any
: IPAddress.Parse(_host);
_listener = new TcpListener(ip, _port);
_listener.Start();
_port = ((IPEndPoint)_listener.LocalEndpoint).Port;
_acceptLoop = Task.Run(() => AcceptLoopAsync(linked.Token), linked.Token);
return Task.CompletedTask;
}
internal void RegisterSubscription(MqttConnection connection, string topic)
{
var set = _subscriptions.GetOrAdd(topic, static _ => new ConcurrentDictionary<MqttConnection, byte>());
set[connection] = 0;
}
internal async Task PublishAsync(string topic, string payload, MqttConnection sender, CancellationToken ct)
{
if (!_subscriptions.TryGetValue(topic, out var subscribers))
return;
foreach (var subscriber in subscribers.Keys)
{
if (subscriber == sender)
continue;
await subscriber.SendMessageAsync(topic, payload, ct);
}
}
internal void Unregister(MqttConnection connection)
{
_connections.TryRemove(connection, out _);
foreach (var set in _subscriptions.Values)
set.TryRemove(connection, out _);
}
public async ValueTask DisposeAsync()
{
await _cts.CancelAsync();
if (_listener != null)
_listener.Stop();
if (_acceptLoop != null)
await _acceptLoop.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
foreach (var connection in _connections.Keys)
await connection.DisposeAsync();
_connections.Clear();
_subscriptions.Clear();
_cts.Dispose();
}
private async Task AcceptLoopAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
TcpClient client;
try
{
client = await _listener!.AcceptTcpClientAsync(ct);
}
catch
{
break;
}
var connection = new MqttConnection(client, this);
_connections[connection] = 0;
_ = Task.Run(async () =>
{
try
{
await connection.RunAsync(ct);
}
finally
{
await connection.DisposeAsync();
}
}, ct);
}
}
}

View File

@@ -0,0 +1,53 @@
namespace NATS.Server.Mqtt;
public enum MqttPacketType
{
Unknown,
Connect,
Subscribe,
Publish,
}
public sealed record MqttPacket(MqttPacketType Type, string Topic, string Payload, string ClientId);
public sealed class MqttProtocolParser
{
public MqttPacket ParseLine(string line)
{
var trimmed = line.Trim();
if (trimmed.Length == 0)
return new MqttPacket(MqttPacketType.Unknown, string.Empty, string.Empty, string.Empty);
if (trimmed.StartsWith("CONNECT ", StringComparison.Ordinal))
{
return new MqttPacket(
MqttPacketType.Connect,
string.Empty,
string.Empty,
trimmed["CONNECT ".Length..].Trim());
}
if (trimmed.StartsWith("SUB ", StringComparison.Ordinal))
{
return new MqttPacket(
MqttPacketType.Subscribe,
trimmed["SUB ".Length..].Trim(),
string.Empty,
string.Empty);
}
if (trimmed.StartsWith("PUB ", StringComparison.Ordinal))
{
var rest = trimmed["PUB ".Length..];
var sep = rest.IndexOf(' ');
if (sep <= 0)
return new MqttPacket(MqttPacketType.Unknown, string.Empty, string.Empty, string.Empty);
var topic = rest[..sep].Trim();
var payload = rest[(sep + 1)..];
return new MqttPacket(MqttPacketType.Publish, topic, payload, string.Empty);
}
return new MqttPacket(MqttPacketType.Unknown, string.Empty, string.Empty, string.Empty);
}
}