LCOV - code coverage report
Current view: top level - src - mqtt_client_publishing_manager.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 58 58 100.0 %
Date: 2017-10-09 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.10