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 : #include "ActiveResolveAttempts.h"
19 :
20 : #include <lib/support/logging/CHIPLogging.h>
21 :
22 : using namespace chip;
23 :
24 : namespace mdns {
25 : namespace Minimal {
26 :
27 : constexpr chip::System::Clock::Timeout ActiveResolveAttempts::kMaxRetryDelay;
28 :
29 70 : void ActiveResolveAttempts::Reset()
30 :
31 : {
32 350 : for (auto & item : mRetryQueue)
33 : {
34 280 : item.attempt.Clear();
35 : }
36 70 : }
37 :
38 4 : void ActiveResolveAttempts::Complete(const PeerId & peerId)
39 : {
40 6 : for (auto & item : mRetryQueue)
41 : {
42 6 : if (item.attempt.Matches(peerId))
43 : {
44 4 : item.attempt.Clear();
45 4 : return;
46 : }
47 : }
48 :
49 : #if CHIP_MINMDNS_HIGH_VERBOSITY
50 : // This may happen during boot time adverisements: nodes come online
51 : // and advertise their IP without any explicit queries for them
52 : ChipLogProgress(Discovery, "Discovered node without a pending query");
53 : #endif
54 : }
55 :
56 12 : bool ActiveResolveAttempts::HasBrowseFor(chip::Dnssd::DiscoveryType type) const
57 : {
58 60 : for (auto & item : mRetryQueue)
59 : {
60 48 : if (!item.attempt.IsBrowse())
61 : {
62 48 : continue;
63 : }
64 :
65 0 : if (item.attempt.BrowseData().type == type)
66 : {
67 0 : return true;
68 : }
69 : }
70 :
71 12 : return false;
72 : }
73 :
74 12 : void ActiveResolveAttempts::CompleteIpResolution(SerializedQNameIterator targetHostName)
75 : {
76 60 : for (auto & item : mRetryQueue)
77 : {
78 48 : if (item.attempt.MatchesIpResolve(targetHostName))
79 : {
80 0 : item.attempt.Clear();
81 0 : return;
82 : }
83 : }
84 : }
85 :
86 2 : CHIP_ERROR ActiveResolveAttempts::CompleteAllBrowses()
87 : {
88 10 : for (auto & item : mRetryQueue)
89 : {
90 8 : if (item.attempt.IsBrowse())
91 : {
92 2 : item.attempt.Clear();
93 : }
94 : }
95 :
96 2 : return CHIP_NO_ERROR;
97 : }
98 :
99 0 : void ActiveResolveAttempts::NodeIdResolutionNoLongerNeeded(const PeerId & peerId)
100 : {
101 0 : for (auto & item : mRetryQueue)
102 : {
103 0 : if (item.attempt.Matches(peerId))
104 : {
105 0 : item.attempt.ConsumerRemoved();
106 0 : return;
107 : }
108 : }
109 : }
110 :
111 13 : void ActiveResolveAttempts::MarkPending(const chip::PeerId & peerId)
112 : {
113 13 : MarkPending(ScheduledAttempt(peerId, /* firstSend */ true));
114 13 : }
115 :
116 4 : void ActiveResolveAttempts::MarkPending(const chip::Dnssd::DiscoveryFilter & filter, const chip::Dnssd::DiscoveryType type)
117 : {
118 4 : MarkPending(ScheduledAttempt(filter, type, /* firstSend */ true));
119 4 : }
120 :
121 0 : void ActiveResolveAttempts::MarkPending(ScheduledAttempt::IpResolve && resolve)
122 : {
123 0 : MarkPending(ScheduledAttempt(std::move(resolve), /* firstSend */ true));
124 0 : }
125 :
126 17 : void ActiveResolveAttempts::MarkPending(ScheduledAttempt && attempt)
127 : {
128 : // Strategy when picking the peer id to use:
129 : // 1 if a matching peer id is already found, use that one
130 : // 2 if an 'unused' entry is found, use that
131 : // 3 otherwise expire the one with the largest nextRetryDelay
132 : // or if equal nextRetryDelay, pick the one with the oldest
133 : // queryDueTime
134 :
135 17 : RetryEntry * entryToUse = &mRetryQueue[0];
136 :
137 62 : for (size_t i = 1; i < kRetryQueueSize; i++)
138 : {
139 47 : if (entryToUse->attempt.Matches(attempt))
140 : {
141 2 : break; // best match possible
142 : }
143 :
144 45 : RetryEntry * entry = mRetryQueue + i;
145 :
146 : // Rule 1: attempt match always matches
147 45 : if (entry->attempt.Matches(attempt))
148 : {
149 0 : entryToUse = entry;
150 0 : continue;
151 : }
152 :
153 : // Rule 2: select unused entries
154 45 : if (!entryToUse->attempt.IsEmpty() && entry->attempt.IsEmpty())
155 : {
156 6 : entryToUse = entry;
157 6 : continue;
158 : }
159 39 : if (entryToUse->attempt.IsEmpty())
160 : {
161 32 : continue;
162 : }
163 :
164 : // Rule 3: both choices are used (have a defined node id):
165 : // - try to find the one with the largest next delay (oldest request)
166 : // - on same delay, use queryDueTime to determine the oldest request
167 : // (the one with the smallest due time was issued the longest time
168 : // ago)
169 7 : if (entry->nextRetryDelay > entryToUse->nextRetryDelay)
170 : {
171 0 : entryToUse = entry;
172 : }
173 7 : else if ((entry->nextRetryDelay == entryToUse->nextRetryDelay) && (entry->queryDueTime < entryToUse->queryDueTime))
174 : {
175 0 : entryToUse = entry;
176 : }
177 : }
178 :
179 17 : if ((!entryToUse->attempt.IsEmpty()) && (!entryToUse->attempt.Matches(attempt)))
180 : {
181 : // TODO: node was evicted here, if/when resolution failures are
182 : // supported this could be a place for error callbacks
183 : //
184 : // Note however that this is NOT an actual 'timeout' it is showing
185 : // a burst of lookups for which we cannot maintain state. A reply may
186 : // still be received for this peer id (query was already sent on the
187 : // network)
188 1 : ChipLogError(Discovery, "Re-using pending resolve entry before reply was received.");
189 : }
190 :
191 17 : attempt.WillCoalesceWith(entryToUse->attempt);
192 17 : entryToUse->attempt = attempt;
193 17 : entryToUse->queryDueTime = mClock->GetMonotonicTimestamp();
194 17 : entryToUse->nextRetryDelay = System::Clock::Seconds16(1);
195 17 : }
196 :
197 64 : std::optional<System::Clock::Timeout> ActiveResolveAttempts::GetTimeUntilNextExpectedResponse() const
198 : {
199 64 : std::optional<System::Clock::Timeout> minDelay = std::nullopt;
200 :
201 64 : chip::System::Clock::Timestamp now = mClock->GetMonotonicTimestamp();
202 :
203 274 : for (auto & entry : mRetryQueue)
204 : {
205 222 : if (entry.attempt.IsEmpty())
206 : {
207 139 : continue;
208 : }
209 :
210 83 : if (now >= entry.queryDueTime)
211 : {
212 : // found an entry that needs processing right now
213 12 : return std::make_optional<System::Clock::Timeout>(0);
214 : }
215 :
216 71 : System::Clock::Timeout entryDelay = entry.queryDueTime - now;
217 71 : if (!minDelay.has_value() || (*minDelay > entryDelay))
218 : {
219 42 : minDelay.emplace(entryDelay);
220 : }
221 : }
222 :
223 52 : return minDelay;
224 : }
225 :
226 85 : std::optional<ActiveResolveAttempts::ScheduledAttempt> ActiveResolveAttempts::NextScheduled()
227 : {
228 85 : chip::System::Clock::Timestamp now = mClock->GetMonotonicTimestamp();
229 :
230 289 : for (auto & entry : mRetryQueue)
231 : {
232 247 : if (entry.attempt.IsEmpty())
233 : {
234 204 : continue; // not a pending item
235 : }
236 :
237 159 : if (entry.queryDueTime > now)
238 : {
239 112 : continue; // not yet due
240 : }
241 :
242 47 : if (entry.nextRetryDelay > kMaxRetryDelay)
243 : {
244 4 : ChipLogError(Discovery, "Timeout waiting for mDNS resolution.");
245 4 : entry.attempt.Clear();
246 4 : continue;
247 : }
248 :
249 43 : entry.queryDueTime = now + entry.nextRetryDelay;
250 43 : entry.nextRetryDelay *= 2;
251 :
252 43 : std::optional<ScheduledAttempt> attempt = std::make_optional(entry.attempt);
253 43 : entry.attempt.firstSend = false;
254 :
255 43 : return attempt;
256 43 : }
257 :
258 42 : return std::nullopt;
259 : }
260 :
261 0 : bool ActiveResolveAttempts::ShouldResolveIpAddress(PeerId peerId) const
262 : {
263 0 : for (auto & item : mRetryQueue)
264 : {
265 0 : if (item.attempt.IsEmpty())
266 : {
267 0 : continue;
268 : }
269 0 : if (item.attempt.IsBrowse())
270 : {
271 0 : return true;
272 : }
273 :
274 0 : if (item.attempt.IsResolve())
275 : {
276 0 : auto & data = item.attempt.ResolveData();
277 0 : if (data.peerId == peerId)
278 : {
279 0 : return true;
280 : }
281 : }
282 : }
283 :
284 0 : return false;
285 : }
286 :
287 0 : bool ActiveResolveAttempts::IsWaitingForIpResolutionFor(SerializedQNameIterator hostName) const
288 : {
289 0 : for (auto & entry : mRetryQueue)
290 : {
291 0 : if (entry.attempt.IsEmpty())
292 : {
293 0 : continue; // not a pending item
294 : }
295 :
296 0 : if (!entry.attempt.IsIpResolve())
297 : {
298 0 : continue;
299 : }
300 :
301 0 : if (hostName == entry.attempt.IpResolveData().hostName.Content())
302 : {
303 0 : return true;
304 : }
305 : }
306 :
307 0 : return false;
308 : }
309 :
310 : } // namespace Minimal
311 : } // namespace mdns
|