blob: 86fd4d51ed4e48ca4be3c2ded58481978a86939c [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* 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.
*/
/**
* @file
* This file defines objects for a User-Directed Commissioning unsolicited
* initiator (client) and recipient (server).
*
*/
#pragma once
#include "UDCClients.h"
#include <lib/core/CHIPCore.h>
#include <lib/dnssd/Resolver.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
#include <transport/TransportMgr.h>
namespace chip {
namespace Protocols {
namespace UserDirectedCommissioning {
inline constexpr char kProtocolName[] = "UserDirectedCommissioning";
// Cache contains 16 clients. This may need to be tweaked.
inline constexpr uint8_t kMaxUDCClients = 16;
/**
* User Directed Commissioning Protocol Message Types
*/
enum class MsgType : uint8_t
{
IdentificationDeclaration = 0x00,
};
/**
* Represents the Identification Delaration message
* sent by a UDC client to a UDC server.
*
* ### IdentificationDeclaration format
*
* <pre>
* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
* ┃ instance name '\n' ┃ ignore ┃ additional data TLV ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┛
* │← · · kInstanceNameMaxLength + 1 · · →│← TLV DataLength() →│
*
* Commissioning kInstanceNameMaxLength is 16
* </pre>
*
*/
class DLL_EXPORT IdentificationDeclaration
{
public:
constexpr static size_t kUdcTLVDataMaxBytes = 500;
const char * GetInstanceName() const { return mInstanceName; }
void SetInstanceName(const char * instanceName) { Platform::CopyString(mInstanceName, instanceName); }
bool HasDiscoveryInfo() { return mVendorId != 0 && mProductId != 0 && mCdPort != 0 && strlen(mDeviceName) > 0; }
const char * GetDeviceName() const { return mDeviceName; }
void SetDeviceName(const char * deviceName) { Platform::CopyString(mDeviceName, deviceName); }
uint16_t GetCdPort() const { return mCdPort; }
void SetCdPort(uint16_t port) { mCdPort = port; }
uint16_t GetVendorId() const { return mVendorId; }
void SetVendorId(uint16_t vendorId) { mVendorId = vendorId; }
uint16_t GetProductId() const { return mProductId; }
void SetProductId(uint16_t productId) { mProductId = productId; }
const uint8_t * GetRotatingId() const { return mRotatingId; }
size_t GetRotatingIdLength() const { return mRotatingIdLen; }
void SetRotatingId(const uint8_t * rotatingId, size_t rotatingIdLen)
{
size_t maxSize = ArraySize(mRotatingId);
mRotatingIdLen = (maxSize < rotatingIdLen) ? maxSize : rotatingIdLen;
memcpy(mRotatingId, rotatingId, mRotatingIdLen);
}
bool GetTargetAppInfo(uint8_t index, TargetAppInfo & info) const
{
if (index < mNumTargetAppInfos)
{
info.vendorId = mTargetAppInfos[index].vendorId;
info.productId = mTargetAppInfos[index].productId;
return true;
}
return false;
}
uint8_t GetNumTargetAppInfos() const { return mNumTargetAppInfos; }
bool AddTargetAppInfo(TargetAppInfo vid)
{
if (mNumTargetAppInfos >= sizeof(mTargetAppInfos))
{
// already at max
return false;
}
mTargetAppInfos[mNumTargetAppInfos].vendorId = vid.vendorId;
mTargetAppInfos[mNumTargetAppInfos].productId = vid.productId;
mNumTargetAppInfos++;
return true;
}
const char * GetPairingInst() const { return mPairingInst; }
void SetPairingInst(const char * pairingInst) { Platform::CopyString(mPairingInst, pairingInst); }
uint16_t GetPairingHint() const { return mPairingHint; }
void SetPairingHint(uint16_t pairingHint) { mPairingHint = pairingHint; }
void SetNoPasscode(bool newValue) { mNoPasscode = newValue; };
bool GetNoPasscode() const { return mNoPasscode; };
void SetCdUponPasscodeDialog(bool newValue) { mCdUponPasscodeDialog = newValue; };
bool GetCdUponPasscodeDialog() const { return mCdUponPasscodeDialog; };
void SetCommissionerPasscode(bool newValue) { mCommissionerPasscode = newValue; };
bool GetCommissionerPasscode() const { return mCommissionerPasscode; };
void SetCommissionerPasscodeReady(bool newValue) { mCommissionerPasscodeReady = newValue; };
bool GetCommissionerPasscodeReady() const { return mCommissionerPasscodeReady; };
void SetCancelPasscode(bool newValue) { mCancelPasscode = newValue; };
bool GetCancelPasscode() const { return mCancelPasscode; };
/**
* Writes the IdentificationDeclaration message to the given buffer.
*
* @return Total number of bytes written or 0 if an error occurred.
*/
uint32_t WritePayload(uint8_t * payloadBuffer, size_t payloadBufferSize);
/**
* Reads the IdentificationDeclaration message from the given buffer.
*/
CHIP_ERROR ReadPayload(uint8_t * payloadBuffer, size_t payloadBufferSize);
/**
* Assigns fields from this Identification Declaration to the given UDC client state.
*/
void UpdateClientState(UDCClientState * client)
{
client->SetDeviceName(GetDeviceName());
client->SetVendorId(GetVendorId());
client->SetProductId(GetProductId());
client->SetRotatingId(GetRotatingId(), GetRotatingIdLength());
client->SetPairingInst(GetPairingInst());
client->SetPairingHint(GetPairingHint());
for (uint8_t i = 0; i < GetNumTargetAppInfos(); i++)
{
TargetAppInfo info;
if (GetTargetAppInfo(i, info))
{
client->AddTargetAppInfo(info);
}
}
client->SetCdPort(GetCdPort());
client->SetNoPasscode(GetNoPasscode());
client->SetCdUponPasscodeDialog(GetCdUponPasscodeDialog());
client->SetCommissionerPasscode(GetCommissionerPasscode());
client->SetCommissionerPasscodeReady(GetCommissionerPasscodeReady());
client->SetCancelPasscode(GetCancelPasscode());
}
void DebugLog()
{
ChipLogDetail(AppServer, "---- Identification Declaration Start ----");
ChipLogDetail(AppServer, "\tinstance: %s", mInstanceName);
if (strlen(mDeviceName) != 0)
{
ChipLogDetail(AppServer, "\tdevice Name: %s", mDeviceName);
}
if (mVendorId != 0)
{
ChipLogDetail(AppServer, "\tvendor id: %d", mVendorId);
}
if (mProductId != 0)
{
ChipLogDetail(AppServer, "\tproduct id: %d", mProductId);
}
if (mCdPort != 0)
{
ChipLogDetail(AppServer, "\tcd port: %d", mCdPort);
}
if (mRotatingIdLen > 0)
{
char rotatingIdString[chip::Dnssd::kMaxRotatingIdLen * 2 + 1] = "";
Encoding::BytesToUppercaseHexString(mRotatingId, mRotatingIdLen, rotatingIdString, sizeof(rotatingIdString));
ChipLogDetail(AppServer, "\trotating id: %s", rotatingIdString);
}
for (uint8_t i = 0; i < mNumTargetAppInfos; i++)
{
ChipLogDetail(AppServer, "\tapp vendor id / product id [%d]: %u/%u", i, mTargetAppInfos[i].vendorId,
mTargetAppInfos[i].productId);
}
if (strlen(mPairingInst) != 0)
{
ChipLogDetail(AppServer, "\tpairing instruction: %s", mPairingInst);
}
if (mPairingHint != 0)
{
ChipLogDetail(AppServer, "\tpairing hint: %d", mPairingHint);
}
if (mNoPasscode)
{
ChipLogDetail(AppServer, "\tno passcode: true");
}
if (mCdUponPasscodeDialog)
{
ChipLogDetail(AppServer, "\tcd upon passcode dialog: true");
}
if (mCommissionerPasscode)
{
ChipLogDetail(AppServer, "\tcommissioner passcode: true");
}
if (mCommissionerPasscodeReady)
{
ChipLogDetail(AppServer, "\tcommissioner passcode ready: true");
}
if (mCancelPasscode)
{
ChipLogDetail(AppServer, "\tcancel passcode: true");
}
ChipLogDetail(AppServer, "---- Identification Declaration End ----");
}
private:
// TODO: update spec per the latest tags
enum IdentificationDeclarationTLVTag
{
kVendorIdTag = 1,
kProductIdTag,
kDeviceNameTag,
kDeviceTypeTag,
kPairingInstTag,
kPairingHintTag,
kRotatingIdTag,
kCdPortTag,
kTargetAppListTag,
kTargetAppTag,
kAppVendorIdTag,
kAppProductIdTag,
kNoPasscodeTag,
kCdUponPasscodeDialogTag,
kCommissionerPasscodeTag,
kCommissionerPasscodeReadyTag,
kCancelPasscodeTag,
kMaxNum = UINT8_MAX
};
char mInstanceName[Dnssd::Commission::kInstanceNameMaxLength + 1] = {};
char mDeviceName[Dnssd::kMaxDeviceNameLen + 1] = {};
uint16_t mCdPort = 0;
uint16_t mVendorId = 0;
uint16_t mProductId = 0;
uint8_t mRotatingId[chip::Dnssd::kMaxRotatingIdLen];
size_t mRotatingIdLen = 0;
constexpr static size_t kMaxTargetAppInfos = 10;
uint8_t mNumTargetAppInfos = 0; // number of vendor Ids
TargetAppInfo mTargetAppInfos[kMaxTargetAppInfos];
char mPairingInst[chip::Dnssd::kMaxPairingInstructionLen + 1] = {};
uint16_t mPairingHint = 0;
bool mNoPasscode = false;
bool mCdUponPasscodeDialog = false;
bool mCommissionerPasscode = false;
bool mCommissionerPasscodeReady = false;
bool mCancelPasscode = false;
};
/**
* Represents the Commissioner Delaration message
* sent by a UDC server to a UDC client.
*/
class DLL_EXPORT CommissionerDeclaration
{
public:
enum class CdError : uint16_t
{
kNoError = 0,
kCommissionableDiscoveryFailed = 1,
kPaseConnectionFailed = 2,
kPaseAuthFailed = 3,
kDacValidationFailed = 4,
kAlreadyOnFabric = 5,
kOperationalDiscoveryFailed = 6,
kCaseConnectionFailed = 7,
kCaseAuthFailed = 8,
kConfigurationFailed = 9,
kBindingConfigurationFailed = 10,
kCommissionerPasscodeNotSupported = 11,
kInvalidIdentificationDeclarationParams = 12,
kAppInstallConsentPending = 13,
kAppInstalling = 14,
kAppInstallFailed = 15,
kAppInstalledRetryNeeded = 16,
kCommissionerPasscodeDisabled = 17,
kUnexpectedCommissionerPasscodeReady = 18
};
constexpr static size_t kUdcTLVDataMaxBytes = 500;
void SetErrorCode(CdError newValue) { mErrorCode = newValue; };
CdError GetErrorCode() const { return mErrorCode; };
void SetNeedsPasscode(bool newValue) { mNeedsPasscode = newValue; };
bool GetNeedsPasscode() const { return mNeedsPasscode; };
void SetNoAppsFound(bool newValue) { mNoAppsFound = newValue; };
bool GetNoAppsFound() const { return mNoAppsFound; };
void SetPasscodeDialogDisplayed(bool newValue) { mPasscodeDialogDisplayed = newValue; };
bool GetPasscodeDialogDisplayed() const { return mPasscodeDialogDisplayed; };
void SetCommissionerPasscode(bool newValue) { mCommissionerPasscode = newValue; };
bool GetCommissionerPasscode() const { return mCommissionerPasscode; };
void SetQRCodeDisplayed(bool newValue) { mQRCodeDisplayed = newValue; };
bool GetQRCodeDisplayed() const { return mQRCodeDisplayed; };
/**
* Writes the CommissionerDeclaration message to the given buffer.
*
* @return Total number of bytes written or 0 if an error occurred.
*/
uint32_t WritePayload(uint8_t * payloadBuffer, size_t payloadBufferSize);
/**
* Reads the CommissionerDeclaration message from the given buffer.
*/
CHIP_ERROR ReadPayload(uint8_t * payloadBuffer, size_t payloadBufferSize);
void DebugLog()
{
ChipLogDetail(AppServer, "---- Commissioner Declaration Start ----");
if (mErrorCode != CdError::kNoError)
{
ChipLogDetail(AppServer, "\terror code: %d", static_cast<uint16_t>(mErrorCode));
}
if (mNeedsPasscode)
{
ChipLogDetail(AppServer, "\tneeds passcode: true");
}
if (mNoAppsFound)
{
ChipLogDetail(AppServer, "\tno apps found: true");
}
if (mPasscodeDialogDisplayed)
{
ChipLogDetail(AppServer, "\tpasscode dialog displayed: true");
}
if (mCommissionerPasscode)
{
ChipLogDetail(AppServer, "\tcommissioner passcode: true");
}
if (mQRCodeDisplayed)
{
ChipLogDetail(AppServer, "\tQR code displayed: true");
}
ChipLogDetail(AppServer, "---- Commissioner Declaration End ----");
}
private:
// TODO: update spec per the latest tags
enum CommissionerDeclarationTLVTag
{
kErrorCodeTag = 1,
kNeedsPasscodeTag,
kNoAppsFoundTag,
kPasscodeDialogDisplayedTag,
kCommissionerPasscodeTag,
kQRCodeDisplayedTag,
kMaxNum = UINT8_MAX
};
CdError mErrorCode = CdError::kNoError;
bool mNeedsPasscode = false;
bool mNoAppsFound = false;
bool mPasscodeDialogDisplayed = false;
bool mCommissionerPasscode = false;
bool mQRCodeDisplayed = false;
};
class DLL_EXPORT InstanceNameResolver
{
public:
/**
* @brief
* Called when a UDC message is received specifying the given instanceName
* This method indicates that UDC Server needs the Commissionable Node corresponding to
* the given instance name to be found. UDC Server will wait for OnCommissionableNodeFound.
*
* @param instanceName DNS-SD instance name for the client requesting commissioning
*
*/
virtual void FindCommissionableNode(char * instanceName) = 0;
virtual ~InstanceNameResolver() = default;
};
class DLL_EXPORT UserConfirmationProvider
{
public:
/**
* @brief
* Called when an Identification Declaration UDC message has been received
* and corresponding nodeData has been found.
* It is expected that the implementer will prompt the user to confirm their intention to
* commission the given node, and obtain the setup code to allow commissioning to proceed,
* and then invoke commissioning on the given Node (using CHIP Device Controller, for example)
*
* @param[in] state The state for the UDC Client.
*
*/
virtual void OnUserDirectedCommissioningRequest(UDCClientState state) = 0;
/**
* @brief
* Called when an Identification Declaration UDC message has been received
* with the cancel flag set.
* It is expected that the implementer will tear down any dialog prompts for the
* commissionee instance (identified in the UDC client state argument).
*
* @param[in] state The state for the UDC Client.
*
*/
virtual void OnCancel(UDCClientState state) = 0;
/**
* @brief
* Called when an Identification Declaration UDC message has been received
* with the commissioner passcode ready flag set.
* It is expected that the implementer will invoke commissioning on the
* commissionee instance (identified in the UDC client state argument).
*
* @param[in] state The state for the UDC Client.
*
*/
virtual void OnCommissionerPasscodeReady(UDCClientState state) = 0;
virtual ~UserConfirmationProvider() = default;
};
class DLL_EXPORT CommissionerDeclarationHandler
{
public:
/**
* @brief
* Called when a Commissioner Declaration UDC message has been received.
* It is expected that the implementer will de-dup messages received from the
* same source within a short (1 second) time window.
*
* @param[in] source The source of the Commissioner Declaration Message.
* @param[in] cd The Commissioner Declaration Message.
*
*/
virtual void OnCommissionerDeclarationMessage(const chip::Transport::PeerAddress & source, CommissionerDeclaration cd) = 0;
virtual ~CommissionerDeclarationHandler() = default;
};
/**
* TODO:
* - add processing of Commissioner Declaration flags
*/
class DLL_EXPORT UserDirectedCommissioningClient : public TransportMgrDelegate
{
public:
/**
* Send a User Directed Commissioning message to a CHIP node.
*
* @param transportMgr A transport to use for sending the message.
* @param idMessage The Identification Declaration message.
* @param peerAddress Address of destination.
*
* @return CHIP_ERROR_NO_MEMORY if allocation fails.
* Other CHIP_ERROR codes as returned by the lower layers.
*
*/
CHIP_ERROR SendUDCMessage(TransportMgrBase * transportMgr, IdentificationDeclaration idMessage,
chip::Transport::PeerAddress peerAddress);
/**
* Encode a User Directed Commissioning message.
*
* @param payload A PacketBufferHandle with the payload.
*
* @return CHIP_ERROR_NO_MEMORY if allocation fails.
* Other CHIP_ERROR codes as returned by the lower layers.
*
*/
CHIP_ERROR EncodeUDCMessage(const System::PacketBufferHandle & payload);
/**
* Set the listener to be called when a Commissioner Declaration UDC request is received.
*
* @param[in] commissionerDeclarationHandler The callback function to handle the message.
*
*/
void SetCommissionerDeclarationHandler(CommissionerDeclarationHandler * commissionerDeclarationHandler)
{
mCommissionerDeclarationHandler = commissionerDeclarationHandler;
}
private:
void OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msgBuf) override;
CommissionerDeclarationHandler * mCommissionerDeclarationHandler = nullptr;
};
/**
* TODO:
* - add processing of Identification Declaration flags
*/
class DLL_EXPORT UserDirectedCommissioningServer : public TransportMgrDelegate
{
public:
/**
* Set the listener to be called when a UDC request is received
* and the Instance Name provided needs to be resolved.
*
* The resolver should call OnCommissionableNodeFound when the instance is found
*
* @param[in] instanceNameResolver The callback function to receive UDC request instance name.
*
*/
void SetInstanceNameResolver(InstanceNameResolver * instanceNameResolver) { mInstanceNameResolver = instanceNameResolver; }
/**
* Set the listener to be called when a UDC request is received
* and the Instance Name has been resolved.
*
* The provider should prompt the user to allow commissioning of the node and provide the setup code.
*
* @param[in] userConfirmationProvider The callback function to obtain user confirmation.
*
*/
void SetUserConfirmationProvider(UserConfirmationProvider * userConfirmationProvider)
{
mUserConfirmationProvider = userConfirmationProvider;
}
/**
* Update the processing state for a UDC Client based upon instance name.
*
* This can be used by the UX to set the state to one of the following values:
* - kUserDeclined
* - kObtainingOnboardingPayload
* - kCommissioningNode
* - kCommissioningFailed
*
* @param[in] instanceName The instance name for the UDC Client.
* @param[in] state The state for the UDC Client.
*
*/
void SetUDCClientProcessingState(char * instanceName, UDCClientProcessingState state);
/**
* Reset the processing states for all UDC Clients
*
*/
void ResetUDCClientProcessingStates() { mUdcClients.ResetUDCClientStates(); }
/**
* Called when a CHIP Node in commissioning mode is found.
*
* Lookup instanceName from nodeData in the active UDC Client states
* and if current state is kDiscoveringNode then change to kPromptingUser and
* call UX Prompt callback
*
* @param[in] nodeData DNS-SD response data.
*
*/
void OnCommissionableNodeFound(const Dnssd::DiscoveredNodeData & nodeData);
/**
* Get the cache of UDC Clients
*
*/
UDCClients<kMaxUDCClients> & GetUDCClients() { return mUdcClients; }
/**
* Print the cache of UDC Clients
*
*/
void PrintUDCClients();
/**
* Send a Commissioner Declaration message to the given peer address
*
* Only one message will be sent.
* Clients should follow spec and send up to 5 times with 100ms sleep between each call.
*/
CHIP_ERROR SendCDCMessage(CommissionerDeclaration cdMessage, chip::Transport::PeerAddress peerAddress);
/**
* Encode a User Directed Commissioning message.
*
* @param payload A PacketBufferHandle with the payload.
*
* @return CHIP_ERROR_NO_MEMORY if allocation fails.
* Other CHIP_ERROR codes as returned by the lower layers.
*
*/
CHIP_ERROR EncodeUDCMessage(const System::PacketBufferHandle & payload);
/**
* Assign the transport manager to use for Commissioner Declaration messages
*/
void SetTransportManager(TransportMgrBase * transportMgr) { mTransportMgr = transportMgr; }
private:
InstanceNameResolver * mInstanceNameResolver = nullptr;
UserConfirmationProvider * mUserConfirmationProvider = nullptr;
void HandleNewUDC(const Transport::PeerAddress & source, IdentificationDeclaration & id);
void HandleUDCCancel(IdentificationDeclaration & id);
void HandleUDCCommissionerPasscodeReady(IdentificationDeclaration & id);
void OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msgBuf) override;
UDCClients<kMaxUDCClients> mUdcClients; // < Active UDC clients
TransportMgrBase * mTransportMgr = nullptr;
};
} // namespace UserDirectedCommissioning
template <>
struct MessageTypeTraits<UserDirectedCommissioning::MsgType>
{
static constexpr const Protocols::Id & ProtocolId() { return UserDirectedCommissioning::Id; }
static auto GetTypeToNameTable()
{
static const std::array<MessageTypeNameLookup, 1> typeToNameTable = {
{
{ UserDirectedCommissioning::MsgType::IdentificationDeclaration, "IdentificationDeclaration" },
},
};
return &typeToNameTable;
}
};
} // namespace Protocols
} // namespace chip