feat: implement full MQTT Go parity across 5 phases — binary protocol, auth/TLS, cross-protocol bridging, monitoring, and JetStream persistence
Phase 1: Binary MQTT 3.1.1 wire protocol with PipeReader-based parsing, full packet type dispatch, and MQTT 3.1.1 compliance checks. Phase 2: Auth pipeline routing MQTT CONNECT through AuthService, TLS transport with SslStream wrapping, pinned cert validation. Phase 3: IMessageRouter refactor (NatsClient → INatsClient), MqttNatsClientAdapter for cross-protocol bridging, MqttTopicMapper with full Go-parity topic/subject translation. Phase 4: /connz mqtt_client field population, /varz actual MQTT port. Phase 5: JetStream persistence — MqttStreamInitializer creates 5 internal streams, MqttConsumerManager for QoS 1/2 consumers, subject-keyed session/retained lookups replacing linear scans. All 503 MQTT tests and 1589 Core tests pass.
This commit is contained in:
@@ -33,6 +33,119 @@ public static class MqttPacketWriter
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a CONNACK packet. Go reference: mqtt.go mqttConnAck.
|
||||
/// </summary>
|
||||
/// <param name="sessionPresent">0x01 if resuming existing session, 0x00 otherwise.</param>
|
||||
/// <param name="returnCode">CONNACK return code (0x00 = accepted).</param>
|
||||
public static byte[] WriteConnAck(byte sessionPresent, byte returnCode)
|
||||
{
|
||||
ReadOnlySpan<byte> payload = [sessionPresent, returnCode];
|
||||
return Write(MqttControlPacketType.ConnAck, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a PUBACK packet (QoS 1 acknowledgment).
|
||||
/// </summary>
|
||||
public static byte[] WritePubAck(ushort packetId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, packetId);
|
||||
return Write(MqttControlPacketType.PubAck, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a SUBACK packet with granted QoS values per subscription filter.
|
||||
/// </summary>
|
||||
public static byte[] WriteSubAck(ushort packetId, ReadOnlySpan<byte> grantedQoS)
|
||||
{
|
||||
var payload = new byte[2 + grantedQoS.Length];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload.AsSpan(0, 2), packetId);
|
||||
grantedQoS.CopyTo(payload.AsSpan(2));
|
||||
return Write(MqttControlPacketType.SubAck, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an UNSUBACK packet.
|
||||
/// </summary>
|
||||
public static byte[] WriteUnsubAck(ushort packetId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, packetId);
|
||||
return Write(MqttControlPacketType.UnsubAck, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a PINGRESP packet (no payload).
|
||||
/// </summary>
|
||||
public static byte[] WritePingResp()
|
||||
=> Write(MqttControlPacketType.PingResp, []);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a PUBREC packet (QoS 2 step 1 response).
|
||||
/// </summary>
|
||||
public static byte[] WritePubRec(ushort packetId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, packetId);
|
||||
return Write(MqttControlPacketType.PubRec, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a PUBREL packet (QoS 2 step 2). Fixed-header flags must be 0x02 per MQTT spec.
|
||||
/// </summary>
|
||||
public static byte[] WritePubRel(ushort packetId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, packetId);
|
||||
return Write(MqttControlPacketType.PubRel, payload, flags: 0x02);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a PUBCOMP packet (QoS 2 step 3 response).
|
||||
/// </summary>
|
||||
public static byte[] WritePubComp(ushort packetId)
|
||||
{
|
||||
Span<byte> payload = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, packetId);
|
||||
return Write(MqttControlPacketType.PubComp, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an MQTT PUBLISH packet for delivery to a client.
|
||||
/// </summary>
|
||||
public static byte[] WritePublish(string topic, ReadOnlySpan<byte> payload, byte qos = 0,
|
||||
bool retain = false, bool dup = false, ushort packetId = 0)
|
||||
{
|
||||
var topicBytes = Encoding.UTF8.GetBytes(topic);
|
||||
var variableHeaderLen = 2 + topicBytes.Length + (qos > 0 ? 2 : 0);
|
||||
var totalPayload = new byte[variableHeaderLen + payload.Length];
|
||||
var pos = 0;
|
||||
|
||||
// Topic name (length-prefixed)
|
||||
BinaryPrimitives.WriteUInt16BigEndian(totalPayload.AsSpan(pos, 2), (ushort)topicBytes.Length);
|
||||
pos += 2;
|
||||
topicBytes.CopyTo(totalPayload.AsSpan(pos));
|
||||
pos += topicBytes.Length;
|
||||
|
||||
// Packet ID (only for QoS > 0)
|
||||
if (qos > 0)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16BigEndian(totalPayload.AsSpan(pos, 2), packetId);
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
// Application payload
|
||||
payload.CopyTo(totalPayload.AsSpan(pos));
|
||||
|
||||
byte flags = 0;
|
||||
if (dup) flags |= 0x08;
|
||||
flags |= (byte)((qos & 0x03) << 1);
|
||||
if (retain) flags |= 0x01;
|
||||
|
||||
return Write(MqttControlPacketType.Publish, totalPayload, flags);
|
||||
}
|
||||
|
||||
internal static byte[] EncodeRemainingLength(int value)
|
||||
{
|
||||
if (value < 0 || value > MqttProtocolConstants.MaxPayloadSize)
|
||||
|
||||
Reference in New Issue
Block a user