Line data Source code
1 : /*
2 : * Package : mqtt_client
3 : * Author : S. Hamblett <steve.hamblett@linux.com>
4 : * Date : 30/06/2017
5 : * Copyright : S.Hamblett
6 : */
7 :
8 : part of mqtt_client;
9 :
10 : /// Handles the logic and workflow surrounding the message publishing and receipt process.
11 : ///
12 : /// It's probably worth going into a bit of the detail around publishing and Quality of Service levels
13 : /// as they are primarily the reason why message publishing has been split out into this class.
14 : ///
15 : /// There are 3 different QOS levels. AtMostOnce (0), means that the message, when sent from broker to client, or
16 : /// client to broker, should be delivered at most one time, and it does not matter if the message is
17 : /// "lost". QOS 2, AtLeastOnce, means that the message should be successfully received by the receiving
18 : /// party at least one time, so requires some sort of acknowledgement so the sender can re-send if the
19 : /// receiver does not acknowledge.
20 : ///
21 : /// QOS 3 is a bit more complicated as it provides the facility for guaranteed delivery of the message
22 : /// exactly one time, no more, no less.
23 : ///
24 : /// Each of these have different message flow between the sender and receiver.</para>
25 : /// QOS 0 - AtMostOnce
26 : /// Sender --> Publish --> Receiver
27 : /// QOS 1 - AtLeastOnce
28 : /// Sender --> Publish --> Receiver --> PublishAck --> Sender
29 : /// |
30 : /// v
31 : /// Message Processor
32 : /// QOS 2 - AtLeastOnce
33 : /// Sender --> Publish --> Receiver --> PublishReceived --> Sender --> PublishRelease --> Reciever --> PublishComplete --> Sender
34 : /// | v
35 : /// Message Processor
36 : class PublishingManager extends events.EventEmitter
37 : implements IPublishingManager {
38 : /// Handles dispensing of message ids for messages published to a topic.
39 : MessageIdentifierDispenser messageIdentifierDispenser =
40 : new MessageIdentifierDispenser();
41 :
42 : /// Stores messages that have been pubished but not yet acknowledged.
43 : Map<int, MqttPublishMessage> publishedMessages =
44 : new Map<int, MqttPublishMessage>();
45 :
46 : /// Stores messages that have been received from a broker with qos level 2 (Exactly Once).
47 : Map<int, MqttPublishMessage> receivedMessages =
48 : new Map<int, MqttPublishMessage>();
49 :
50 : /// Stores a cache of data converters used when publishing data to a broker.
51 : Map<Type, Object> dataConverters = new Map<Type, Object>();
52 :
53 : /// The current connection handler.
54 : IMqttConnectionHandler connectionHandler;
55 :
56 : /// Raised when a message has been recieved by the client and the relevant QOS handshake is complete.
57 : MessageReceived publishEvent;
58 :
59 : /// Initializes a new instance of the PublishingManager class.
60 3 : PublishingManager(IMqttConnectionHandler connectionHandler) {
61 3 : this.connectionHandler = connectionHandler;
62 3 : connectionHandler.registerForMessage(
63 3 : MqttMessageType.publishAck, handlePublishAcknowledgement);
64 3 : connectionHandler.registerForMessage(
65 3 : MqttMessageType.publish, handlePublish);
66 3 : connectionHandler.registerForMessage(
67 3 : MqttMessageType.publishComplete, handlePublishComplete);
68 3 : connectionHandler.registerForMessage(
69 3 : MqttMessageType.publishRelease, handlePublishRelease);
70 3 : connectionHandler.registerForMessage(
71 3 : MqttMessageType.publishReceived, handlePublishReceived);
72 : }
73 :
74 : /// Publish a message to the broker on the specified topic.
75 : /// The topic to send the message to
76 : /// The QOS to use when publishing the message.
77 : /// The message to send.
78 : /// The message identifier assigned to the message.
79 : int publish(PublicationTopic topic, MqttQos qualityOfService,
80 : typed.Uint8Buffer data) {
81 2 : final int msgId = messageIdentifierDispenser
82 4 : .getNextMessageIdentifier("topic:{$topic.toString()}");
83 2 : final MqttPublishMessage msg = new MqttPublishMessage()
84 4 : .toTopic(topic.toString())
85 2 : .withMessageIdentifier(msgId)
86 2 : .withQos(qualityOfService)
87 2 : .publishData(data);
88 : // QOS level 1 or 2 messages need to be saved so we can do the ack processes
89 2 : if (qualityOfService == MqttQos.atLeastOnce ||
90 2 : qualityOfService == MqttQos.exactlyOnce) {
91 4 : publishedMessages[msgId] = msg;
92 : }
93 4 : connectionHandler.sendMessage(msg);
94 : return msgId;
95 : }
96 :
97 : /// Handles the receipt of publish acknowledgement messages.
98 : /// This callback simply removes it from the list of published messages.
99 : bool handlePublishAcknowledgement(MqttMessage msg) {
100 1 : final MqttPublishAckMessage ackMsg = msg as MqttPublishAckMessage;
101 : // If we're expecting an ack for the message, remove it from the list of pubs awaiting ack.
102 2 : if (publishedMessages.keys
103 3 : .contains(ackMsg.variableHeader.messageIdentifier)) {
104 4 : publishedMessages.remove(ackMsg.variableHeader.messageIdentifier);
105 : }
106 : return true;
107 : }
108 :
109 : /// Handles the receipt of publish messages from a message broker.
110 : bool handlePublish(MqttMessage msg) {
111 2 : final MqttPublishMessage pubMsg = msg as MqttPublishMessage;
112 : bool publishSuccess = true;
113 : try {
114 : final PublicationTopic topic =
115 6 : new PublicationTopic(pubMsg.variableHeader.topicName);
116 6 : if (pubMsg.header.qos == MqttQos.atMostOnce) {
117 : // QOS AtMostOnce 0 require no response.
118 : // Send the message for processing to whoever is waiting.
119 4 : emitEvent(new MessageReceived(topic, msg));
120 6 : } else if (pubMsg.header.qos == MqttQos.atLeastOnce) {
121 : // QOS AtLeastOnce 1 require an acknowledgement
122 : // Send the message for processing to whoever is waiting.
123 4 : emitEvent(new MessageReceived(topic, msg));
124 2 : final MqttPublishAckMessage ackMsg = new MqttPublishAckMessage()
125 6 : .withMessageIdentifier(pubMsg.variableHeader.messageIdentifier);
126 4 : connectionHandler.sendMessage(ackMsg);
127 3 : } else if (pubMsg.header.qos == MqttQos.exactlyOnce) {
128 : // QOS ExactlyOnce means we can't give it away yet, we gotta do a handshake
129 : // to make sure the broker knows we got it, and we know he knows we got it.
130 : // If we've already got it thats ok, it just means its being republished because
131 : // of a handshake breakdown, overwrite our existing one for the sake of it
132 1 : if (!receivedMessages
133 3 : .containsKey(pubMsg.variableHeader.messageIdentifier)) {
134 4 : receivedMessages[pubMsg.variableHeader.messageIdentifier] = pubMsg;
135 : }
136 : final MqttPublishReceivedMessage pubRecv =
137 1 : new MqttPublishReceivedMessage()
138 3 : .withMessageIdentifier(pubMsg.variableHeader.messageIdentifier);
139 2 : connectionHandler.sendMessage(pubRecv);
140 : }
141 : } catch (Exception) {
142 : publishSuccess = false;
143 : }
144 : return publishSuccess;
145 : }
146 :
147 : /// Handles the publish complete, for messages that are undergoing Qos ExactlyOnce processing.
148 : bool handlePublishRelease(MqttMessage msg) {
149 : final MqttPublishReleaseMessage pubRelMsg =
150 1 : msg as MqttPublishReleaseMessage;
151 : bool publishSuccess = true;
152 : try {
153 : final MqttPublishMessage pubMsg =
154 4 : receivedMessages.remove(pubRelMsg.variableHeader.messageIdentifier);
155 : if (pubMsg != null) {
156 : // Send the message for processing to whoever is waiting.
157 : final PublicationTopic topic =
158 3 : new PublicationTopic(pubMsg.variableHeader.topicName);
159 2 : emitEvent(new MessageReceived(topic, pubMsg));
160 : final MqttPublishCompleteMessage compMsg =
161 1 : new MqttPublishCompleteMessage()
162 3 : .withMessageIdentifier(pubMsg.variableHeader.messageIdentifier);
163 2 : connectionHandler.sendMessage(compMsg);
164 : }
165 : } catch (Exception) {
166 : publishSuccess = false;
167 : }
168 : return publishSuccess;
169 : }
170 :
171 : /// Handles a publish complete message received from a broker.
172 : /// Returns true if the message flow completed successfully, otherwise false.
173 : bool handlePublishComplete(MqttMessage msg) {
174 : final MqttPublishCompleteMessage compMsg =
175 1 : msg as MqttPublishCompleteMessage;
176 : final MqttPublishMessage ok =
177 4 : publishedMessages.remove(compMsg.variableHeader.messageIdentifier);
178 : if (ok != null) {
179 : return true;
180 : }
181 : return false;
182 : }
183 :
184 : /// Handles publish received messages during processing of QOS level 2 (Exactly once) messages.
185 : /// Returns true or false, depending on the success of message processing.
186 : bool handlePublishReceived(MqttMessage msg) {
187 : final MqttPublishReceivedMessage recvMsg =
188 1 : msg as MqttPublishReceivedMessage;
189 : // If we've got a matching message, respond with a "ok release it for processing"
190 1 : if (publishedMessages
191 3 : .containsKey(recvMsg.variableHeader.messageIdentifier)) {
192 1 : final MqttPublishReleaseMessage relMsg = new MqttPublishReleaseMessage()
193 3 : .withMessageIdentifier(recvMsg.variableHeader.messageIdentifier);
194 2 : connectionHandler.sendMessage(relMsg);
195 : }
196 : return true;
197 : }
198 : }
|