/// <summary> /// Creates a new subscription for the specified topic. /// </summary> /// <typeparam name="T">The type of data the subscription is expected to return.</typeparam> /// <typeparam name="TPayloadConverter">The type of the converter that can convert from bytes to the type T.</typeparam> /// <param name="topic">The topic to subscribe to.</param> /// <param name="qos">The QOS level to subscribe at.</param> /// <returns>An observable that yields messages when they arrive.</returns> /// <exception cref="InvalidTopicException">If a topic that does not meet the MQTT topic spec rules is provided.</exception> private IObservable <MqttReceivedMessage <T> > CreateNewSubscription <T, TPayloadConverter>(string topic, MqttQos qos) where TPayloadConverter : IPayloadConverter <T>, new() { Log.Info(m => m("Creating subscription for topoc {0} @ QOS {1}.", topic, qos)); try { var subscriptionTopic = new SubscriptionTopic(topic); // Get an ID that represents the subscription. We will use this same ID for unsubscribe as well. var msgId = messageIdentifierDispenser.GetNextMessageIdentifier("subscriptions"); // create a new observable that is used to yield messages // that arrive for the topoc. var observable = CreateObservableForSubscription(subscriptionTopic, msgId); var sub = new Subscription { Topic = subscriptionTopic, Qos = qos, MessageIdentifier = msgId, CreatedTime = DateTime.Now, Observable = observable, }; pendingSubscriptions.Add(sub.MessageIdentifier, sub); // build a subscribe message for the caller and send it off to the broker. var msg = new MqttSubscribeMessage().WithMessageIdentifier(sub.MessageIdentifier) .ToTopic(sub.Topic.ToString()) .AtQos(sub.Qos); connectionHandler.SendMessage(msg); return(WrapSubscriptionObservable <T, TPayloadConverter>(sub.Observable)); } catch (ArgumentException ex) { Log.Warn(m => m("Error while processing topoc {0}. topoc structure not valid.", topic), ex); throw new InvalidTopicException(ex.Message, topic, ex); } }
public void MultiWildcardOnlyTopicMatchesAnyRandomTopic() { var topic = new SubscriptionTopic("#"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm/closingprice"))); }
public void SingleLevelEqualTopicsMatch() { var topic = new SubscriptionTopic("finance"); Assert.True(topic.Matches(new PublicationTopic("finance"))); }
public void ToStringReturnsSameTopicAsInput() { const string expectedTopicString = "finance/ibm"; var topic = new SubscriptionTopic(expectedTopicString); Assert.Equal(expectedTopicString, topic.ToString()); }
public void TopicsDifferingOnlyByCaseDoNotMatch() { var topic = new SubscriptionTopic("finance"); Assert.False(topic.Matches(new PublicationTopic("Finance"))); }
public void TopicWithMultiWildcardDoesNotMatchTopicWithDifferenceBeforeWildcardLevel() { var topic = new SubscriptionTopic("finance/#"); Assert.False(topic.Matches(new PublicationTopic("money/ibm"))); }
public void TopicWithSingleWildcardAtEndMatchesAnythingInSameLevel() { var topic = new SubscriptionTopic("finance/+/closingprice"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm/closingprice"))); }
public void TopicWithSingleWildcardAtEndDoesNotMatchTopicThatGoesDeeper() { var topic = new SubscriptionTopic("finance/+"); Assert.False(topic.Matches(new PublicationTopic("finance/ibm/closingprice"))); }
public void TopicWithSingleWildcardAtEndMatchesTopicWithEmptyLastFragmentAtThatSpot() { var topic = new SubscriptionTopic("finance/ibm/+"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm/"))); }
public void SingleLevelNonEqualTopicsDoNotMatch() { var topic = new SubscriptionTopic("finance"); Assert.False(topic.Matches(new PublicationTopic("money"))); }
public void TopicWithSingleWildcardMatchesTopicEmptyFragmentAtThatPoint() { var topic = new SubscriptionTopic("finance/+/closingprice"); Assert.True(topic.Matches(new PublicationTopic("finance//closingprice"))); }
public void TopicWithSingleAndMultiWildcardMatchesTopicWithAnyValueAtThoseLevelsAndDeeper() { var topic = new SubscriptionTopic("finance/+/closingprice/month/#"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm/closingprice/month/october/2014"))); }
public void TopicWithMoreThanOneSingleWildcardAtDifferentLevelsMatchesTopicWithAnyValueAtThoseLevels() { var topic = new SubscriptionTopic("finance/+/closingprice/month/+"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm/closingprice/month/october"))); }
public void MultiWildcardOnlyTopicMatchesTopicStartingWithSeparator() { var topic = new SubscriptionTopic("#"); Assert.True(topic.Matches(new PublicationTopic("/finance/ibm/closingprice"))); }
public void TopicWithSingleWildcardAtEndDoesNotMatchTopicThatDoesNotContainAnythingAtSameLevel() { var topic = new SubscriptionTopic("finance/+"); Assert.False(topic.Matches(new PublicationTopic("finance"))); }
public void TopicWithMultiWildcardAtEndMatchesTopicThatDoesNotMatchSameDepth() { var topic = new SubscriptionTopic("finance/#"); Assert.True(topic.Matches(new PublicationTopic("finance"))); }
public void MultiLevelNonEqualTopicsDoNotMatch() { var topic = new SubscriptionTopic("finance/ibm/closingprice"); Assert.False(topic.Matches(new PublicationTopic("some/random/topic"))); }
/// <summary> /// Creates an observable for a subscription. /// </summary> /// <param name="subscriptionTopic">The topic to the obserbable should read messages on.</param> /// <param name="msgId">The messgeid assigned to the subscription.</param> /// <returns>An observable that yields a byte array for each message that arrives on a topoc.</returns> private IObservable <MqttReceivedMessage <byte[]> > CreateObservableForSubscription(SubscriptionTopic subscriptionTopic, short msgId) { var observable = Observable.Create <MqttReceivedMessage <byte[]> >(observer => { Log.Info(m => m("Creating underlying core observable for topoc {0}.", subscriptionTopic)); // Listen for payload messages and when they arrive for our topoc // publish them onto the observable. var msgPubObservable = Observable.FromEventPattern <PublishEventArgs>(h => publishingManager.MessageReceived += h, h => publishingManager.MessageReceived -= h); var msgPubSub = msgPubObservable .Where(ep => subscriptionTopic.Matches(ep.EventArgs.Topic)) .Select(ep => ep.EventArgs) .Subscribe(eventArgs => { try { // we use the messages topic name here so that if the // matched subscription was a wildcard, we give the // consumer the actual topic the message was published // for, not the original wildcard subscription topic. observer.OnNext(new MqttReceivedMessage <byte[]>(eventArgs.Topic.ToString(), eventArgs.PublishMessage.Payload.Message.ToArray())); } catch (Exception ex) { Log.Error(m => m("Error while publishing message to observer for topic {0}.", eventArgs.Topic.ToString()), ex); } }); // Unsubscribe from the topoc on the server, return(Disposable.Create(() => { Log.Info(m => m("Last subscriber gone for topic '{0}', unsubscribing on broker.", subscriptionTopic)); // stop processing publish messages for this topoc received by thethe publishing manager. msgPubSub.Dispose(); // build a unsubscribe message for the caller and send it off to the broker. var unsubscribeMsg = new MqttUnsubscribeMessage() .WithMessageIdentifier(messageIdentifierDispenser.GetNextMessageIdentifier("unsubscriptions")) .WithMessageIdentifier(msgId) .FromTopic(subscriptionTopic.ToString()); connectionHandler.SendMessage(unsubscribeMsg); })); }); // Publish and refcount so we can share the single subscription amongst all // subscribers and dispose automatically when everyone has disposed their // subscriptions. return(observable.Publish() .RefCount()); }
public void TopicWithMultiWildcardAtEndMatchesTopicWithAnythingAtWildcardLevel() { var topic = new SubscriptionTopic("finance/#"); Assert.True(topic.Matches(new PublicationTopic("finance/ibm"))); }