static void FilterRetainedApplicationMessages( IList <MqttApplicationMessage> retainedApplicationMessages, CreateSubscriptionResult createSubscriptionResult, SubscribeResult subscribeResult) { for (var index = retainedApplicationMessages.Count - 1; index >= 0; index--) { var retainedApplicationMessage = retainedApplicationMessages[index]; if (retainedApplicationMessage == null) { continue; } if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe) { // This is a MQTT V5+ feature. continue; } if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription) { // This is a MQTT V5+ feature. continue; } if (MqttTopicFilterComparer.Compare(retainedApplicationMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) { continue; } var retainedMessageMatch = new MqttRetainedMessageMatch { ApplicationMessage = retainedApplicationMessage, SubscriptionQualityOfServiceLevel = createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel }; if (subscribeResult.RetainedMessages == null) { subscribeResult.RetainedMessages = new List <MqttRetainedMessageMatch>(); } subscribeResult.RetainedMessages.Add(retainedMessageMatch); // Clear the retained message from the list because the client should receive every message only // one time even if multiple subscriptions affect them. retainedApplicationMessages[index] = null; } }
public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) { var possibleSubscriptions = new List <MqttSubscription>(); // Check for possible subscriptions. They might have collisions but this is fine. _subscriptionsLock.Wait(); try { if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions)) { possibleSubscriptions.AddRange(noWildcardSubscriptions.ToList()); } foreach (var wcs in _wildcardSubscriptionsByTopicHash) { var wildcardSubscriptions = wcs.Value; var subscriptionHash = wcs.Key; var subscriptionHashMask = wildcardSubscriptions.HashMask; if ((topicHash & subscriptionHashMask) == subscriptionHash) { possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList()); } } } finally { _subscriptionsLock.Release(); } // The pre check has evaluated that nothing is subscribed. // If there were some possible candidates they get checked below // again to avoid collisions. if (possibleSubscriptions.Count == 0) { return(CheckSubscriptionsResult.NotSubscribed); } var senderIsReceiver = string.Equals(senderClientId, _session.Id); var maxQoSLevel = -1; // Not subscribed. HashSet <uint> subscriptionIdentifiers = null; var retainAsPublished = false; foreach (var subscription in possibleSubscriptions) { if (subscription.NoLocal && senderIsReceiver) { // This is a MQTTv5 feature! continue; } if (MqttTopicFilterComparer.Compare(topic, subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) { continue; } if (subscription.RetainAsPublished) { // This is a MQTTv5 feature! retainAsPublished = true; } if ((int)subscription.GrantedQualityOfServiceLevel > maxQoSLevel) { maxQoSLevel = (int)subscription.GrantedQualityOfServiceLevel; } if (subscription.Identifier > 0) { if (subscriptionIdentifiers == null) { subscriptionIdentifiers = new HashSet <uint>(); } subscriptionIdentifiers.Add(subscription.Identifier); } } if (maxQoSLevel == -1) { return(CheckSubscriptionsResult.NotSubscribed); } var result = new CheckSubscriptionsResult { IsSubscribed = true, RetainAsPublished = retainAsPublished, SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers, // Start with the same QoS as the publisher. QualityOfServiceLevel = applicationMessageQoSLevel }; // Now downgrade if required. // // If a subscribing Client has been granted maximum QoS 1 for a particular Topic Filter, then a QoS 0 Application Message matching the filter is delivered // to the Client at QoS 0. This means that at most one copy of the message is received by the Client. On the other hand, a QoS 2 Message published to // the same topic is downgraded by the Server to QoS 1 for delivery to the Client, so that Client might receive duplicate copies of the Message. // Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published". // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server // downgrades the QoS to one more suitable for its usage. if (maxQoSLevel < (int)applicationMessageQoSLevel) { result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel; } return(result); }