1 /** 2 * 3 * /home/tomas/workspace/mqtt-d/source/mqttd/message.d 4 * 5 * Author: 6 * Tomáš Chaloupka <chalucha@gmail.com> 7 * 8 * Copyright (c) 2015 Tomáš Chaloupka 9 * 10 * Boost Software License 1.0 (BSL-1.0) 11 * 12 * Permission is hereby granted, free of charge, to any person or organization obtaining a copy 13 * of the software and accompanying documentation covered by this license (the "Software") to use, 14 * reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative 15 * works of the Software, and to permit third-parties to whom the Software is furnished to do so, 16 * all subject to the following: 17 * 18 * The copyright notices in the Software and this entire statement, including the above license 19 * grant, this restriction and the following disclaimer, must be included in all copies of the Software, 20 * in whole or in part, and all derivative works of the Software, unless such copies or derivative works 21 * are solely in the form of machine-executable object code generated by a source language processor. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 24 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 25 * PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE 26 * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, 27 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 * OTHER DEALINGS IN THE SOFTWARE. 29 */ 30 module mqttd.messages; 31 32 import std.range; 33 import std.exception : enforce; 34 import std.traits : isIntegral; 35 import std.typecons : Nullable; 36 debug import std.stdio; 37 38 import mqttd.traits; 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 = 1; //reserved - no change 426 assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, false, false)); 427 assert(flags == 0); 428 429 flags = 2; 430 assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, false, true)); 431 432 flags = 4; 433 assert(flags == ConnectFlags(false, false, false, QoSLevel.QoS0, true, false)); 434 435 flags = 24; 436 assert(flags == ConnectFlags(false, false, false, QoSLevel.Reserved, false, false)); 437 438 flags = 32; 439 assert(flags == ConnectFlags(false, false, true, QoSLevel.QoS0, false, false)); 440 441 flags = 64; 442 assert(flags == ConnectFlags(false, true, false, QoSLevel.QoS0, false, false)); 443 444 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 640 /// A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second packet of the QoS 2 protocol exchange. 641 struct PubRec 642 { 643 FixedHeader header = FixedHeader(PacketType.PUBREC, 0, 2); 644 /// This contains the Packet Identifier from the PUBLISH Packet that is being acknowledged. 645 ushort packetId; 646 } 647 648 /// A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the QoS 2 protocol exchange. 649 struct PubRel 650 { 651 FixedHeader header = FixedHeader(PacketType.PUBREL, 0x02, 2); 652 653 /// This contains the same Packet Identifier as the PUBREC Packet that is being acknowledged. 654 ushort packetId; 655 } 656 657 /// The PUBCOMP Packet is the response to a PUBREL Packet. It is the fourth and final packet of the QoS 2 protocol exchange. 658 struct PubComp 659 { 660 FixedHeader header = FixedHeader(PacketType.PUBCOMP, 0, 2); 661 662 /// This contains the same Packet Identifier as the PUBREC Packet that is being acknowledged. 663 ushort packetId; 664 } 665 666 /** 667 * The SUBSCRIBE Packet is sent from the Client to the Server to create one or more Subscriptions. 668 * Each Subscription registers a Client’s interest in one or more Topics. 669 * The Server sends PUBLISH Packets to the Client in order to forward Application Messages that were published to Topics that match these Subscriptions. 670 * The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with which the Server can send Application Messages to the Client. 671 * 672 * 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. 673 */ 674 struct Subscribe 675 { 676 FixedHeader header = FixedHeader(PacketType.SUBSCRIBE, 0x02); 677 678 /// This contains the Packet Identifier. 679 ushort packetId; 680 681 /// Topics to register to 682 Topic[] topics; 683 } 684 685 /** 686 * A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing of a SUBSCRIBE Packet. 687 * A SUBACK Packet contains a list of return codes, that specify the maximum QoS level that was granted in each 688 * Subscription that was requested by the SUBSCRIBE. 689 */ 690 struct SubAck 691 { 692 FixedHeader header = FixedHeader(PacketType.SUBACK, 0); 693 694 /// This contains the Packet Identifier from the SUBSCRIBE Packet that is being acknowledged. 695 ushort packetId; 696 /** 697 * The payload contains a list of return codes. Each return code corresponds to a Topic Filter in the 698 * SUBSCRIBE Packet being acknowledged. 699 * The order of return codes in the SUBACK Packet MUST match the order of Topic Filters in the SUBSCRIBE Packet. 700 */ 701 QoSLevel[] returnCodes; 702 } 703 704 /// An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. 705 struct Unsubscribe 706 { 707 FixedHeader header = FixedHeader(PacketType.UNSUBSCRIBE, 0x02); 708 709 /// This contains the Packet Identifier. 710 ushort packetId; 711 712 /** 713 * 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. 714 * The Payload of an UNSUBSCRIBE packet MUST contain at least one Topic Filter. An UNSUBSCRIBE packet with no payload is a protocol violation. 715 */ 716 string[] topics; 717 } 718 719 /// The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an UNSUBSCRIBE Packet. 720 struct UnsubAck 721 { 722 FixedHeader header = FixedHeader(PacketType.UNSUBACK, 0, 2); 723 724 /// This contains the same Packet Identifier as the UNSUBSCRIBE Packet that is being acknowledged. 725 ushort packetId; 726 } 727 728 /** 729 * The PINGREQ Packet is sent from a Client to the Server. It can be used to: 730 * 731 * 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. 732 * Request that the Server responds to confirm that it is alive. 733 * Exercise the network to indicate that the Network Connection is active. 734 * 735 * This Packet is used in Keep Alive processing 736 */ 737 struct PingReq 738 { 739 FixedHeader header = FixedHeader(PacketType.PINGREQ, 0, 0); 740 } 741 742 /** 743 * A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet. It indicates that the Server is alive. 744 * This Packet is used in Keep Alive processing. 745 */ 746 struct PingResp 747 { 748 FixedHeader header = FixedHeader(PacketType.PINGRESP, 0, 0); 749 } 750 751 /** 752 * The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. It indicates that the Client is disconnecting cleanly. 753 * 754 * After sending a DISCONNECT Packet the Client: 755 * MUST close the Network Connection. 756 * MUST NOT send any more Control Packets on that Network Connection. 757 * 758 * On receipt of DISCONNECT the Server: 759 * MUST discard any Will Message associated with the current connection without publishing it. 760 * SHOULD close the Network Connection if the Client has not already done so. 761 */ 762 struct Disconnect 763 { 764 FixedHeader header = FixedHeader(PacketType.DISCONNECT, 0, 0); 765 }