blob: d3de7044bbda8b6736379e1090cf5dd07dcf62a3 [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.
*/
#include <app/server/Dnssd.h>
#include <app-common/zap-generated/cluster-enums.h>
#include <inttypes.h>
#include <lib/core/Optional.h>
#include <lib/dnssd/Advertiser.h>
#include <lib/dnssd/ServiceNaming.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/Span.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ReliableMessageProtocolConfig.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/CommissionableDataProvider.h>
#include <platform/ConfigurationManager.h>
#include <platform/DeviceInstanceInfoProvider.h>
#include <protocols/secure_channel/PASESession.h>
#if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID)
#include <setup_payload/AdditionalDataPayloadGenerator.h>
#endif
#include <credentials/FabricTable.h>
#include <setup_payload/SetupPayload.h>
#include <system/TimeSource.h>
#include <algorithm>
using namespace chip;
using namespace chip::DeviceLayer;
namespace chip {
namespace app {
namespace {
void OnPlatformEvent(const DeviceLayer::ChipDeviceEvent * event)
{
switch (event->Type)
{
case DeviceLayer::DeviceEventType::kDnssdInitialized:
case DeviceLayer::DeviceEventType::kDnssdRestartNeeded:
app::DnssdServer::Instance().StartServer();
break;
default:
break;
}
}
void OnPlatformEventWrapper(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
(void) arg;
OnPlatformEvent(event);
}
} // namespace
constexpr System::Clock::Timestamp DnssdServer::kTimeoutCleared;
bool DnssdServer::HaveOperationalCredentials()
{
VerifyOrDie(mFabricTable != nullptr);
// Look for any fabric info that has a useful operational identity.
return mFabricTable->FabricCount() != 0;
}
#if CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
void DnssdServer::SetExtendedDiscoveryTimeoutSecs(int32_t secs)
{
ChipLogDetail(Discovery, "Setting extended discovery timeout to %" PRId32 "s", secs);
mExtendedDiscoveryTimeoutSecs = MakeOptional(secs);
if (mExtendedDiscoveryExpiration != kTimeoutCleared &&
mExtendedDiscoveryExpiration > mTimeSource.GetMonotonicTimestamp() + System::Clock::Seconds32(secs))
{
// Reset our timer to the new (shorter) timeout.
ScheduleExtendedDiscoveryExpiration();
}
}
int32_t DnssdServer::GetExtendedDiscoveryTimeoutSecs()
{
return mExtendedDiscoveryTimeoutSecs.ValueOr(CHIP_DEVICE_CONFIG_EXTENDED_DISCOVERY_TIMEOUT_SECS);
}
/// Callback from Extended Discovery Expiration timer
void HandleExtendedDiscoveryExpiration(System::Layer * aSystemLayer, void * aAppState)
{
DnssdServer::Instance().OnExtendedDiscoveryExpiration(aSystemLayer, aAppState);
}
void DnssdServer::OnExtendedDiscoveryExpiration(System::Layer * aSystemLayer, void * aAppState)
{
ChipLogDetail(Discovery, "Extended discovery timed out");
mExtendedDiscoveryExpiration = kTimeoutCleared;
// Reset our advertising, now that we have flagged ourselves as possibly not
// needing extended discovery anymore.
StartServer();
}
CHIP_ERROR DnssdServer::ScheduleExtendedDiscoveryExpiration()
{
int32_t extendedDiscoveryTimeoutSecs = GetExtendedDiscoveryTimeoutSecs();
if (extendedDiscoveryTimeoutSecs == CHIP_DEVICE_CONFIG_DISCOVERY_NO_TIMEOUT)
{
return CHIP_NO_ERROR;
}
ChipLogDetail(Discovery, "Scheduling extended discovery timeout in %" PRId32 "s", extendedDiscoveryTimeoutSecs);
mExtendedDiscoveryExpiration = mTimeSource.GetMonotonicTimestamp() + System::Clock::Seconds32(extendedDiscoveryTimeoutSecs);
return DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(extendedDiscoveryTimeoutSecs),
HandleExtendedDiscoveryExpiration, nullptr);
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
CHIP_ERROR DnssdServer::GetCommissionableInstanceName(char * buffer, size_t bufferLen)
{
auto & mdnsAdvertiser = chip::Dnssd::ServiceAdvertiser::Instance();
return mdnsAdvertiser.GetCommissionableInstanceName(buffer, bufferLen);
}
CHIP_ERROR DnssdServer::SetEphemeralDiscriminator(Optional<uint16_t> discriminator)
{
VerifyOrReturnError(discriminator.ValueOr(0) <= kMaxDiscriminatorValue, CHIP_ERROR_INVALID_ARGUMENT);
mEphemeralDiscriminator = discriminator;
return CHIP_NO_ERROR;
}
#if CHIP_CONFIG_ENABLE_ICD_SERVER
template <class AdvertisingParams>
void DnssdServer::AddICDKeyToAdvertisement(AdvertisingParams & advParams)
{
VerifyOrDieWithMsg(mICDManager != nullptr, Discovery,
"Invalid pointer to the ICDManager which is required for the LIT operating mode");
Dnssd::ICDModeAdvertise ICDModeToAdvertise = Dnssd::ICDModeAdvertise::kNone;
// Only advertise the ICD key if the device can operate as a LIT
if (mICDManager->SupportsFeature(Clusters::IcdManagement::Feature::kLongIdleTimeSupport))
{
if (mICDManager->GetICDMode() == ICDConfigurationData::ICDMode::LIT)
{
ICDModeToAdvertise = Dnssd::ICDModeAdvertise::kLIT;
}
else
{
ICDModeToAdvertise = Dnssd::ICDModeAdvertise::kSIT;
}
}
advParams.SetICDModeToAdvertise(ICDModeToAdvertise);
}
#endif
void DnssdServer::GetPrimaryOrFallbackMACAddress(chip::MutableByteSpan mac)
{
if (ConfigurationMgr().GetPrimaryMACAddress(mac) != CHIP_NO_ERROR)
{
// Only generate a fallback "MAC" once, so we don't keep constantly changing our host name.
if (std::all_of(std::begin(mFallbackMAC), std::end(mFallbackMAC), [](uint8_t v) { return v == 0; }))
{
ChipLogError(Discovery, "Failed to get primary mac address of device. Generating a random one.");
Crypto::DRBG_get_bytes(mFallbackMAC, sizeof(mFallbackMAC));
}
VerifyOrDie(mac.size() == sizeof(mFallbackMAC)); // kPrimaryMACAddressLength
memcpy(mac.data(), mFallbackMAC, sizeof(mFallbackMAC));
}
}
/// Set MDNS operational advertisement
CHIP_ERROR DnssdServer::AdvertiseOperational()
{
VerifyOrReturnError(mFabricTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
for (const FabricInfo & fabricInfo : *mFabricTable)
{
if (!fabricInfo.ShouldAdvertiseIdentity())
{
continue;
}
uint8_t macBuffer[DeviceLayer::ConfigurationManager::kPrimaryMACAddressLength];
MutableByteSpan mac(macBuffer);
GetPrimaryOrFallbackMACAddress(mac);
auto advertiseParameters = chip::Dnssd::OperationalAdvertisingParameters()
.SetPeerId(fabricInfo.GetPeerId())
.SetMac(mac)
.SetPort(GetSecuredPort())
.SetInterfaceId(GetInterfaceId())
.SetLocalMRPConfig(GetLocalMRPConfig().std_optional())
.EnableIpV4(true);
#if CHIP_CONFIG_ENABLE_ICD_SERVER
AddICDKeyToAdvertisement(advertiseParameters);
#endif
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
advertiseParameters.SetTCPSupportModes(chip::Dnssd::TCPModeAdvertise::kTCPClientServer);
#endif
auto & mdnsAdvertiser = chip::Dnssd::ServiceAdvertiser::Instance();
ChipLogProgress(Discovery, "Advertise operational node " ChipLogFormatX64 "-" ChipLogFormatX64,
ChipLogValueX64(advertiseParameters.GetPeerId().GetCompressedFabricId()),
ChipLogValueX64(advertiseParameters.GetPeerId().GetNodeId()));
// Should we keep trying to advertise the other operational
// identities on failure?
ReturnErrorOnFailure(mdnsAdvertiser.Advertise(advertiseParameters));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DnssdServer::Advertise(bool commissionableNode, chip::Dnssd::CommissioningMode mode)
{
auto advertiseParameters = chip::Dnssd::CommissionAdvertisingParameters()
.SetPort(commissionableNode ? GetSecuredPort() : GetUnsecuredPort())
.SetInterfaceId(GetInterfaceId())
.EnableIpV4(true);
advertiseParameters.SetCommissionAdvertiseMode(commissionableNode ? chip::Dnssd::CommssionAdvertiseMode::kCommissionableNode
: chip::Dnssd::CommssionAdvertiseMode::kCommissioner);
advertiseParameters.SetCommissioningMode(mode);
char pairingInst[chip::Dnssd::kKeyPairingInstructionMaxLength + 1];
uint8_t macBuffer[DeviceLayer::ConfigurationManager::kPrimaryMACAddressLength];
MutableByteSpan mac(macBuffer);
GetPrimaryOrFallbackMACAddress(mac);
advertiseParameters.SetMac(mac);
uint16_t value;
uint32_t val32;
if (DeviceLayer::GetDeviceInstanceInfoProvider()->GetVendorId(value) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "Vendor ID not known");
}
else
{
advertiseParameters.SetVendorId(std::make_optional<uint16_t>(value));
}
if (DeviceLayer::GetDeviceInstanceInfoProvider()->GetProductId(value) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "Product ID not known");
}
else
{
advertiseParameters.SetProductId(std::make_optional<uint16_t>(value));
}
if (DeviceLayer::ConfigurationMgr().IsCommissionableDeviceTypeEnabled() &&
DeviceLayer::ConfigurationMgr().GetDeviceTypeId(val32) == CHIP_NO_ERROR)
{
advertiseParameters.SetDeviceType(std::make_optional<uint32_t>(val32));
}
char deviceName[chip::Dnssd::kKeyDeviceNameMaxLength + 1];
if (DeviceLayer::ConfigurationMgr().IsCommissionableDeviceNameEnabled() &&
DeviceLayer::ConfigurationMgr().GetCommissionableDeviceName(deviceName, sizeof(deviceName)) == CHIP_NO_ERROR)
{
advertiseParameters.SetDeviceName(std::make_optional<const char *>(deviceName));
}
advertiseParameters.SetLocalMRPConfig(GetLocalMRPConfig().std_optional());
#if CHIP_CONFIG_ENABLE_ICD_SERVER
AddICDKeyToAdvertisement(advertiseParameters);
#endif
if (commissionableNode)
{
uint16_t discriminator = 0;
CHIP_ERROR error = DeviceLayer::GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator);
if (error != CHIP_NO_ERROR)
{
ChipLogError(Discovery,
"Setup discriminator read error (%" CHIP_ERROR_FORMAT ")! Critical error, will not be commissionable.",
error.Format());
return error;
}
// Override discriminator with temporary one if one is set
discriminator = mEphemeralDiscriminator.ValueOr(discriminator);
advertiseParameters.SetShortDiscriminator(static_cast<uint8_t>((discriminator >> 8) & 0x0F))
.SetLongDiscriminator(discriminator);
#if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID)
char rotatingDeviceIdHexBuffer[RotatingDeviceId::kHexMaxLength];
ReturnErrorOnFailure(GenerateRotatingDeviceId(rotatingDeviceIdHexBuffer, ArraySize(rotatingDeviceIdHexBuffer)));
advertiseParameters.SetRotatingDeviceId(std::make_optional<const char *>(rotatingDeviceIdHexBuffer));
#endif
if (!HaveOperationalCredentials())
{
if (DeviceLayer::ConfigurationMgr().GetInitialPairingHint(value) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "DNS-SD Pairing Hint not set");
}
else
{
advertiseParameters.SetPairingHint(std::make_optional<uint16_t>(value));
}
if (DeviceLayer::ConfigurationMgr().GetInitialPairingInstruction(pairingInst, sizeof(pairingInst)) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "DNS-SD Pairing Instruction not set");
}
else
{
advertiseParameters.SetPairingInstruction(std::make_optional<const char *>(pairingInst));
}
}
else
{
if (DeviceLayer::ConfigurationMgr().GetSecondaryPairingHint(value) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "DNS-SD Pairing Hint not set");
}
else
{
advertiseParameters.SetPairingHint(std::make_optional<uint16_t>(value));
}
if (DeviceLayer::ConfigurationMgr().GetSecondaryPairingInstruction(pairingInst, sizeof(pairingInst)) != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "DNS-SD Pairing Instruction not set");
}
else
{
advertiseParameters.SetPairingInstruction(std::make_optional<const char *>(pairingInst));
}
}
}
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_PASSCODE
else
{
advertiseParameters.SetCommissionerPasscodeSupported(std::make_optional<bool>(true));
}
#endif
auto & mdnsAdvertiser = chip::Dnssd::ServiceAdvertiser::Instance();
ChipLogProgress(Discovery, "Advertise commission parameter vendorID=%u productID=%u discriminator=%04u/%02u cm=%u cp=%u",
advertiseParameters.GetVendorId().value_or(0), advertiseParameters.GetProductId().value_or(0),
advertiseParameters.GetLongDiscriminator(), advertiseParameters.GetShortDiscriminator(),
to_underlying(advertiseParameters.GetCommissioningMode()),
advertiseParameters.GetCommissionerPasscodeSupported().value_or(false) ? 1 : 0);
return mdnsAdvertiser.Advertise(advertiseParameters);
}
CHIP_ERROR DnssdServer::AdvertiseCommissioner()
{
return Advertise(false /* commissionableNode */, chip::Dnssd::CommissioningMode::kDisabled);
}
CHIP_ERROR DnssdServer::AdvertiseCommissionableNode(chip::Dnssd::CommissioningMode mode)
{
#if CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
mCurrentCommissioningMode = mode;
if (mode != Dnssd::CommissioningMode::kDisabled)
{
// We're not doing extended discovery right now.
DeviceLayer::SystemLayer().CancelTimer(HandleExtendedDiscoveryExpiration, nullptr);
mExtendedDiscoveryExpiration = kTimeoutCleared;
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
return Advertise(true /* commissionableNode */, mode);
}
void DnssdServer::StartServer()
{
Dnssd::CommissioningMode mode = Dnssd::CommissioningMode::kDisabled;
if (mCommissioningModeProvider)
{
mode = mCommissioningModeProvider->GetCommissioningMode();
}
return StartServer(mode);
}
void DnssdServer::StopServer()
{
// Make sure we don't hold on to a dangling fabric table pointer.
mFabricTable = nullptr;
DeviceLayer::PlatformMgr().RemoveEventHandler(OnPlatformEventWrapper, 0);
#if CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
if (mExtendedDiscoveryExpiration != kTimeoutCleared)
{
DeviceLayer::SystemLayer().CancelTimer(HandleExtendedDiscoveryExpiration, nullptr);
mExtendedDiscoveryExpiration = kTimeoutCleared;
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
if (Dnssd::ServiceAdvertiser::Instance().IsInitialized())
{
auto err = Dnssd::ServiceAdvertiser::Instance().RemoveServices();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to remove advertised services: %" CHIP_ERROR_FORMAT, err.Format());
}
Dnssd::ServiceAdvertiser::Instance().Shutdown();
}
}
void DnssdServer::StartServer(Dnssd::CommissioningMode mode)
{
ChipLogProgress(Discovery, "Updating services using commissioning mode %d", static_cast<int>(mode));
DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformEventWrapper, 0);
CHIP_ERROR err = Dnssd::ServiceAdvertiser::Instance().Init(chip::DeviceLayer::UDPEndPointManager());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to initialize advertiser: %" CHIP_ERROR_FORMAT, err.Format());
}
err = Dnssd::ServiceAdvertiser::Instance().RemoveServices();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to remove advertised services: %" CHIP_ERROR_FORMAT, err.Format());
}
err = AdvertiseOperational();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise operational node: %" CHIP_ERROR_FORMAT, err.Format());
}
if (mode != Dnssd::CommissioningMode::kDisabled)
{
err = AdvertiseCommissionableNode(mode);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise commissionable node: %" CHIP_ERROR_FORMAT, err.Format());
}
}
#if CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
else if (GetExtendedDiscoveryTimeoutSecs() != CHIP_DEVICE_CONFIG_DISCOVERY_DISABLED)
{
bool alwaysAdvertiseExtended = (GetExtendedDiscoveryTimeoutSecs() == CHIP_DEVICE_CONFIG_DISCOVERY_NO_TIMEOUT);
// We do extended discovery advertising in three cases:
// 1) We don't have a timeout for extended discovery.
// 2) We are transitioning out of commissioning mode (basic or enhanced)
// and should therefore start extended discovery.
// 3) We are resetting advertising while we are in the middle of an
// existing extended discovery advertising period.
if (alwaysAdvertiseExtended || mCurrentCommissioningMode != Dnssd::CommissioningMode::kDisabled ||
mExtendedDiscoveryExpiration != kTimeoutCleared)
{
err = AdvertiseCommissionableNode(mode);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise extended commissionable node: %" CHIP_ERROR_FORMAT, err.Format());
}
if (mExtendedDiscoveryExpiration == kTimeoutCleared)
{
// set timeout
ScheduleExtendedDiscoveryExpiration();
}
}
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
err = AdvertiseCommissioner();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise commissioner: %" CHIP_ERROR_FORMAT, err.Format());
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
err = Dnssd::ServiceAdvertiser::Instance().FinalizeServiceUpdate();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to finalize service update: %" CHIP_ERROR_FORMAT, err.Format());
}
}
#if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID)
CHIP_ERROR DnssdServer::GenerateRotatingDeviceId(char rotatingDeviceIdHexBuffer[], size_t rotatingDeviceIdHexBufferSize)
{
AdditionalDataPayloadGeneratorParams additionalDataPayloadParams;
uint8_t rotatingDeviceIdUniqueId[chip::DeviceLayer::ConfigurationManager::kRotatingDeviceIDUniqueIDLength];
MutableByteSpan rotatingDeviceIdUniqueIdSpan(rotatingDeviceIdUniqueId);
size_t rotatingDeviceIdValueOutputSize = 0;
ReturnErrorOnFailure(
chip::DeviceLayer::GetDeviceInstanceInfoProvider()->GetRotatingDeviceIdUniqueId(rotatingDeviceIdUniqueIdSpan));
ReturnErrorOnFailure(
chip::DeviceLayer::ConfigurationMgr().GetLifetimeCounter(additionalDataPayloadParams.rotatingDeviceIdLifetimeCounter));
additionalDataPayloadParams.rotatingDeviceIdUniqueId = rotatingDeviceIdUniqueIdSpan;
return AdditionalDataPayloadGenerator().generateRotatingDeviceIdAsHexString(
additionalDataPayloadParams, rotatingDeviceIdHexBuffer, rotatingDeviceIdHexBufferSize, rotatingDeviceIdValueOutputSize);
}
#endif
void DnssdServer::OnICDModeChange()
{
// ICDMode changed, restart DNS-SD advertising, because SII and ICD key are affected by this change.
// StartServer will take care of setting the operational and commissionable advertisements
StartServer();
}
} // namespace app
} // namespace chip