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