feat(mqtt): implement CONNECT/CONNACK/DISCONNECT packet handlers
Implement Task 3 of MQTT orchestration: - Create MqttPacketHandlers.cs with ParseConnect(), ProcessConnect(), EnqueueConnAck(), HandleDisconnect() - Wire CONNECT and DISCONNECT dispatch in MqttParser.cs - Parse CONNECT: protocol name/level, flags, keep-alive, client ID, will, auth - Send CONNACK (4-byte fixed packet with return code) - DISCONNECT clears will message and closes connection cleanly - Auto-generate client ID for empty ID + clean session - Validate reserved bit, will flags, username/password consistency - Add Reader field to MqttHandler for per-connection parsing - 11 unit tests for CONNECT parsing and processing - 1 end-to-end integration test: TCP → CONNECT → CONNACK over the wire
This commit is contained in:
@@ -160,6 +160,96 @@ public sealed class ServerBootTests : IDisposable
|
||||
server.Running().ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end: TCP connect → send MQTT CONNECT → receive CONNACK.
|
||||
/// Validates the full MQTT handshake over the wire.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task MqttBoot_ConnectHandshake_ShouldReceiveConnAck()
|
||||
{
|
||||
var opts = new ServerOptions
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Mqtt = { Port = -1, Host = "127.0.0.1" },
|
||||
};
|
||||
|
||||
var (server, err) = NatsServer.NewServer(opts);
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
try
|
||||
{
|
||||
server!.Start();
|
||||
var mqttAddr = server.MqttAddr();
|
||||
mqttAddr.ShouldNotBeNull();
|
||||
|
||||
using var tcp = new System.Net.Sockets.TcpClient();
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await tcp.ConnectAsync(mqttAddr!.Address, mqttAddr.Port, cts.Token);
|
||||
|
||||
var stream = tcp.GetStream();
|
||||
|
||||
// Build and send MQTT CONNECT packet.
|
||||
var connectPacket = BuildMqttConnectPacket("integration-test");
|
||||
await stream.WriteAsync(connectPacket, cts.Token);
|
||||
await stream.FlushAsync(cts.Token);
|
||||
|
||||
// Read CONNACK response (4 bytes).
|
||||
var response = new byte[4];
|
||||
var totalRead = 0;
|
||||
while (totalRead < 4)
|
||||
{
|
||||
var n = await stream.ReadAsync(response.AsMemory(totalRead, 4 - totalRead), cts.Token);
|
||||
if (n == 0) break;
|
||||
totalRead += n;
|
||||
}
|
||||
|
||||
totalRead.ShouldBe(4, "Should receive 4-byte CONNACK");
|
||||
response[0].ShouldBe((byte)0x20, "Packet type should be CONNACK");
|
||||
response[1].ShouldBe((byte)0x02, "Remaining length should be 2");
|
||||
response[3].ShouldBe((byte)0x00, "Return code should be Accepted (0)");
|
||||
}
|
||||
finally
|
||||
{
|
||||
server!.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds a minimal MQTT CONNECT packet.</summary>
|
||||
private static byte[] BuildMqttConnectPacket(string clientId)
|
||||
{
|
||||
var payload = new List<byte>();
|
||||
// Protocol name "MQTT"
|
||||
payload.AddRange(new byte[] { 0x00, 0x04 });
|
||||
payload.AddRange(System.Text.Encoding.UTF8.GetBytes("MQTT"));
|
||||
// Protocol level 4
|
||||
payload.Add(0x04);
|
||||
// Flags: clean session
|
||||
payload.Add(0x02);
|
||||
// Keep alive: 60s
|
||||
payload.AddRange(new byte[] { 0x00, 0x3C });
|
||||
// Client ID
|
||||
var cidBytes = System.Text.Encoding.UTF8.GetBytes(clientId);
|
||||
payload.Add((byte)(cidBytes.Length >> 8));
|
||||
payload.Add((byte)(cidBytes.Length & 0xFF));
|
||||
payload.AddRange(cidBytes);
|
||||
|
||||
// Fixed header
|
||||
var result = new List<byte>();
|
||||
result.Add(0x10); // CONNECT type
|
||||
var remLen = payload.Count;
|
||||
do
|
||||
{
|
||||
var b = (byte)(remLen & 0x7F);
|
||||
remLen >>= 7;
|
||||
if (remLen > 0) b |= 0x80;
|
||||
result.Add(b);
|
||||
} while (remLen > 0);
|
||||
result.AddRange(payload);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that Shutdown() after Start() completes cleanly.
|
||||
/// Uses DontListen to skip TCP binding — tests lifecycle only.
|
||||
|
||||
Reference in New Issue
Block a user