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 : 23 : #include <lib/core/Optional.h> 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 116 : ScheduledAttempt() 73 116 : { 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 116 : } 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 128 : bool MatchesIpResolve(SerializedQNameIterator hostName) const 136 : { 137 128 : 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 131 : bool Matches(const chip::Dnssd::DiscoveredNodeData & data, chip::Dnssd::DiscoveryType type) const 144 : { 145 131 : if (!resolveData.Is<Browse>()) 146 : { 147 129 : return false; 148 : } 149 : 150 2 : auto & browse = resolveData.Get<Browse>(); 151 : 152 2 : if (browse.type != type) 153 : { 154 0 : return false; 155 : } 156 : 157 2 : switch (browse.filter.type) 158 : { 159 0 : case chip::Dnssd::DiscoveryFilterType::kNone: 160 0 : return true; 161 0 : case chip::Dnssd::DiscoveryFilterType::kShortDiscriminator: 162 0 : return browse.filter.code == static_cast<uint64_t>((data.commissionData.longDiscriminator >> 8) & 0x0F); 163 2 : case chip::Dnssd::DiscoveryFilterType::kLongDiscriminator: 164 2 : return browse.filter.code == data.commissionData.longDiscriminator; 165 0 : case chip::Dnssd::DiscoveryFilterType::kVendorId: 166 0 : return browse.filter.code == data.commissionData.vendorId; 167 0 : case chip::Dnssd::DiscoveryFilterType::kDeviceType: 168 0 : return browse.filter.code == data.commissionData.deviceType; 169 0 : case chip::Dnssd::DiscoveryFilterType::kCommissioningMode: 170 0 : return browse.filter.code == data.commissionData.commissioningMode; 171 0 : case chip::Dnssd::DiscoveryFilterType::kInstanceName: 172 0 : return strncmp(browse.filter.instanceName, data.commissionData.instanceName, 173 0 : chip::Dnssd::Commission::kInstanceNameMaxLength + 1) == 0; 174 0 : case chip::Dnssd::DiscoveryFilterType::kCommissioner: 175 : case chip::Dnssd::DiscoveryFilterType::kCompressedFabricId: 176 : default: 177 : // These are for other discovery types. 178 0 : return false; 179 : } 180 : } 181 : 182 663 : bool IsEmpty() const { return !resolveData.Valid(); } 183 17 : bool IsResolve() const { return resolveData.Is<Resolve>(); } 184 128 : bool IsBrowse() const { return resolveData.Is<Browse>(); } 185 0 : bool IsIpResolve() const { return resolveData.Is<IpResolve>(); } 186 154 : void Clear() { resolveData = DataType(); } 187 : 188 : // Called when this scheduled attempt will replace an existing scheduled 189 : // attempt, because either they match or we have run out of attempt 190 : // slots. When this happens, we want to propagate the consumer count 191 : // from the existing attempt to the new one, if they're matching resolve 192 : // attempts. 193 17 : void WillCoalesceWith(const ScheduledAttempt & existing) 194 : { 195 17 : if (!IsResolve()) 196 : { 197 : // Consumer count is only tracked for resolve requests 198 4 : return; 199 : } 200 : 201 13 : if (!existing.Matches(*this)) 202 : { 203 : // Out of attempt slots, so just dropping the existing attempt. 204 12 : return; 205 : } 206 : 207 : // Adding another consumer to the same query. Propagate along the 208 : // consumer count to our new attempt, which will replace the 209 : // existing one. 210 1 : resolveData.Get<Resolve>().consumerCount = existing.resolveData.Get<Resolve>().consumerCount + 1; 211 : } 212 : 213 0 : void ConsumerRemoved() 214 : { 215 0 : if (!IsResolve()) 216 : { 217 0 : return; 218 : } 219 : 220 0 : auto & count = resolveData.Get<Resolve>().consumerCount; 221 0 : if (count > 0) 222 : { 223 0 : --count; 224 : } 225 : 226 0 : if (count == 0) 227 : { 228 0 : Clear(); 229 : } 230 : } 231 : 232 0 : const Browse & BrowseData() const { return resolveData.Get<Browse>(); } 233 0 : const Resolve & ResolveData() const { return resolveData.Get<Resolve>(); } 234 0 : const IpResolve & IpResolveData() const { return resolveData.Get<IpResolve>(); } 235 : 236 : using DataType = chip::Variant<Browse, Resolve, IpResolve>; 237 : 238 : DataType resolveData; 239 : 240 : // First packet send is marked separately: minMDNS logic can choose 241 : // to first send a unicast query followed by a multicast one. 242 : bool firstSend = false; 243 : }; 244 : 245 145 : ActiveResolveAttempts(chip::System::Clock::ClockBase * clock) : mClock(clock) { Reset(); } 246 : 247 : /// Clear out the internal queue 248 : void Reset(); 249 : 250 : /// Mark a resolution as a success, removing it from the internal list 251 : void Complete(const chip::PeerId & peerId); 252 : void CompleteCommissioner(const chip::Dnssd::DiscoveredNodeData & data); 253 : void CompleteCommissionable(const chip::Dnssd::DiscoveredNodeData & data); 254 : void CompleteIpResolution(SerializedQNameIterator targetHostName); 255 : 256 : /// Mark all browse-type scheduled attemptes as a success, removing them 257 : /// from the internal list. 258 : CHIP_ERROR CompleteAllBrowses(); 259 : 260 : /// Note that resolve attempts for the given peer id now have one fewer 261 : /// consumer. 262 : void NodeIdResolutionNoLongerNeeded(const chip::PeerId & peerId); 263 : 264 : /// Mark that a resolution is pending, adding it to the internal list 265 : /// 266 : /// Once this complete, this peer id will be returned immediately 267 : /// by NextScheduled (potentially with others as well) 268 : void MarkPending(const chip::PeerId & peerId); 269 : void MarkPending(const chip::Dnssd::DiscoveryFilter & filter, const chip::Dnssd::DiscoveryType type); 270 : void MarkPending(ScheduledAttempt::IpResolve && resolve); 271 : 272 : // Get minimum time until the next pending reply is required. 273 : // 274 : // Returns missing if no actively tracked elements exist. 275 : chip::Optional<chip::System::Clock::Timeout> GetTimeUntilNextExpectedResponse() const; 276 : 277 : // Get the peer Id that needs scheduling for a query 278 : // 279 : // Assumes that the resolution is being sent and will apply internal 280 : // query logic. This means: 281 : // - internal tracking of 'next due time' will updated as 'request sent 282 : // now' 283 : // - there is NO sorting implied by this call. Returned value will be 284 : // any peer that needs a new request sent 285 : chip::Optional<ScheduledAttempt> NextScheduled(); 286 : 287 : /// Check if any of the pending queries are for the given host name for 288 : /// IP resolution. 289 : bool IsWaitingForIpResolutionFor(SerializedQNameIterator hostName) const; 290 : 291 : /// Check if a browse operation is active for the given discovery type 292 : bool HasBrowseFor(chip::Dnssd::DiscoveryType type) const; 293 : 294 : private: 295 : struct RetryEntry 296 : { 297 : ScheduledAttempt attempt; 298 : // When a reply is expected for this item 299 : chip::System::Clock::Timestamp queryDueTime; 300 : 301 : // Next expected delay for sending if reply is not reached by 302 : // 'queryDueTimeMs' 303 : // 304 : // Based on RFC 6762 expectations are: 305 : // - the interval between the first two queries MUST be at least 306 : // one second 307 : // - the intervals between successive queries MUST increase by at 308 : // least a factor of two 309 : chip::System::Clock::Timeout nextRetryDelay = chip::System::Clock::Seconds16(1); 310 : }; 311 : void MarkPending(ScheduledAttempt && attempt); 312 : chip::System::Clock::ClockBase * mClock; 313 : RetryEntry mRetryQueue[kRetryQueueSize]; 314 : }; 315 : 316 : } // namespace Minimal 317 : } // namespace mdns