blob: 27a32a3e5f909daf5e7375e40aeddb078c6949f2 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <lib/core/Optional.h>
#include <lib/core/PeerId.h>
#include <lib/dnssd/Resolver.h>
#include <system/SystemClock.h>
namespace mdns {
namespace Minimal {
/// Keeps track of active resolve attempts
///
/// Maintains a list of 'pending mdns resolve queries' and provides operations
/// for:
/// - add/remove to the list
/// - figuring out a 'next query time' for items in the list
/// - iterating through the 'schedule now' items of the list
///
class ActiveResolveAttempts
{
public:
static constexpr size_t kRetryQueueSize = 4;
static constexpr chip::System::Clock::Timeout kMaxRetryDelay = chip::System::Clock::Seconds16(16);
struct ScheduledAttempt
{
enum AttemptType
{
kInvalid,
kResolve,
kBrowse,
};
ScheduledAttempt() : attemptType(kInvalid) {}
ScheduledAttempt(const chip::PeerId & peer, bool first) : attemptType(kResolve), peerId(peer), firstSend(first) {}
ScheduledAttempt(const chip::Dnssd::DiscoveryFilter discoveryFilter, const chip::Dnssd::DiscoveryType type, bool first) :
attemptType(kBrowse), browse(discoveryFilter, type), firstSend(first)
{}
bool operator==(const ScheduledAttempt & other) const { return Matches(other) && other.firstSend == firstSend; }
bool Matches(const ScheduledAttempt & other) const
{
if (other.attemptType != attemptType)
{
return false;
}
switch (attemptType)
{
case kInvalid:
return true;
case kBrowse:
return (other.browse.filter == browse.filter && other.browse.type == browse.type);
case kResolve:
return other.peerId == peerId;
default:
return false;
}
}
bool Matches(const chip::PeerId & peer) const { return (attemptType == kResolve) && (peerId == peer); }
bool Matches(const chip::Dnssd::DiscoveredNodeData & data) const
{
if (attemptType != kBrowse)
{
return false;
}
// TODO: we should mark returned node data based on the query
if (browse.type != chip::Dnssd::DiscoveryType::kCommissionableNode)
{
// We don't currently have markers in the returned DiscoveredNodeData to differentiate these, so assume all returned
// packets match
return true;
}
switch (browse.filter.type)
{
case chip::Dnssd::DiscoveryFilterType::kNone:
return true;
case chip::Dnssd::DiscoveryFilterType::kShortDiscriminator:
return browse.filter.code == static_cast<uint64_t>((data.longDiscriminator >> 8) & 0x0F);
case chip::Dnssd::DiscoveryFilterType::kLongDiscriminator:
return browse.filter.code == data.longDiscriminator;
case chip::Dnssd::DiscoveryFilterType::kVendorId:
return browse.filter.code == data.vendorId;
case chip::Dnssd::DiscoveryFilterType::kDeviceType:
return browse.filter.code == data.deviceType;
case chip::Dnssd::DiscoveryFilterType::kCommissioningMode:
return browse.filter.code == data.commissioningMode;
case chip::Dnssd::DiscoveryFilterType::kInstanceName:
return strncmp(browse.filter.instanceName, data.instanceName,
chip::Dnssd::Commission::kInstanceNameMaxLength + 1) == 0;
case chip::Dnssd::DiscoveryFilterType::kCommissioner:
case chip::Dnssd::DiscoveryFilterType::kCompressedFabricId:
default:
// These are for other discovery types.
return false;
}
}
bool IsEmpty() const { return attemptType == kInvalid; }
bool IsResolve() const { return attemptType == kResolve; }
bool IsBrowse() const { return attemptType == kBrowse; }
void Clear() { attemptType = kInvalid; }
// Not using Variant because it assumes a heap impl
AttemptType attemptType;
struct Browse
{
Browse(const chip::Dnssd::DiscoveryFilter discoveryFilter, const chip::Dnssd::DiscoveryType discoveryType) :
filter(discoveryFilter), type(discoveryType)
{}
chip::Dnssd::DiscoveryFilter filter;
chip::Dnssd::DiscoveryType type;
};
union
{
chip::PeerId peerId; // Peer id for resolve attempts
Browse browse;
};
// First packet send is marked separately: minMDNS logic can choose
// to first send a unicast query followed by a multicast one.
bool firstSend = false;
};
ActiveResolveAttempts(chip::System::Clock::ClockBase * clock) : mClock(clock) { Reset(); }
/// Clear out the internal queue
void Reset();
/// Mark a resolution as a success, removing it from the internal list
void Complete(const chip::PeerId & peerId);
void Complete(const chip::Dnssd::DiscoveredNodeData & data);
/// Mark that a resolution is pending, adding it to the internal list
///
/// Once this complete, this peer id will be returned immediately
/// by NextScheduled (potentially with others as well)
void MarkPending(const chip::PeerId & peerId);
void MarkPending(const chip::Dnssd::DiscoveryFilter & filter, const chip::Dnssd::DiscoveryType type);
// Get minimum time until the next pending reply is required.
//
// Returns missing if no actively tracked elements exist.
chip::Optional<chip::System::Clock::Timeout> GetTimeUntilNextExpectedResponse() const;
// Get the peer Id that needs scheduling for a query
//
// Assumes that the resolution is being sent and will apply internal
// query logic. This means:
// - internal tracking of 'next due time' will updated as 'request sent
// now'
// - there is NO sorting implied by this call. Returned value will be
// any peer that needs a new request sent
chip::Optional<ScheduledAttempt> NextScheduled();
private:
struct RetryEntry
{
ScheduledAttempt attempt;
// When a reply is expected for this item
chip::System::Clock::Timestamp queryDueTime;
// Next expected delay for sending if reply is not reached by
// 'queryDueTimeMs'
//
// Based on RFC 6762 expectations are:
// - the interval between the first two queries MUST be at least
// one second
// - the intervals between successive queries MUST increase by at
// least a factor of two
chip::System::Clock::Timeout nextRetryDelay = chip::System::Clock::Seconds16(1);
};
void MarkPending(const ScheduledAttempt & attempt);
chip::System::Clock::ClockBase * mClock;
RetryEntry mRetryQueue[kRetryQueueSize];
};
} // namespace Minimal
} // namespace mdns