1 /**
2  * Definition of MQTT protocol messages
3  *
4  * Author:
5  * Tomáš Chaloupka <chalucha@gmail.com>
6  *
7  * License:
8  * Boost Software License 1.0 (BSL-1.0)
9  *
10  * Permission is hereby granted, free of charge, to any person or organization obtaining a copy
11  * of the software and accompanying documentation covered by this license (the "Software") to use,
12  * reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative
13  * works of the Software, and to permit third-parties to whom the Software is furnished to do so,
14  * all subject to the following:
15  *
16  * The copyright notices in the Software and this entire statement, including the above license
17  * grant, this restriction and the following disclaimer, must be included in all copies of the Software,
18  * in whole or in part, and all derivative works of the Software, unless such copies or derivative works
19  * are solely in the form of machine-executable object code generated by a source language processor.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
22  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
23  * PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
24  * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT,
25  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26  * OTHER DEALINGS IN THE SOFTWARE.
27  */
28 module mqttd.messages;
29 
30 import std.range;
31 import std.exception : enforce;
32 import std.traits : isIntegral;
33 import std.typecons : Nullable;
34 debug import std.stdio;
35 
36 import mqttd.traits;
37 
38 @safe:
39 
40 /**
41  * Exception thrown when package format is somehow malformed
42  */
43 final class PacketFormatException : Exception
44 {
45 	this(string msg = null, Throwable next = null)
46 	{
47 		super(msg, next);
48 	}
49 }
50 
51 enum ubyte MQTT_PROTOCOL_LEVEL_3_1_1 = 0x04;
52 enum string MQTT_PROTOCOL_NAME = "MQTT";
53 
54 /**
55  * MQTT Control Packet type
56  *
57  * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_2.1_-
58  */
59 enum PacketType : ubyte
60 {
61 	/// Forbidden - Reserved
62 	RESERVED1   = 0,
63 	/// Client -> Server - Client request to connect to Server
64 	CONNECT     = 1,
65 	/// Server -> Client - Connect acknowledgment
66 	CONNACK     = 2,
67 	/// Publish message
68 	PUBLISH     = 3,
69 	/// Publish acknowledgment
70 	PUBACK      = 4,
71 	/// Publish received (assured delivery part 1)
72 	PUBREC      = 5,
73 	/// Publish release (assured delivery part 2)
74 	PUBREL      = 6,
75 	/// Publish complete (assured delivery part 3)
76 	PUBCOMP     = 7,
77 	/// Client -> Server - Client subscribe request
78 	SUBSCRIBE   = 8,
79 	/// Server -> Client - Subscribe acknowledgment
80 	SUBACK      = 9,
81 	/// Client -> Server - Unsubscribe request
82 	UNSUBSCRIBE = 10,
83 	/// Server -> Client - Unsubscribe acknowledgment
84 	UNSUBACK    = 11,
85 	/// Client -> Server - PING request
86 	PINGREQ     = 12,
87 	/// Server -> Client - PING response
88 	PINGRESP    = 13,
89 	/// Client -> Server - Client is disconnecting
90 	DISCONNECT  = 14,
91 	/// Forbidden - Reserved
92 	RESERVED2   = 15
93 }
94 
95 /**
96  * Indicates the level of assurance for delivery of an Application Message
97  * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.11_-
98  */
99 enum QoSLevel : ubyte
100 {
101 	/// At most once delivery
102 	QoS0 = 0x00,
103 	/// At least once delivery
104 	QoS1 = 0x01,
105 	/// Exactly once delivery
106 	QoS2 = 0x02,
107 	/// Reserved – must not be used
108 	Reserved = 0x03,
109 	/// Failure - used in SubAck packet only
110 	Failure = 0x80
111 }
112 
113 /// Connect Return code values - 0 = accepted, the rest means refused (6-255 are reserved)
114 enum ConnectReturnCode : ubyte
115 {
116 	/// Connection accepted
117 	ConnectionAccepted = 0x00,
118 	/// The Server does not support the level of the MQTT protocol requested by the Client
119 	ProtocolVersion    = 0x01,
120 	/// The Client identifier is correct UTF-8 but not allowed by the Server
121 	Identifier         = 0x02,
122 	/// The Network Connection has been made but the MQTT service is unavailable
123 	ServerUnavailable  = 0x03,
124 	/// The data in the user name or password is malformed
125 	UserNameOrPassword = 0x04,
126 	/// The Client is not authorized to connect
127 	NotAuthorized      = 0x05
128 }
129 
130 /**
131  * Each MQTT Control Packet contains a fixed header.
132  *
133  * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Figure_2.2_-
134  */
135 struct FixedHeader
136 {
137 	@safe pure @nogc nothrow:
138 	private ubyte _payload;
139 
140 	/// Represented as a 4-bit unsigned value
141 	@property PacketType type() const
142 	{
143 		return cast(PacketType)(_payload >> 4);
144 	}
145 
146 	/// ditto
147 	@property void type(in PacketType type)
148 	{
149 		_payload = cast(ubyte)((_payload & ~0xf0) | (type << 4));
150 	}
151 
152 	/// Duplicate delivery of a PUBLISH Control Packet
153 	@property bool dup() const
154 	{
155 		return (_payload & 0x08) == 0x08;
156 	}
157 
158 	/// ditto
159 	@property void dup(in bool value)
160 	{
161 		_payload = cast(ubyte)((_payload & ~0x08) | (value ? 0x08 : 0x00));
162 	}
163 
164 	/// Quality Of Service for a message
165 	@property QoSLevel qos() const
166 	{
167 		return cast(QoSLevel)((_payload >> 1) & 0x03);
168 	}
169 
170 	/// ditto
171 	@property void qos(in QoSLevel value)
172 	{
173 		_payload = cast(ubyte)((_payload & ~0x06) | (value << 1));
174 	}
175 
176 	/// PUBLISH Retain flag
177 	@property bool retain() const
178 	{
179 		return (_payload & 0x01) == 0x01;
180 	}
181 
182 	/// ditto
183 	@property void retain(in bool value)
184 	{
185 		_payload = cast(ubyte)(_payload & ~0x01) | (value ? 0x01 : 0x00);
186 	}
187 
188 	/// flags to ubyte
189 	@property ubyte flags() const
190 	{
191 		return _payload;
192 	}
193 
194 	@property void flags(in ubyte value)
195 	{
196 		_payload = value;
197 	}
198 
199 	/**
200 	 * The Remaining Length is the number of bytes remaining within the current packet,
201 	 * including data in the variable header and the payload.
202 	 * The Remaining Length does not include the bytes used to encode the Remaining Length.
203 	 */
204 	uint length;
205 
206 	alias flags this;
207 
208 	this(PacketType type, bool dup, QoSLevel qos, bool retain, uint length = 0)
209 	{
210 		this.type = type;
211 		this.dup = dup;
212 		this.retain = retain;
213 		this.qos = qos;
214 		this.length = length;
215 	}
216 
217 	this(T)(PacketType type, T flags, uint length = 0) if(isIntegral!T)
218 	{
219 		this.flags = cast(ubyte)(type << 4 | flags);
220 		this.type = type;
221 		this.length = length;
222 	}
223 
224 	this(T)(T value) if(isIntegral!T)
225 	{
226 		this.flags = cast(ubyte)value;
227 	}
228 }
229 
230 /**
231  * The Connect Flags byte contains a number of parameters specifying the behavior of the MQTT connection.
232  * It also indicates the presence or absence of fields in the payload.
233  */
234 struct ConnectFlags
235 {
236 	@safe pure @nogc nothrow:
237 	private ubyte _payload;
238 
239 	/**
240 	 * If the User Name Flag is set to 0, a user name MUST NOT be present in the payload.
241 	 * If the User Name Flag is set to 1, a user name MUST be present in the payload.
242 	 */
243 	@property bool userName() const
244 	{
245 		return (_payload & 0x80) == 0x80;
246 	}
247 
248 	/// ditto
249 	@property void userName(in bool value)
250 	{
251 		_payload = cast(ubyte)((_payload & ~0x80) | (value ? 0x80 : 0x00));
252 	}
253 
254 	/**
255 	 * If the Password Flag is set to 0, a password MUST NOT be present in the payload.
256 	 * If the Password Flag is set to 1, a password MUST be present in the payload.
257 	 * If the User Name Flag is set to 0, the Password Flag MUST be set to 0.
258 	 */
259 	@property bool password() const
260 	{
261 		return (_payload & 0x40) == 0x40;
262 	}
263 
264 	/// ditto
265 	@property void password(in bool value)
266 	{
267 		_payload = cast(ubyte)((_payload & ~0x40) | (value ? 0x40 : 0x00));
268 	}
269 
270 	/**
271 	 * This bit specifies if the Will Message is to be Retained when it is published.
272 	 *
273 	 * If the Will Flag is set to 0, then the Will Retain Flag MUST be set to 0.
274 	 * If the Will Flag is set to 1:
275 	 *      If Will Retain is set to 0, the Server MUST publish the Will Message as a non-retained message.
276 	 *      If Will Retain is set to 1, the Server MUST publish the Will Message as a retained message
277 	 */
278 	@property bool willRetain() const
279 	{
280 		return (_payload & 0x20) == 0x20;
281 	}
282 
283 	/// ditto
284 	@property void willRetain(in bool value)
285 	{
286 		_payload = cast(ubyte)((_payload & ~0x20) | (value ? 0x20 : 0x00));
287 	}
288 
289 	/**
290 	 * Specify the QoS level to be used when publishing the Will Message.
291 	 *
292 	 * If the Will Flag is set to 0, then the Will QoS MUST be set to 0 (0x00).
293 	 * If the Will Flag is set to 1, the value of Will QoS can be 0 (0x00), 1 (0x01), or 2 (0x02).
294 	 * It MUST NOT be 3 (0x03)
295 	 */
296 	@property QoSLevel willQoS() const
297 	{
298 		return cast(QoSLevel)((_payload >> 3) & 0x03);
299 	}
300 
301 	/// ditto
302 	@property void willQoS(in QoSLevel value)
303 	{
304 		_payload = cast(ubyte)((_payload & ~0x18) | (value << 3));
305 	}
306 
307 	/**
308 	 * If the Will Flag is set to 1 this indicates that, if the Connect request is accepted, a Will Message MUST
309 	 * be stored on the Server and associated with the Network Connection. The Will Message MUST be published
310 	 * when the Network Connection is subsequently closed unless the Will Message has been deleted by the Server
311 	 * on receipt of a DISCONNECT Packet.
312 	 *
313 	 * Situations in which the Will Message is published include, but are not limited to:
314 	 *      An I/O error or network failure detected by the Server.
315 	 *      The Client fails to communicate within the Keep Alive time.
316 	 *      The Client closes the Network Connection without first sending a DISCONNECT Packet.
317 	 *      The Server closes the Network Connection because of a protocol error.
318 	 *
319 	 * If the Will Flag is set to 1, the Will QoS and Will Retain fields in the Connect Flags will be used by
320 	 * the Server, and the Will Topic and Will Message fields MUST be present in the payload.
321 	 *
322 	 * The Will Message MUST be removed from the stored Session state in the Server once it has been published
323 	 * or the Server has received a DISCONNECT packet from the Client.
324 	 *
325 	 * If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero
326 	 * and the Will Topic and Will Message fields MUST NOT be present in the payload.
327 	 *
328 	 * If the Will Flag is set to 0, a Will Message MUST NOT be published when this Network Connection ends
329 	 */
330 	@property bool will() const
331 	{
332 		return (_payload & 0x04) == 0x04;
333 	}
334 
335 	/// ditto
336 	@property void will(in bool value)
337 	{
338 		_payload = cast(ubyte)((_payload & ~0x04) | (value ? 0x04 : 0x00));
339 	}
340 
341 	/**
342 	 * This bit specifies the handling of the Session state.
343 	 * The Client and Server can store Session state to enable reliable messaging to continue across a sequence
344 	 * of Network Connections. This bit is used to control the lifetime of the Session state.
345 	 *
346 	 * If CleanSession is set to 0, the Server MUST resume communications with the Client based on state from
347 	 * the current Session (as identified by the Client identifier).
348 	 * If there is no Session associated with the Client identifier the Server MUST create a new Session.
349 	 * The Client and Server MUST store the Session after the Client and Server are disconnected.
350 	 * After the disconnection of a Session that had CleanSession set to 0, the Server MUST store
351 	 * further QoS 1 and QoS 2 messages that match any subscriptions that the client had at the time of disconnection
352 	 * as part of the Session state.
353 	 * It MAY also store QoS 0 messages that meet the same criteria.
354 	 *
355 	 * If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one.
356 	 * This Session lasts as long as the Network Connection. State data associated with this Session MUST NOT be reused
357 	 * in any subsequent Session.
358 	 *
359 	 * The Session state in the Client consists of:
360 	 *      QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged.
361 	 *      QoS 2 messages which have been received from the Server, but have not been completely acknowledged.
362 	 *
363 	 * To ensure consistent state in the event of a failure, the Client should repeat its attempts to connect with
364 	 * CleanSession set to 1, until it connects successfully.
365 	 *
366 	 * Typically, a Client will always connect using CleanSession set to 0 or CleanSession set to 1 and not swap
367 	 * between the two values. The choice will depend on the application. A Client using CleanSession set to 1 will
368 	 * not receive old Application Messages and has to subscribe afresh to any topics that it is interested in each
369 	 * time it connects. A Client using CleanSession set to 0 will receive all QoS 1 or QoS 2 messages that were
370 	 * published while it was disconnected. Hence, to ensure that you do not lose messages while disconnected,
371 	 * use QoS 1 or QoS 2 with CleanSession set to 0.
372 	 *
373 	 * When a Client connects with CleanSession set to 0, it is requesting that the Server maintain its MQTT session
374 	 * state after it disconnects. Clients should only connect with CleanSession set to 0, if they intend to reconnect
375 	 * to the Server at some later point in time. When a Client has determined that it has no further use for
376 	 * the session, it should do a final connect with CleanSession set to 1 and then disconnect.
377 	 */
378 	@property bool cleanSession() const
379 	{
380 		return (_payload & 0x02) == 0x02;
381 	}
382 
383 	/// ditto
384 	@property void cleanSession(in bool value)
385 	{
386 		_payload = cast(ubyte)((_payload & ~0x02) | (value ? 0x02 : 0x00));
387 	}
388 
389 	@property ubyte flags() const
390 	{
391 		return _payload;
392 	}
393 
394 	@property void flags(ubyte value) pure
395 	{
396 		_payload = value & ~0x01;
397 	}
398 
399 	this(bool userName, bool password, bool willRetain, QoSLevel willQoS, bool will, bool cleanSession)
400 	{
401 		this.userName = userName;
402 		this.password = password;
403 		this.willRetain = willRetain;
404 		this.willQoS = willQoS;
405 		this.will = will;
406 		this.cleanSession = cleanSession;
407 	}
408 
409 	this(T)(T value) if(isIntegral!T)
410 	{
411 		this.flags = cast(ubyte)(value & ~0x01);
412 	}
413 
414 	alias flags this;
415 
416 	unittest
417 	{
418 		import std.array;
419 
420 		ConnectFlags flags;
421 
422 		assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, false, false));
423 		assert(flags == 0);
424 
425 		flags.flags = 1; //reserved - no change
426 		assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, false, false));
427 		assert(flags == 0);
428 
429 		flags.flags = 2;
430 		assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, false, true));
431 
432 		flags.flags = 4;
433 		assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, true, false));
434 
435 		flags.flags = 24;
436 		assert(flags == ConnectFlags(false, false, false, QoSLevel.Reserved, false, false));
437 
438 		flags.flags = 32;
439 		assert(flags == ConnectFlags(false, false, true, QoSLevel.QoS0, false, false));
440 
441 		flags.flags = 64;
442 		assert(flags == ConnectFlags(false, true, false, QoSLevel.QoS0, false, false));
443 
444 		flags.flags = 128;
445 		assert(flags == ConnectFlags(true, false, false, QoSLevel.QoS0, false, false));
446 	}
447 }
448 
449 /// Connect Acknowledge Flags
450 struct ConnAckFlags
451 {
452 	@safe pure @nogc nothrow:
453 	private ubyte _payload;
454 
455 	/**
456 	 * If the Server accepts a connection with CleanSession set to 1, the Server MUST set Session Present to 0
457 	 * in the CONNACK packet in addition to setting a zero return code in the CONNACK packet.
458 	 *
459 	 * If the Server accepts a connection with CleanSession set to 0, the value set in Session Present depends on
460 	 * whether the Server already has stored Session state for the supplied client ID. If the Server has stored
461 	 * Session state, it MUST set Session Present to 1 in the CONNACK packet.
462 	 * If the Server does not have stored Session state, it MUST set Session Present to 0 in the CONNACK packet.
463 	 * This is in addition to setting a zero return code in the CONNACK packet.
464 	 *
465 	 * The Session Present flag enables a Client to establish whether the Client and Server have a consistent view
466 	 * about whether there is already stored Session state.
467 	 *
468 	 * Once the initial setup of a Session is complete, a Client with stored Session state will expect the Server
469 	 * to maintain its stored Session state. In the event that the value of Session Present received by the Client
470 	 * from the Server is not as expected, the Client can choose whether to proceed with the Session or to disconnect.
471 	 * The Client can discard the Session state on both Client and Server by disconnecting, connecting with
472 	 * Clean Session set to 1 and then disconnecting again.
473 	 *
474 	 * If a server sends a CONNACK packet containing a non-zero return code it MUST set Session Present to 0
475 	 */
476 	@property bool sessionPresent() const
477 	{
478 		return (_payload & 0x01) == 0x01;
479 	}
480 
481 	/// ditto
482 	@property void sessionPresent(in bool value)
483 	{
484 		_payload = cast(ubyte)(value ? 0x01 : 0x00);
485 	}
486 
487 	@property ubyte flags() const
488 	{
489 		return _payload;
490 	}
491 
492 	@property void flags(ubyte value) pure
493 	{
494 		_payload = cast(ubyte)(value & 0x01); //clean reserved bits
495 	}
496 
497 	alias flags this;
498 
499 	this(T)(T value) if(isIntegral!T)
500 	{
501 		this.flags = cast(ubyte)value;
502 	}
503 }
504 
505 /// The payload of a SUBSCRIBE Packet
506 static struct Topic
507 {
508 	/**
509 	 * Topic Filter indicating the Topic to which the Client wants to subscribe.
510 	 * The Topic Filters in a SUBSCRIBE packet payload MUST be UTF-8 encoded strings.
511 	 * A Server SHOULD support Topic filters that contain the wildcard characters.
512 	 * If it chooses not to support topic filters that contain wildcard characters it MUST reject any Subscription request whose filter contains them
513 	 */
514 	string filter;
515 	/// This gives the maximum QoS level at which the Server can send Application Messages to the Client.
516 	QoSLevel qos;
517 }
518 
519 /**
520  * After a Network Connection is established by a Client to a Server,
521  * the first Packet sent from the Client to the Server MUST be a CONNECT Packet.
522  *
523  * A Client can only send the CONNECT Packet once over a Network Connection.
524  * The Server MUST process a second CONNECT Packet sent from a Client as a protocol violation and disconnect the Client.
525  *
526  * The payload contains one or more encoded fields.
527  * They specify a unique Client identifier for the Client, a Will topic, Will Message, User Name and Password.
528  * All but the Client identifier are optional and their presence is determined based on flags in the variable header.
529  */
530 struct Connect
531 {
532 	FixedHeader header = FixedHeader(0x10);
533 
534 	/// The Protocol Name is a UTF-8 encoded string that represents the protocol name “MQTT”
535 	string protocolName = MQTT_PROTOCOL_NAME;
536 
537 	/**
538 	 * The 8 bit unsigned value that represents the revision level of the protocol used by the Client.
539 	 * The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04).
540 	 */
541 	ubyte protocolLevel = MQTT_PROTOCOL_LEVEL_3_1_1;
542 
543 	/**
544 	 * The Connect Flags byte contains a number of parameters specifying the behavior of the MQTT connection.
545 	 * It also indicates the presence or absence of fields in the payload.
546 	 */
547 	ConnectFlags flags;
548 
549 	/**
550 	 * The Keep Alive is a time interval measured in seconds. Expressed as a 16-bit word, it is the maximum time
551 	 * interval that is permitted to elapse between the point at which the Client finishes transmitting one Control
552 	 * Packet and the point it starts sending the next. It is the responsibility of the Client to ensure that the
553 	 * interval between Control Packets being sent does not exceed the Keep Alive value.
554 	 * In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet.
555 	 *
556 	 * The Client can send PINGREQ at any time, irrespective of the Keep Alive value, and use the PINGRESP to determine
557 	 * that the network and the Server are working.
558 	 *
559 	 * If the Keep Alive value is non-zero and the Server does not receive a Control Packet from the Client within
560 	 * one and a half times the Keep Alive time period, it MUST disconnect the Network Connection to the Client as if
561 	 * the network had failed.
562 	 *
563 	 * If a Client does not receive a PINGRESP Packet within a reasonable amount of time after it has sent a PINGREQ,
564 	 * it SHOULD close the Network Connection to the Server.
565 	 *
566 	 * A Keep Alive value of zero (0) has the effect of turning off the keep alive mechanism.
567 	 * This means that, in this case, the Server is not required to disconnect the Client on the grounds of inactivity.
568 	 * Note that a Server is permitted to disconnect a Client that it determines to be inactive or non-responsive
569 	 * at any time, regardless of the Keep Alive value provided by that Client.
570 	 *
571 	 * The actual value of the Keep Alive is application specific; typically this is a few minutes.
572 	 * The maximum value is 18 hours 12 minutes and 15 seconds.
573 	 */
574 	ushort keepAlive;
575 
576 	/// Client Identifier
577 	string clientIdentifier;
578 
579 	/// Will Topic
580 	string willTopic;
581 
582 	/// Will Message
583 	string willMessage;
584 
585 	/// User Name
586 	string userName;
587 
588 	/// Password
589 	string password;
590 }
591 
592 /// Responce to Connect request
593 struct ConnAck
594 {
595 	FixedHeader header = FixedHeader(PacketType.CONNACK, 0, 2);
596 
597 	ConnAckFlags flags;
598 
599 	ConnectReturnCode returnCode;
600 }
601 
602 /// A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client to transport an Application Message.
603 struct Publish
604 {
605 	FixedHeader header = FixedHeader(0x30);
606 	/**
607 	 * The Topic Name identifies the information channel to which payload data is published.
608 	 *
609 	 * The Topic Name MUST be present as the first field in the PUBLISH Packet Variable header.
610 	 * It MUST be a UTF-8 encoded string.
611 	 *
612 	 * The Topic Name in the PUBLISH Packet MUST NOT contain wildcard characters.
613 	 * The Topic Name in a PUBLISH Packet sent by a Server to a subscribing Client MUST match the Subscription’s
614 	 * Topic Filter according to the matching process.
615 	 * However, since the Server is permitted to override the Topic Name, it might not be the same as the Topic Name
616 	 * in the original PUBLISH Packet.
617 	 */
618 	string topic;
619 	/// The Packet Identifier field is only present in PUBLISH Packets where the QoS level is 1 or 2
620 	ushort packetId; // if QoS > 0
621 	/**
622 	 * The Payload contains the Application Message that is being published.
623 	 * The content and format of the data is application specific.
624 	 * The length of the payload can be calculated by subtracting the length of the variable header from
625 	 * the Remaining Length field that is in the Fixed Header.
626 	 * It is valid for a PUBLISH Packet to contain a zero length payload.
627 	 */
628 	ubyte[] payload;
629 }
630 
631 /// A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1.
632 struct PubAck
633 {
634 	FixedHeader header = FixedHeader(PacketType.PUBACK, 0, 2);
635 
636 	/// This contains the Packet Identifier from the PUBLISH Packet that is being acknowledged.
637 	ushort packetId;
638 
639 	this(ushort packetId)
640 	{
641 		this.packetId = packetId;
642 	}
643 }
644 
645 /// A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second packet of the QoS 2 protocol exchange.
646 struct PubRec
647 {
648 	FixedHeader header = FixedHeader(PacketType.PUBREC, 0, 2);
649 
650 	/// This contains the Packet Identifier from the PUBLISH Packet that is being acknowledged.
651 	ushort packetId;
652 
653 	this(ushort packetId)
654 	{
655 		this.packetId = packetId;
656 	}
657 }
658 
659 /// A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the QoS 2 protocol exchange.
660 struct PubRel
661 {
662 	FixedHeader header = FixedHeader(PacketType.PUBREL, 0x02, 2);
663 
664 	/// This contains the same Packet Identifier as the PUBREC Packet that is being acknowledged.
665 	ushort packetId;
666 
667 	this(ushort packetId)
668 	{
669 		this.packetId = packetId;
670 	}
671 }
672 
673 /// The PUBCOMP Packet is the response to a PUBREL Packet. It is the fourth and final packet of the QoS 2 protocol exchange.
674 struct PubComp
675 {
676 	FixedHeader header = FixedHeader(PacketType.PUBCOMP, 0, 2);
677 
678 	/// This contains the same Packet Identifier as the PUBREC Packet that is being acknowledged.
679 	ushort packetId;
680 
681 	this(ushort packetId)
682 	{
683 		this.packetId = packetId;
684 	}
685 }
686 
687 /**
688  * The SUBSCRIBE Packet is sent from the Client to the Server to create one or more Subscriptions.
689  * Each Subscription registers a Client’s interest in one or more Topics.
690  * The Server sends PUBLISH Packets to the Client in order to forward Application Messages that were published to Topics that match these Subscriptions.
691  * The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with which the Server can send Application Messages to the Client.
692  *
693  * The payload of a SUBSCRIBE packet MUST contain at least one Topic Filter / QoS pair. A SUBSCRIBE packet with no payload is a protocol violation.
694  */
695 struct Subscribe
696 {
697 	FixedHeader header = FixedHeader(PacketType.SUBSCRIBE, 0x02);
698 
699 	/// This contains the Packet Identifier.
700 	ushort packetId;
701 
702 	/// Topics to register to
703 	Topic[] topics;
704 }
705 
706 /**
707  * A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing of a SUBSCRIBE Packet.
708  * A SUBACK Packet contains a list of return codes, that specify the maximum QoS level that was granted in each
709  * Subscription that was requested by the SUBSCRIBE.
710  */
711 struct SubAck
712 {
713 	FixedHeader header = FixedHeader(PacketType.SUBACK, 0);
714 
715 	/// This contains the Packet Identifier from the SUBSCRIBE Packet that is being acknowledged.
716 	ushort packetId;
717 	/**
718 	 * The payload contains a list of return codes. Each return code corresponds to a Topic Filter in the
719 	 * SUBSCRIBE Packet being acknowledged.
720 	 * The order of return codes in the SUBACK Packet MUST match the order of Topic Filters in the SUBSCRIBE Packet.
721 	 */
722 	QoSLevel[] returnCodes;
723 }
724 
725 /// An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics.
726 struct Unsubscribe
727 {
728 	FixedHeader header = FixedHeader(PacketType.UNSUBSCRIBE, 0x02);
729 
730 	/// This contains the Packet Identifier.
731 	ushort packetId;
732 
733 	/**
734 	 * The list of Topic Filters that the Client wishes to unsubscribe from. The Topic Filters in an UNSUBSCRIBE packet MUST be UTF-8 encoded strings.
735 	 * The Payload of an UNSUBSCRIBE packet MUST contain at least one Topic Filter. An UNSUBSCRIBE packet with no payload is a protocol violation.
736 	 */
737 	string[] topics;
738 }
739 
740 /// The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an UNSUBSCRIBE Packet.
741 struct UnsubAck
742 {
743 	FixedHeader header = FixedHeader(PacketType.UNSUBACK, 0, 2);
744 
745 	/// This contains the same Packet Identifier as the UNSUBSCRIBE Packet that is being acknowledged.
746 	ushort packetId;
747 }
748 
749 /**
750  * The PINGREQ Packet is sent from a Client to the Server. It can be used to:
751  *
752  * Indicate to the Server that the Client is alive in the absence of any other Control Packets being sent from the Client to the Server.
753  * Request that the Server responds to confirm that it is alive.
754  * Exercise the network to indicate that the Network Connection is active.
755  *
756  * This Packet is used in Keep Alive processing
757  */
758 struct PingReq
759 {
760 	FixedHeader header = FixedHeader(PacketType.PINGREQ, 0, 0);
761 }
762 
763 /**
764  * A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet. It indicates that the Server is alive.
765  * This Packet is used in Keep Alive processing.
766  */
767 struct PingResp
768 {
769 	FixedHeader header = FixedHeader(PacketType.PINGRESP, 0, 0);
770 }
771 
772 /**
773  * The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. It indicates that the Client is disconnecting cleanly.
774  *
775  * After sending a DISCONNECT Packet the Client:
776  *      MUST close the Network Connection.
777  *      MUST NOT send any more Control Packets on that Network Connection.
778  *
779  * On receipt of DISCONNECT the Server:
780  *      MUST discard any Will Message associated with the current connection without publishing it.
781  *      SHOULD close the Network Connection if the Client has not already done so.
782  */
783 struct Disconnect
784 {
785 	FixedHeader header = FixedHeader(PacketType.DISCONNECT, 0, 0);
786 }