LCOV - code coverage report
Current view: top level - lib/dnssd - ActiveResolveAttempts.h (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 26 90 28.9 %
Date: 2024-02-15 08:20:41 Functions: 10 22 45.5 %

          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

Generated by: LCOV version 1.14