blob: e39bd32287e6acc550f10216f324fb0c50dab9c3 [file] [log] [blame]
/*
*
* Copyright (c) 2020 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 "RecordResponder.h"
#include "ReplyFilter.h"
#include "Responder.h"
#include <system/SystemClock.h>
namespace mdns {
namespace Minimal {
/// Represents available data (replies) for mDNS queries.
struct QueryResponderRecord
{
Responder * responder = nullptr; // what response/data is available
bool reportService = false; // report as a service when listing dnssd services
chip::System::Clock::Timestamp lastMulticastTime = chip::System::Clock::kZero; // last time this record was multicast
};
namespace Internal {
/// Internal information for query responder records.
struct QueryResponderInfo : public QueryResponderRecord
{
bool reportNowAsAdditional; // report as additional data required
bool alsoReportAdditionalQName = false; // report more data when this record is listed
FullQName additionalQName; // if alsoReportAdditionalQName is set, send this extra data
void Clear()
{
responder = nullptr;
reportService = false;
reportNowAsAdditional = false;
alsoReportAdditionalQName = false;
}
};
} // namespace Internal
/// Allows building query responder configuration
class QueryResponderSettings
{
public:
QueryResponderSettings() : mInfo(nullptr) {}
QueryResponderSettings(Internal::QueryResponderInfo * info) : mInfo(info) {}
QueryResponderSettings(const QueryResponderSettings & other) = default;
/// This record should be part of dns-sd service listing requests
QueryResponderSettings & SetReportInServiceListing(bool reportService)
{
if (IsValid())
{
mInfo->reportService = reportService;
}
return *this;
}
/// When this record is send back, additional records should also be provided.
///
/// This is useful to avoid chattyness by sending back referenced records
/// (e.g. when sending a PTR record, send the corresponding SRV and when sending
/// SRV, send back the corresponding A/AAAA records).
QueryResponderSettings & SetReportAdditional(const FullQName & qname)
{
if (IsValid())
{
mInfo->alsoReportAdditionalQName = true;
mInfo->additionalQName = qname;
}
return *this;
}
bool IsValid() const { return mInfo != nullptr; }
private:
Internal::QueryResponderInfo * mInfo;
};
/// Determines what query records should be included in a response.
///
/// Provides an 'Accept' method to determine if a reply is to be sent or not.
class QueryResponderRecordFilter
{
public:
/// Default contstructor accepts everything that is not null
QueryResponderRecordFilter() {}
QueryResponderRecordFilter(const QueryResponderRecordFilter & other) = default;
QueryResponderRecordFilter & operator=(const QueryResponderRecordFilter & other) = default;
/// Set if to include only items marked as 'additional reply' or everything.
QueryResponderRecordFilter & SetIncludeAdditionalRepliesOnly(bool includeAdditionalRepliesOnly)
{
mIncludeAdditionalRepliesOnly = includeAdditionalRepliesOnly;
return *this;
}
/// Filter out anything rejected by the given reply filter.
/// If replyFilter is nullptr, no such filtering is applied.
QueryResponderRecordFilter & SetReplyFilter(ReplyFilter * replyFilter)
{
mReplyFilter = replyFilter;
return *this;
}
/// Filter out anything that was multicast past ms.
/// If ms is 0, no filtering is done
QueryResponderRecordFilter & SetIncludeOnlyMulticastBeforeMS(chip::System::Clock::Timestamp time)
{
mIncludeOnlyMulticastBefore = time;
return *this;
}
bool Accept(Internal::QueryResponderInfo * record) const
{
if (record->responder == nullptr)
{
return false;
}
if (mIncludeAdditionalRepliesOnly && !record->reportNowAsAdditional)
{
return false;
}
if ((mIncludeOnlyMulticastBefore > chip::System::Clock::kZero) &&
(record->lastMulticastTime >= mIncludeOnlyMulticastBefore))
{
return false;
}
if ((mReplyFilter != nullptr) &&
!mReplyFilter->Accept(record->responder->GetQType(), record->responder->GetQClass(), record->responder->GetQName()))
{
return false;
}
return true;
}
private:
bool mIncludeAdditionalRepliesOnly = false;
ReplyFilter * mReplyFilter = nullptr;
chip::System::Clock::Timestamp mIncludeOnlyMulticastBefore = chip::System::Clock::kZero;
};
/// Iterates over an array of QueryResponderRecord items, providing only 'valid' ones, where
/// valid is based on the provided filter.
class QueryResponderIterator
{
public:
using value_type = QueryResponderRecord;
using pointer = QueryResponderRecord *;
using reference = QueryResponderRecord &;
QueryResponderIterator() : mCurrent(nullptr), mRemaining(0) {}
QueryResponderIterator(QueryResponderRecordFilter * recordFilter, Internal::QueryResponderInfo * pos, size_t size) :
mFilter(recordFilter), mCurrent(pos), mRemaining(size)
{
SkipInvalid();
}
QueryResponderIterator(const QueryResponderIterator & other) = default;
QueryResponderIterator & operator=(const QueryResponderIterator & other) = default;
QueryResponderIterator & operator++()
{
if (mRemaining != 0)
{
mCurrent++;
mRemaining--;
}
SkipInvalid();
return *this;
}
QueryResponderIterator operator++(int)
{
QueryResponderIterator tmp(*this);
operator++();
return tmp;
}
bool operator==(const QueryResponderIterator & rhs) const { return mCurrent == rhs.mCurrent; }
bool operator!=(const QueryResponderIterator & rhs) const { return mCurrent != rhs.mCurrent; }
QueryResponderRecord & operator*() { return *mCurrent; }
QueryResponderRecord * operator->() { return mCurrent; }
Internal::QueryResponderInfo * GetInternal() { return mCurrent; }
const Internal::QueryResponderInfo * GetInternal() const { return mCurrent; }
private:
/// Skips invalid/not useful values.
/// ensures that if mRemaining is 0, mCurrent is nullptr;
void SkipInvalid()
{
while ((mRemaining > 0) && !mFilter->Accept(mCurrent))
{
mRemaining--;
mCurrent++;
}
if (mRemaining == 0)
{
mCurrent = nullptr;
}
}
QueryResponderRecordFilter * mFilter;
Internal::QueryResponderInfo * mCurrent;
size_t mRemaining;
};
/// Responds to mDNS queries.
///
/// In particular:
/// - replies data as provided by the underlying responders
/// - replies to "_services._dns-sd._udp.local."
///
/// Maintains a stateful list of 'additional replies' that can be marked/unmarked
/// for query processing
class QueryResponderBase : public Responder // "_services._dns-sd._udp.local"
{
public:
/// Builds a new responder with the given storage for the response infos
QueryResponderBase(Internal::QueryResponderInfo * infos, size_t infoSizes);
~QueryResponderBase() override {}
/// Setup initial settings (clears all infos and sets up dns-sd query replies)
void Init();
/// Add a new responder to be processed
///
/// Return valid QueryResponderSettings on add success.
QueryResponderSettings AddResponder(RecordResponder * responder);
/// Implementation of the responder delegate.
///
/// Adds responses for all known _dns-sd services.
void AddAllResponses(const chip::Inet::IPPacketInfo * source, ResponderDelegate * delegate,
const ResponseConfiguration & configuration) override;
QueryResponderIterator begin(QueryResponderRecordFilter * filter)
{
return QueryResponderIterator(filter, mResponderInfos, mResponderInfoSize);
}
QueryResponderIterator end() { return QueryResponderIterator(); }
/// Clear any items marked as 'additional'.
void ResetAdditionals();
/// Marks queries matching this qname as 'to be additionally reported'
/// @return the number of items marked new as 'additional data'.
size_t MarkAdditional(const FullQName & qname);
/// Flag any additional responses required for the given iterator
void MarkAdditionalRepliesFor(QueryResponderIterator it);
/// Resets the internal broadcast throttle setting to allow re-broadcasting
/// of all packets without a timedelay.
void ClearBroadcastThrottle();
private:
Internal::QueryResponderInfo * mResponderInfos;
size_t mResponderInfoSize;
};
template <size_t kSize>
class QueryResponder : public QueryResponderBase
{
public:
QueryResponder() : QueryResponderBase(mData, kSize) { Init(); }
private:
Internal::QueryResponderInfo mData[kSize];
};
} // namespace Minimal
} // namespace mdns