blob: 7131cbfaaf4d88e2c861e2aa4f931020aa2cbfa4 [file] [log] [blame]
/*
*
* Copyright (c) 2022 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.
*/
#include <lib/dnssd/IncrementalResolve.h>
#include <lib/dnssd/IPAddressSorter.h>
#include <lib/dnssd/ServiceNaming.h>
#include <lib/dnssd/TxtFields.h>
#include <lib/dnssd/minimal_mdns/Logging.h>
#include <lib/dnssd/minimal_mdns/core/RecordWriter.h>
#include <lib/support/CHIPMemString.h>
#include <trace/trace.h>
namespace chip {
namespace Dnssd {
using namespace mdns::Minimal;
using namespace mdns::Minimal::Logging;
namespace {
ByteSpan GetSpan(const mdns::Minimal::BytesRange & range)
{
return ByteSpan(range.Start(), range.Size());
}
/// Handles filling record data from TXT records.
///
/// Supported records are whatever `FillNodeDataFromTxt` supports.
template <class DataType>
class TxtParser : public mdns::Minimal::TxtRecordDelegate
{
public:
explicit TxtParser(DataType & data) : mData(data) {}
void OnRecord(const mdns::Minimal::BytesRange & name, const mdns::Minimal::BytesRange & value) override
{
FillNodeDataFromTxt(GetSpan(name), GetSpan(value), mData);
}
private:
DataType & mData;
};
enum class ServiceNameType
{
kInvalid, // not a matter service name
kOperational,
kCommissioner,
kCommissionable,
};
// Common prefix to check for all operational/commissioner/commissionable name parts
constexpr QNamePart kOperationalSuffix[] = { kOperationalServiceName, kOperationalProtocol, kLocalDomain };
constexpr QNamePart kCommissionableSuffix[] = { kCommissionableServiceName, kCommissionProtocol, kLocalDomain };
constexpr QNamePart kCommissionerSuffix[] = { kCommissionerServiceName, kCommissionProtocol, kLocalDomain };
ServiceNameType ComputeServiceNameType(SerializedQNameIterator name)
{
// SRV record names look like:
// <compressed-fabric-id>-<node-id>._matter._tcp.local (operational)
// <instance>._matterc._udp.local (commissionable)
// <instance>._matterd._udp.local (commissioner)
// Starts with compressed-fabric-node or instance, skip over it.
if (!name.Next() || !name.IsValid())
{
// missing required components - empty service name
return ServiceNameType::kInvalid;
}
if (name == kOperationalSuffix)
{
return ServiceNameType::kOperational;
}
if (name == kCommissionableSuffix)
{
return ServiceNameType::kCommissionable;
}
if (name == kCommissionerSuffix)
{
return ServiceNameType::kCommissioner;
}
return ServiceNameType::kInvalid;
}
/// Automatically resets a IncrementalResolver to inactive in its destructor
/// unless disarmed.
///
/// Used for RAII for inactive reset
class AutoInactiveResetter
{
public:
explicit AutoInactiveResetter(IncrementalResolver & resolver) : mResolver(resolver) {}
~AutoInactiveResetter()
{
if (mArmed)
{
mResolver.ResetToInactive();
}
}
void Disarm() { mArmed = false; }
private:
bool mArmed = true;
IncrementalResolver & mResolver;
};
} // namespace
CHIP_ERROR StoredServerName::Set(SerializedQNameIterator value)
{
chip::Encoding::BigEndian::BufferWriter output(mNameBuffer, sizeof(mNameBuffer));
RecordWriter writer(&output);
writer.WriteQName(value);
if (!writer.Fit())
{
Clear();
return CHIP_ERROR_NO_MEMORY;
}
return CHIP_NO_ERROR;
}
SerializedQNameIterator StoredServerName::Get() const
{
return SerializedQNameIterator(BytesRange(mNameBuffer, mNameBuffer + sizeof(mNameBuffer)), mNameBuffer);
}
CHIP_ERROR IncrementalResolver::InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const mdns::Minimal::SrvRecord & srv)
{
AutoInactiveResetter inactiveReset(*this);
ReturnErrorOnFailure(mRecordName.Set(name));
ReturnErrorOnFailure(mTargetHostName.Set(srv.GetName()));
mCommonResolutionData.port = srv.GetPort();
{
// TODO: Chip code historically seems to assume that the host name is of the
// form "<MAC or 802.15.4 Extended Address in hex>.local" and only saves the first part.
//
// This should not be needed as server name should not be relevant once parsed.
// The Resolver keeps track of this name for the purpose of address resolution
// in mTargetHostName.
//
// Keeping track of this in resolution data does not seem useful and should be
// removed.
SerializedQNameIterator serverName = srv.GetName();
VerifyOrReturnError(serverName.Next() && serverName.IsValid(), CHIP_ERROR_INVALID_ARGUMENT);
// only save the first part: the MAC or 802.15.4 Extended Address in hex
Platform::CopyString(mCommonResolutionData.hostName, serverName.Value());
}
switch (ComputeServiceNameType(name))
{
case ServiceNameType::kOperational:
mSpecificResolutionData.Set<OperationalNodeData>();
{
// Operational addresses start with peer node information
SerializedQNameIterator nameCopy = name;
if (!nameCopy.Next() || !nameCopy.IsValid())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
CHIP_ERROR err =
ExtractIdFromInstanceName(nameCopy.Value(), &mSpecificResolutionData.Get<OperationalNodeData>().peerId);
if (err != CHIP_NO_ERROR)
{
return err;
}
}
LogFoundOperationalSrvRecord(mSpecificResolutionData.Get<OperationalNodeData>().peerId, mTargetHostName.Get());
break;
case ServiceNameType::kCommissioner:
case ServiceNameType::kCommissionable:
mSpecificResolutionData.Set<CommissionNodeData>();
{
// Commission addresses start with instance name
SerializedQNameIterator nameCopy = name;
if (!nameCopy.Next() || !nameCopy.IsValid())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
Platform::CopyString(mSpecificResolutionData.Get<CommissionNodeData>().instanceName, nameCopy.Value());
}
LogFoundCommissionSrvRecord(mSpecificResolutionData.Get<CommissionNodeData>().instanceName, mTargetHostName.Get());
break;
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
inactiveReset.Disarm();
return CHIP_NO_ERROR;
}
IncrementalResolver::RequiredInformationFlags IncrementalResolver::GetMissingRequiredInformation() const
{
RequiredInformationFlags flags;
if (!mSpecificResolutionData.Valid())
{
flags.Set(RequiredInformationBitFlags::kSrvInitialization);
}
else
{
if (mCommonResolutionData.numIPs == 0)
{
flags.Set(RequiredInformationBitFlags::kIpAddress);
}
}
return flags;
}
CHIP_ERROR IncrementalResolver::OnRecord(Inet::InterfaceId interface, const ResourceData & data, BytesRange packetRange)
{
MATTER_TRACE_EVENT_SCOPE("Incremental resolver record parsing"); // measure until loop finished
if (!IsActive())
{
return CHIP_NO_ERROR; // nothing to parse
}
switch (data.GetType())
{
case QType::TXT:
if (data.GetName() != mRecordName.Get())
{
MATTER_TRACE_EVENT_INSTANT("TXT not applicable");
return CHIP_NO_ERROR;
}
return OnTxtRecord(data, packetRange);
case QType::A: {
if (data.GetName() != mTargetHostName.Get())
{
MATTER_TRACE_EVENT_INSTANT("A (IPv4) not applicable");
return CHIP_NO_ERROR;
}
#if INET_CONFIG_ENABLE_IPV4
Inet::IPAddress addr;
if (!ParseARecord(data.GetData(), &addr))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
return OnIpAddress(interface, addr);
#else
#if CHIP_MINMDNS_HIGH_VERBOSITY
ChipLogProgress(Discovery, "Ignoring A record: IPv4 not supported");
#endif
// skipping IPv4 addresses
return CHIP_NO_ERROR;
#endif
}
case QType::AAAA: {
if (data.GetName() != mTargetHostName.Get())
{
MATTER_TRACE_EVENT_INSTANT("AAAA (IPv6) not applicable");
return CHIP_NO_ERROR;
}
Inet::IPAddress addr;
if (!ParseAAAARecord(data.GetData(), &addr))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
return OnIpAddress(interface, addr);
}
case QType::SRV: // SRV handled on creation, ignored for 'additional data'
default:
// Other types not interesting during parsing
return CHIP_NO_ERROR;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR IncrementalResolver::OnTxtRecord(const ResourceData & data, BytesRange packetRange)
{
{
TxtParser<CommonResolutionData> delegate(mCommonResolutionData);
if (!ParseTxtRecord(data.GetData(), &delegate))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
if (IsActiveCommissionParse())
{
TxtParser<CommissionNodeData> delegate(mSpecificResolutionData.Get<CommissionNodeData>());
if (!ParseTxtRecord(data.GetData(), &delegate))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR IncrementalResolver::OnIpAddress(Inet::InterfaceId interface, const Inet::IPAddress & addr)
{
if (mCommonResolutionData.numIPs >= ArraySize(mCommonResolutionData.ipAddress))
{
return CHIP_ERROR_NO_MEMORY;
}
if (!mCommonResolutionData.interfaceId.IsPresent())
{
mCommonResolutionData.interfaceId = interface;
}
else if (mCommonResolutionData.interfaceId != interface)
{
// IP addresses received from multiple packets over different interfaces.
// Processing is assumed per single interface.
return CHIP_ERROR_INVALID_ARGUMENT;
}
mCommonResolutionData.ipAddress[mCommonResolutionData.numIPs++] = addr;
LogFoundIPAddress(mTargetHostName.Get(), addr);
return CHIP_NO_ERROR;
}
CHIP_ERROR IncrementalResolver::Take(DiscoveredNodeData & outputData)
{
VerifyOrReturnError(IsActiveCommissionParse(), CHIP_ERROR_INCORRECT_STATE);
IPAddressSorter::Sort(mCommonResolutionData.ipAddress, mCommonResolutionData.numIPs, mCommonResolutionData.interfaceId);
outputData.resolutionData = mCommonResolutionData;
outputData.commissionData = mSpecificResolutionData.Get<CommissionNodeData>();
ResetToInactive();
return CHIP_NO_ERROR;
}
CHIP_ERROR IncrementalResolver::Take(ResolvedNodeData & outputData)
{
VerifyOrReturnError(IsActiveOperationalParse(), CHIP_ERROR_INCORRECT_STATE);
outputData.resolutionData = mCommonResolutionData;
outputData.operationalData = mSpecificResolutionData.Get<OperationalNodeData>();
ResetToInactive();
return CHIP_NO_ERROR;
}
} // namespace Dnssd
} // namespace chip