blob: 4e314669201add521642b0461e2cd510074d03bf [file] [log] [blame]
/*
* Copyright (c) 2022 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.
*/
#pragma once
#include <app/OperationalSessionSetup.h>
#include <app/data-model/NullObject.h>
#include <controller/CHIPDeviceController.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPCallback.h>
#include <lib/core/CHIPError.h>
#include <lib/core/NodeId.h>
#include <lib/core/Optional.h>
#include <setup_payload/SetupPayload.h>
#include <system/SystemClock.h>
namespace chip {
namespace Controller {
// Passing SetupPayload by value on purpose, in case a consumer decides to reuse
// this object from inside the callback.
typedef void (*OnOpenCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload);
typedef void (*OnOpenBasicCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status);
/**
* A helper class to open a commissioning window given some parameters.
*/
class CommissioningWindowOpener
{
public:
CommissioningWindowOpener(DeviceController * controller) :
mController(controller), mDeviceConnected(&OnDeviceConnectedCallback, this),
mDeviceConnectionFailure(&OnDeviceConnectionFailureCallback, this)
{}
enum class CommissioningWindowOption : uint8_t
{
kOriginalSetupCode = 0,
kTokenWithRandomPIN,
kTokenWithProvidedPIN,
};
/*
* @brief
* Try to look up the device attached to our controller with the given
* node id and ask it to re-enter commissioning mode with its original
* PASE verifier, discriminator, etc. The device will exit commissioning
* mode after a successful commissioning, or after the given `timeout`
* time.
*
* @param[in] deviceId The device Id.
* @param[in] timeout The commissioning mode should terminate after this much time.
* @param[in] callback The callback to call once the commissioning window is
* open or if an error occurs.
*/
CHIP_ERROR OpenBasicCommissioningWindow(NodeId deviceId, System::Clock::Seconds16 timeout,
Callback::Callback<OnOpenBasicCommissioningWindow> * callback);
/**
* @brief
* Try to look up the device attached to our controller with the given
* node id and ask it to re-enter commissioning mode with a PASE verifier
* derived from the given information and the given discriminator. The
* device will exit commissioning mode after a successful commissioning,
* or after the given `timeout` time.
*
* @param[in] deviceId The device Id.
* @param[in] timeout The commissioning mode should terminate after this much time.
* @param[in] iteration The PAKE iteration count associated with the PAKE Passcode ID and ephemeral
* PAKE passcode verifier to be used for this commissioning.
* @param[in] discriminator The long discriminator for the DNS-SD advertisement.
* @param[in] setupPIN The setup PIN to use, or NullOptional to use a randomly-generated one.
* @param[in] salt The salt to use, or NullOptional to use a
* randomly-generated one. If provided, must be at
* least kSpake2p_Min_PBKDF_Salt_Length bytes and
* at most kSpake2p_Max_PBKDF_Salt_Length bytes in
* length.
* @param[in] callback The function to be called on success or failure of opening of commissioning window.
* @param[out] payload The setup payload, not including the VID/PID bits,
* even if those were asked for, that is generated
* based on the passed-in information. The payload
* provided to the callback function, unlike this
* out parameter, will include the VID/PID bits if
* readVIDPIDAttributes is true.
*
* @param[in] readVIDPIDAttributes Should the API internally read VID and PID from the device while opening the
* commissioning window. If this argument is `true`, the API will read VID and
* PID from the device and include them in the setup payload passed to the
* callback.
*/
CHIP_ERROR OpenCommissioningWindow(NodeId deviceId, System::Clock::Seconds16 timeout, uint32_t iteration,
uint16_t discriminator, Optional<uint32_t> setupPIN, Optional<ByteSpan> salt,
Callback::Callback<OnOpenCommissioningWindow> * callback, SetupPayload & payload,
bool readVIDPIDAttributes = false);
private:
enum class Step : uint8_t
{
// Ready to start opening a commissioning window.
kAcceptCommissioningStart,
// Need to read VID.
kReadVID,
// Need to read PID.
kReadPID,
// Need to open commissioning window.
kOpenCommissioningWindow,
};
CHIP_ERROR OpenCommissioningWindowInternal(Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle);
static void OnPIDReadResponse(void * context, uint16_t value);
static void OnVIDReadResponse(void * context, VendorId value);
static void OnVIDPIDReadFailureResponse(void * context, CHIP_ERROR error);
static void OnOpenCommissioningWindowSuccess(void * context, const app::DataModel::NullObjectType &);
static void OnOpenCommissioningWindowFailure(void * context, CHIP_ERROR error);
static void OnDeviceConnectedCallback(void * context, Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle);
static void OnDeviceConnectionFailureCallback(void * context, const ScopedNodeId & peerId, CHIP_ERROR error);
DeviceController * const mController = nullptr;
Step mNextStep = Step::kAcceptCommissioningStart;
Callback::Callback<OnOpenCommissioningWindow> * mCommissioningWindowCallback = nullptr;
Callback::Callback<OnOpenBasicCommissioningWindow> * mBasicCommissioningWindowCallback = nullptr;
SetupPayload mSetupPayload;
NodeId mNodeId = kUndefinedNodeId;
System::Clock::Seconds16 mCommissioningWindowTimeout = System::Clock::kZero;
CommissioningWindowOption mCommissioningWindowOption = CommissioningWindowOption::kOriginalSetupCode;
Spake2pVerifier mVerifier; // Used for non-basic commissioning.
// Parameters needed for non-basic commissioning.
uint32_t mPBKDFIterations = 0;
uint8_t mPBKDFSaltBuffer[kSpake2p_Max_PBKDF_Salt_Length];
ByteSpan mPBKDFSalt;
Callback::Callback<OnDeviceConnected> mDeviceConnected;
Callback::Callback<OnDeviceConnectionFailure> mDeviceConnectionFailure;
};
/**
* A helper class that can be used by consumers that don't care about the callback from the
* open-commissioning-window process and just want automatic cleanup of the CommissioningWindowOpener when done
* with it.
*/
class AutoCommissioningWindowOpener : private CommissioningWindowOpener
{
public:
// Takes the same arguments as CommissioningWindowOpener::OpenBasicCommissioningWindow except without the
// callback.
static CHIP_ERROR OpenBasicCommissioningWindow(DeviceController * controller, NodeId deviceId,
System::Clock::Seconds16 timeout);
// Takes the same arguments as CommissioningWindowOpener::OpenCommissioningWindow except without the
// callback.
static CHIP_ERROR OpenCommissioningWindow(DeviceController * controller, NodeId deviceId, System::Clock::Seconds16 timeout,
uint32_t iteration, uint16_t discriminator, Optional<uint32_t> setupPIN,
Optional<ByteSpan> salt, SetupPayload & payload, bool readVIDPIDAttributes = false);
private:
AutoCommissioningWindowOpener(DeviceController * controller);
static void OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload);
static void OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status);
chip::Callback::Callback<chip::Controller::OnOpenCommissioningWindow> mOnOpenCommissioningWindowCallback;
chip::Callback::Callback<chip::Controller::OnOpenBasicCommissioningWindow> mOnOpenBasicCommissioningWindowCallback;
};
} // Namespace Controller
} // namespace chip