// Copyright 2020-2026 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/mqtt.go mqttParse() in the NATS server Go source. namespace ZB.MOM.NatsNet.Server.Mqtt; /// /// MQTT binary packet parser and dispatch. /// Reads packets from a byte buffer using and dispatches /// to the appropriate handler based on packet type. /// Mirrors Go mqttParse() in server/mqtt.go. /// internal static class MqttParser { /// PINGRESP packet bytes: 0xD0 0x00. private static readonly byte[] PingRespPacket = [MqttPacket.PingResp, 0x00]; /// /// Parses MQTT packets from and dispatches to handlers. /// Returns null on success, or an exception describing the parse/dispatch error. /// Handles partial packets by saving state in the client's . /// Mirrors Go mqttParse(r *mqttReader, c *client, ...). /// public static Exception? Parse(ClientConnection c, byte[] buf, int len) { var mqtt = c.Mqtt!; var r = mqtt.Reader; // Slice buffer to actual length if needed. if (len < buf.Length) { var tmp = new byte[len]; Buffer.BlockCopy(buf, 0, tmp, 0, len); buf = tmp; } r.Reset(buf); var connected = (c.Flags & ClientFlags.ConnectReceived) != 0; Exception? err = null; while (err == null && r.HasMore()) { r.PacketStart = r.Position; // Read packet type + flags byte. byte b; try { b = r.ReadByte("packet type"); } catch (Exception ex) { err = ex; break; } var pt = (byte)(b & MqttPacket.Mask); // CONNECT must be the first packet. if (!connected && pt != MqttPacket.Connect) { err = new InvalidOperationException( $"the first packet should be a CONNECT (0x{MqttPacket.Connect:X2}), got 0x{pt:X2}"); break; } // Read remaining length (variable-length encoding). int pl; bool complete; try { (pl, complete) = r.ReadPacketLen(); } catch (Exception ex) { err = ex; break; } if (!complete) break; // Partial packet — state saved in reader. // Dispatch based on packet type. switch (pt) { case MqttPacket.Pub: var pubFlags = (byte)(b & MqttPacket.FlagMask); var (pp, pubErr) = MqttPacketHandlers.ParsePub(r, pl, pubFlags, mqtt.RejectQoS2Pub); if (pubErr != null) { err = pubErr; break; } err = MqttPacketHandlers.ProcessPub(c, pp!); break; case MqttPacket.PubAck: // TODO: Task 5 — process PUBACK err = new NotImplementedException("PUBACK not yet implemented"); break; case MqttPacket.PubRec: // TODO: Task 5 — process PUBREC err = new NotImplementedException("PUBREC not yet implemented"); break; case MqttPacket.PubRel: // TODO: Task 5 — process PUBREL err = new NotImplementedException("PUBREL not yet implemented"); break; case MqttPacket.PubComp: // TODO: Task 5 — process PUBCOMP err = new NotImplementedException("PUBCOMP not yet implemented"); break; case MqttPacket.Sub: var (subPi, subFilters, subErr) = MqttPacketHandlers.ParseSubsOrUnsubs(r, b, pl, isSub: true); if (subErr != null) { err = subErr; break; } err = MqttPacketHandlers.ProcessSubs(c, subPi, subFilters!); break; case MqttPacket.Unsub: var (unsubPi, unsubFilters, unsubErr) = MqttPacketHandlers.ParseSubsOrUnsubs(r, b, pl, isSub: false); if (unsubErr != null) { err = unsubErr; break; } err = MqttPacketHandlers.ProcessUnsubs(c, unsubPi, unsubFilters!); break; case MqttPacket.Ping: HandlePingReq(c); break; case MqttPacket.Connect: if (connected) { err = new InvalidOperationException("second CONNECT packet not allowed"); break; } var (rc, cp, parseErr) = MqttPacketHandlers.ParseConnect(r); if (parseErr != null) { // Send CONNACK with error code if we have one, then close. if (rc != MqttConnAckRc.Accepted) MqttPacketHandlers.EnqueueConnAck(c, rc, false); err = parseErr; break; } if (rc != MqttConnAckRc.Accepted) { MqttPacketHandlers.EnqueueConnAck(c, rc, false); err = new InvalidOperationException($"CONNECT rejected with code 0x{rc:X2}"); break; } err = MqttPacketHandlers.ProcessConnect(c, cp!); if (err == null) connected = true; break; case MqttPacket.Disconnect: MqttPacketHandlers.HandleDisconnect(c); return null; // Connection closed, exit parse loop. default: err = new InvalidOperationException($"unknown MQTT packet type: 0x{pt:X2}"); break; } } return err; } /// /// Handles PINGREQ by enqueueing a PINGRESP packet. /// Mirrors Go mqttEnqueuePingResp(). /// private static void HandlePingReq(ClientConnection c) { lock (c) { c.EnqueueProto(PingRespPacket); } } }