Line data Source code
1 : /*
2 : * Package : mqtt_client
3 : * Author : S. Hamblett <steve.hamblett@linux.com>
4 : * Date : 31/05/2017
5 : * Copyright : S.Hamblett
6 : */
7 :
8 : part of mqtt_client;
9 :
10 : /// Implementation of a Subscription topic that performs additional validations
11 : /// of topics that are subscribed to.
12 : class SubscriptionTopic extends Topic {
13 : /// Creates a new instance of a rawTopic from a topic string.
14 : SubscriptionTopic(String rawTopic)
15 4 : : super(rawTopic, [
16 : Topic.validateMinLength,
17 : Topic.validateMaxLength,
18 : _validateMultiWildcard,
19 : _validateFragments
20 : ]);
21 :
22 : /// Validates all unique fragments in the topic match the MQTT spec requirements.
23 : static void _validateFragments(Topic topicInstance) {
24 : // If any fragment contains a wildcard or a multi wildcard but is greater than
25 : // 1 character long, then it's an error - wildcards must appear by themselves.
26 4 : final bool invalidFragment = topicInstance.topicFragments.any(
27 : (String fragment) =>
28 2 : (fragment.contains(Topic.multiWildcard) ||
29 2 : fragment.contains(Topic.wildcard)) &&
30 2 : fragment.length > 1);
31 : if (invalidFragment) {
32 1 : throw new Exception(
33 : "mqtt_client::SubscriptionTopic: rawTopic Fragment contains a wildcard but is more than one character long");
34 : }
35 : }
36 :
37 : /// Validates the placement of the multi-wildcard character in subscription topics.
38 : static void _validateMultiWildcard(Topic topicInstance) {
39 4 : if (topicInstance.rawTopic.contains(Topic.multiWildcard) &&
40 2 : !topicInstance.rawTopic.endsWith(Topic.multiWildcard)) {
41 1 : throw new Exception(
42 : "mqtt_client::SubscriptionTopic: The rawTopic wildcard # can only be present at the end of a topic");
43 : }
44 6 : if (topicInstance.rawTopic.length > 1 &&
45 4 : topicInstance.rawTopic.endsWith(Topic.multiWildcard) &&
46 2 : !topicInstance.rawTopic.endsWith(Topic.multiWildcardValidEnd)) {
47 1 : throw new Exception(
48 : "mqtt_client::SubscriptionTopic: Topics using the # wildcard longer than 1 character must "
49 : "be immediately preceeded by a the rawTopic separator /");
50 : }
51 : }
52 :
53 : /// Checks if the rawTopic matches the supplied rawTopic using the MQTT rawTopic matching rules.
54 : /// Returns true if the rawTopic matches based on the MQTT rawTopic matching rules, otherwise false.
55 : bool matches(PublicationTopic matcheeTopic) {
56 : // If the left rawTopic is just a multi wildcard then we have a match without
57 : // needing to check any further.
58 2 : if (this.rawTopic == Topic.multiWildcard) {
59 : return true;
60 : }
61 : // If the topics are an exact match, bail early with a cheap comparison
62 3 : if (this.rawTopic == matcheeTopic.rawTopic) {
63 : return true;
64 : }
65 : // no match yet so we need to check each fragment
66 4 : for (int i = 0; i < this.topicFragments.length; i++) {
67 2 : final String lhsFragment = topicFragments[i];
68 : // If we've reached a multi wildcard in the lhs rawTopic,
69 : // we have a match.
70 : // (this is the mqtt spec rule finance matches finance or finance/#)
71 1 : if (lhsFragment == Topic.multiWildcard) {
72 : return true;
73 : }
74 1 : final bool isLhsWildcard = lhsFragment == Topic.wildcard;
75 : // If we've reached a wildcard match but the matchee does not have anything at
76 : // this fragment level then it's not a match.
77 : // (this is the mqtt spec rule finance does not match finance/+
78 3 : if (isLhsWildcard && matcheeTopic.topicFragments.length <= i) {
79 : return false;
80 : }
81 : // if lhs is not a wildcard we need to check whether the
82 : // two fragments match each other.
83 : if (!isLhsWildcard) {
84 2 : final String rhsFragment = matcheeTopic.topicFragments[i];
85 : // If the hs fragment is not wildcard then we need an exact match
86 1 : if (lhsFragment != rhsFragment) {
87 : return false;
88 : }
89 : }
90 : // If we're at the last fragment of the lhs rawTopic but there are
91 : // more fragments in the in the matchee then the matchee rawTopic
92 : // is too specific to be a match.
93 4 : if (i + 1 == this.topicFragments.length &&
94 5 : matcheeTopic.topicFragments.length > this.topicFragments.length) {
95 : return false;
96 : }
97 : // If we're here the current fragment matches so check the next
98 : }
99 : // If we exit out of the loop without a return then we have a full match rawTopic/rawTopic which would
100 : // have been caught by the original exact match check at the top anyway.
101 : return true;
102 : }
103 : }
|