/* * * 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 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 class QueryResponder : public QueryResponderBase { public: QueryResponder() : QueryResponderBase(mData, kSize) { Init(); } private: Internal::QueryResponderInfo mData[kSize]; }; } // namespace Minimal } // namespace mdns