Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #pragma once
19 :
20 : #include <cstddef>
21 : #include <cstdint>
22 : #include <optional>
23 :
24 : #include <lib/core/PeerId.h>
25 : #include <lib/dnssd/Resolver.h>
26 : #include <lib/dnssd/minimal_mdns/core/HeapQName.h>
27 : #include <lib/support/Variant.h>
28 : #include <system/SystemClock.h>
29 :
30 : namespace mdns {
31 : namespace Minimal {
32 :
33 : /// Keeps track of active resolve attempts
34 : ///
35 : /// Maintains a list of 'pending mdns resolve queries' and provides operations
36 : /// for:
37 : /// - add/remove to the list
38 : /// - figuring out a 'next query time' for items in the list
39 : /// - iterating through the 'schedule now' items of the list
40 : ///
41 : class ActiveResolveAttempts
42 : {
43 : public:
44 : static constexpr size_t kRetryQueueSize = 4;
45 : static constexpr chip::System::Clock::Timeout kMaxRetryDelay = chip::System::Clock::Seconds16(16);
46 :
47 : struct ScheduledAttempt
48 : {
49 : struct Browse
50 : {
51 0 : Browse(const chip::Dnssd::DiscoveryFilter discoveryFilter, const chip::Dnssd::DiscoveryType discoveryType) :
52 0 : filter(discoveryFilter), type(discoveryType)
53 0 : {}
54 : chip::Dnssd::DiscoveryFilter filter;
55 : chip::Dnssd::DiscoveryType type;
56 : };
57 :
58 : struct Resolve
59 : {
60 : chip::PeerId peerId;
61 : uint32_t consumerCount = 0;
62 :
63 0 : Resolve(chip::PeerId id) : peerId(id) {}
64 : };
65 :
66 : struct IpResolve
67 : {
68 : HeapQName hostName;
69 0 : IpResolve(HeapQName && host) : hostName(std::move(host)) {}
70 : };
71 :
72 252 : ScheduledAttempt()
73 252 : {
74 : static_assert(sizeof(Resolve) <= sizeof(Browse) || sizeof(Resolve) <= sizeof(HeapQName),
75 : "Figure out where to put the Resolve counter so that Resolve is not making ScheduledAttempt bigger than "
76 : "it has to be anyway to handle the other attempt types.");
77 252 : }
78 0 : ScheduledAttempt(const chip::PeerId & peer, bool first) :
79 0 : resolveData(chip::InPlaceTemplateType<Resolve>(), peer), firstSend(first)
80 0 : {}
81 0 : ScheduledAttempt(const chip::Dnssd::DiscoveryFilter discoveryFilter, const chip::Dnssd::DiscoveryType type, bool first) :
82 0 : resolveData(chip::InPlaceTemplateType<Browse>(), discoveryFilter, type), firstSend(first)
83 0 : {}
84 :
85 0 : ScheduledAttempt(IpResolve && ipResolve, bool first) :
86 0 : resolveData(chip::InPlaceTemplateType<IpResolve>(), ipResolve), firstSend(first)
87 0 : {}
88 :
89 : bool operator==(const ScheduledAttempt & other) const { return Matches(other) && other.firstSend == firstSend; }
90 0 : bool Matches(const ScheduledAttempt & other) const
91 : {
92 0 : if (!resolveData.Valid())
93 : {
94 0 : return !other.resolveData.Valid();
95 : }
96 :
97 0 : if (resolveData.Is<Browse>())
98 : {
99 0 : if (!other.resolveData.Is<Browse>())
100 : {
101 0 : return false;
102 : }
103 :
104 0 : auto & a = resolveData.Get<Browse>();
105 0 : auto & b = other.resolveData.Get<Browse>();
106 0 : return (a.filter == b.filter && a.type == b.type);
107 : }
108 :
109 0 : if (resolveData.Is<Resolve>())
110 : {
111 0 : if (!other.resolveData.Is<Resolve>())
112 : {
113 0 : return false;
114 : }
115 0 : auto & a = resolveData.Get<Resolve>();
116 0 : auto & b = other.resolveData.Get<Resolve>();
117 :
118 0 : return a.peerId == b.peerId;
119 : }
120 :
121 0 : if (resolveData.Is<IpResolve>())
122 : {
123 0 : if (!other.resolveData.Is<IpResolve>())
124 : {
125 0 : return false;
126 : }
127 0 : auto & a = resolveData.Get<IpResolve>();
128 0 : auto & b = other.resolveData.Get<IpResolve>();
129 :
130 0 : return a.hostName == b.hostName;
131 : }
132 0 : return false;
133 : }
134 :
135 48 : bool MatchesIpResolve(SerializedQNameIterator hostName) const
136 : {
137 48 : return resolveData.Is<IpResolve>() && (hostName == resolveData.Get<IpResolve>().hostName.Content());
138 : }
139 6 : bool Matches(const chip::PeerId & peer) const
140 : {
141 6 : return resolveData.Is<Resolve>() && (resolveData.Get<Resolve>().peerId == peer);
142 : }
143 : bool Matches(const chip::Dnssd::DiscoveredNodeData & data, chip::Dnssd::DiscoveryType type) const
144 : {
145 : if (!resolveData.Is<Browse>())
146 : {
147 : return false;
148 : }
149 :
150 : auto & browse = resolveData.Get<Browse>();
151 :
152 : if (browse.type != type)
153 : {
154 : return false;
155 : }
156 : auto & nodeData = data.Get<chip::Dnssd::CommissionNodeData>();
157 :
158 : switch (browse.filter.type)
159 : {
160 : case chip::Dnssd::DiscoveryFilterType::kNone:
161 : return true;
162 : case chip::Dnssd::DiscoveryFilterType::kShortDiscriminator:
163 : return browse.filter.code == static_cast<uint64_t>((nodeData.longDiscriminator >> 8) & 0x0F);
164 : case chip::Dnssd::DiscoveryFilterType::kLongDiscriminator:
165 : return browse.filter.code == nodeData.longDiscriminator;
166 : case chip::Dnssd::DiscoveryFilterType::kVendorId:
167 : return browse.filter.code == nodeData.vendorId;
168 : case chip::Dnssd::DiscoveryFilterType::kDeviceType:
169 : return browse.filter.code == nodeData.deviceType;
170 : case chip::Dnssd::DiscoveryFilterType::kCommissioningMode:
171 : return browse.filter.code == nodeData.commissioningMode;
172 : case chip::Dnssd::DiscoveryFilterType::kInstanceName:
173 : return strncmp(browse.filter.instanceName, nodeData.instanceName,
174 : chip::Dnssd::Commission::kInstanceNameMaxLength + 1) == 0;
175 : case chip::Dnssd::DiscoveryFilterType::kCommissioner:
176 : case chip::Dnssd::DiscoveryFilterType::kCompressedFabricId:
177 : default:
178 : // These are for other discovery types.
179 : return false;
180 : }
181 : }
182 :
183 583 : bool IsEmpty() const { return !resolveData.Valid(); }
184 17 : bool IsResolve() const { return resolveData.Is<Resolve>(); }
185 56 : bool IsBrowse() const { return resolveData.Is<Browse>(); }
186 0 : bool IsIpResolve() const { return resolveData.Is<IpResolve>(); }
187 290 : void Clear() { resolveData = DataType(); }
188 :
189 : // Called when this scheduled attempt will replace an existing scheduled
190 : // attempt, because either they match or we have run out of attempt
191 : // slots. When this happens, we want to propagate the consumer count
192 : // from the existing attempt to the new one, if they're matching resolve
193 : // attempts.
194 17 : void WillCoalesceWith(const ScheduledAttempt & existing)
195 : {
196 17 : if (!IsResolve())
197 : {
198 : // Consumer count is only tracked for resolve requests
199 4 : return;
200 : }
201 :
202 13 : if (!existing.Matches(*this))
203 : {
204 : // Out of attempt slots, so just dropping the existing attempt.
205 12 : return;
206 : }
207 :
208 : // Adding another consumer to the same query. Propagate along the
209 : // consumer count to our new attempt, which will replace the
210 : // existing one.
211 1 : resolveData.Get<Resolve>().consumerCount = existing.resolveData.Get<Resolve>().consumerCount + 1;
212 : }
213 :
214 0 : void ConsumerRemoved()
215 : {
216 0 : if (!IsResolve())
217 : {
218 0 : return;
219 : }
220 :
221 0 : auto & count = resolveData.Get<Resolve>().consumerCount;
222 0 : if (count > 0)
223 : {
224 0 : --count;
225 : }
226 :
227 0 : if (count == 0)
228 : {
229 0 : Clear();
230 : }
231 : }
232 :
233 0 : const Browse & BrowseData() const { return resolveData.Get<Browse>(); }
234 0 : const Resolve & ResolveData() const { return resolveData.Get<Resolve>(); }
235 0 : const IpResolve & IpResolveData() const { return resolveData.Get<IpResolve>(); }
236 :
237 : using DataType = chip::Variant<Browse, Resolve, IpResolve>;
238 :
239 : DataType resolveData;
240 :
241 : // First packet send is marked separately: minMDNS logic can choose
242 : // to first send a unicast query followed by a multicast one.
243 : bool firstSend = false;
244 : };
245 :
246 315 : ActiveResolveAttempts(chip::System::Clock::ClockBase * clock) : mClock(clock) { Reset(); }
247 :
248 : /// Clear out the internal queue
249 : void Reset();
250 :
251 : /// Mark a resolution as a success, removing it from the internal list
252 : void Complete(const chip::PeerId & peerId);
253 : void CompleteIpResolution(SerializedQNameIterator targetHostName);
254 :
255 : /// Mark all browse-type scheduled attemptes as a success, removing them
256 : /// from the internal list.
257 : CHIP_ERROR CompleteAllBrowses();
258 :
259 : /// Note that resolve attempts for the given peer id now have one fewer
260 : /// consumer.
261 : void NodeIdResolutionNoLongerNeeded(const chip::PeerId & peerId);
262 :
263 : /// Mark that a resolution is pending, adding it to the internal list
264 : ///
265 : /// Once this complete, this peer id will be returned immediately
266 : /// by NextScheduled (potentially with others as well)
267 : void MarkPending(const chip::PeerId & peerId);
268 : void MarkPending(const chip::Dnssd::DiscoveryFilter & filter, const chip::Dnssd::DiscoveryType type);
269 : void MarkPending(ScheduledAttempt::IpResolve && resolve);
270 :
271 : // Get minimum time until the next pending reply is required.
272 : //
273 : // Returns missing if no actively tracked elements exist.
274 : std::optional<chip::System::Clock::Timeout> GetTimeUntilNextExpectedResponse() const;
275 :
276 : // Get the peer Id that needs scheduling for a query
277 : //
278 : // Assumes that the resolution is being sent and will apply internal
279 : // query logic. This means:
280 : // - internal tracking of 'next due time' will updated as 'request sent
281 : // now'
282 : // - there is NO sorting implied by this call. Returned value will be
283 : // any peer that needs a new request sent
284 : std::optional<ScheduledAttempt> NextScheduled();
285 :
286 : /// Check if any of the pending queries are for the given host name for
287 : /// IP resolution.
288 : bool IsWaitingForIpResolutionFor(SerializedQNameIterator hostName) const;
289 :
290 : /// Determines if address resolution for the given peer ID is required
291 : ///
292 : /// IP Addresses are required for active operational discovery of specific peers
293 : /// or if an active browse is being performed.
294 : bool ShouldResolveIpAddress(chip::PeerId peerId) const;
295 :
296 : /// Check if a browse operation is active for the given discovery type
297 : bool HasBrowseFor(chip::Dnssd::DiscoveryType type) const;
298 :
299 : private:
300 : struct RetryEntry
301 : {
302 : ScheduledAttempt attempt;
303 : // When a reply is expected for this item
304 : chip::System::Clock::Timestamp queryDueTime;
305 :
306 : // Next expected delay for sending if reply is not reached by
307 : // 'queryDueTimeMs'
308 : //
309 : // Based on RFC 6762 expectations are:
310 : // - the interval between the first two queries MUST be at least
311 : // one second
312 : // - the intervals between successive queries MUST increase by at
313 : // least a factor of two
314 : chip::System::Clock::Timeout nextRetryDelay = chip::System::Clock::Seconds16(1);
315 : };
316 : void MarkPending(ScheduledAttempt && attempt);
317 : chip::System::Clock::ClockBase * mClock;
318 : RetryEntry mRetryQueue[kRetryQueueSize];
319 : };
320 :
321 : } // namespace Minimal
322 : } // namespace mdns
|