Przeglądaj źródła

IP cluster-based commissioning (#6798)

* Cluster-based commissioning - phase 1.

Adds calls to clusters to commission a device properly. This is
intended to be used to setup a device that is already on the
network, but not already on a CHIP network. Hence, network
credentials are not required, but we do need to send all the
remaining commissioning information.

Challenges:
IM cluster commands time out more often than the non-IM commands,
even when the device is clearly sending a reply.
Need to find the race and get rid of it.

Still to do:
- add operational credentials into state machine
- add more commissioning phases (time set etc.)
- add proper scan command for determining ethernet netif name
- swap over to CASE before sending commissioning complete command
- once we're totally in IM, combine some commands
- add post commissioning setup commands (trusted node ids etc.)

* Restyled by gn

* Address review comments.

* Add back some lines lost in a rebase.

* Address review comments

- remove heap allocation from callbacks
- overwrite node ID resolution functions rather than using a delegate

* Restyled by gn

* Restyled by shfmt

Co-authored-by: Restyled.io <commits@restyled.io>
C Freeman 4 lat temu
rodzic
commit
e86bb35ed6

+ 12 - 0
config/esp32/components/chip/Kconfig

@@ -654,6 +654,18 @@ menu "CHIP Device Layer"
 
     endmenu
 
+    menu "Commissioning options"
+        config RENDEZVOUS_WAIT_FOR_COMMISSIONING_COMPLETE
+            int "Use full IP-based commissioning (expect cluster commands)"
+            default 0
+            help
+                Setting this to y will cause the commissioner to send commissioning commands to the
+                various clusters after establishing a PASE session.
+
+    endmenu
+
+
+
     menu "Testing Options"
 
         config ENABLE_TEST_DEVICE_IDENTITY

+ 1 - 1
examples/all-clusters-app/esp32/main/main.cpp

@@ -339,7 +339,7 @@ public:
         else if (i == 2)
         {
             app::Mdns::AdvertiseCommissionableNode();
-            OpenDefaultPairingWindow(ResetAdmins::kNo, PairingWindowAdvertisement::kMdns);
+            OpenDefaultPairingWindow(ResetAdmins::kYes, PairingWindowAdvertisement::kMdns);
         }
     }
 

+ 8 - 1
scripts/build_python.sh

@@ -40,6 +40,7 @@ ENVIRONMENT_ROOT="$CHIP_ROOT/out/python_env"
 
 declare chip_detail_logging=false
 declare chip_mdns="minimal"
+declare clusters=true
 
 help() {
 
@@ -52,6 +53,8 @@ Input Options:
                                                             By default it is false.
   -m, --chip_mdns           ChipMDNSValue                   Specify ChipMDNSValue as platform or minimal.
                                                             By default it is minimal.
+  -c, --clusters_for_ip_commissioning  true/false           Specify whether to use clusters for IP commissioning.
+                                                            By default it is true.
 "
 }
 
@@ -71,6 +74,10 @@ while (($#)); do
             chip_mdns=$2
             shift
             ;;
+        --clusters_for_ip_commissioning | -c)
+            clusters=$2
+            shift
+            ;;
         -*)
             help
             echo "Unknown Option \"$1\""
@@ -87,7 +94,7 @@ echo "Input values: chip_detail_logging = $chip_detail_logging , chip_mdns = \"$
 source "$CHIP_ROOT/scripts/activate.sh"
 
 # Generates ninja files
-gn --root="$CHIP_ROOT" gen "$OUTPUT_ROOT" --args="chip_detail_logging=$chip_detail_logging chip_mdns=\"$chip_mdns\""
+gn --root="$CHIP_ROOT" gen "$OUTPUT_ROOT" --args="chip_detail_logging=$chip_detail_logging chip_mdns=\"$chip_mdns\" chip_use_clusters_for_ip_commissioning=$clusters"
 
 # Compiles python files
 ninja -C "$OUTPUT_ROOT" python

+ 13 - 0
src/app/clusters/network-commissioning/network-commissioning.cpp

@@ -28,6 +28,7 @@
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <platform/ConnectivityManager.h>
+#include <platform/internal/DeviceControlServer.h>
 
 #if CHIP_DEVICE_CONFIG_ENABLE_THREAD
 #include <platform/ThreadStackManager.h>
@@ -258,6 +259,14 @@ EmberAfNetworkCommissioningError OnEnableNetworkCommandCallbackInternal(app::Com
 {
     size_t networkSeq;
     EmberAfNetworkCommissioningError err = EMBER_ZCL_NETWORK_COMMISSIONING_ERROR_NETWORK_ID_NOT_FOUND;
+    // TODO(cecille): This is very dangerous - need to check against real netif name, ensure no password.
+    constexpr char ethernetNetifMagicCode[] = "ETH0";
+    if (networkID.size() == sizeof(ethernetNetifMagicCode) &&
+        memcmp(networkID.data(), ethernetNetifMagicCode, networkID.size()) == 0)
+    {
+        ChipLogProgress(Zcl, "Wired network enabling requested. Assuming success.");
+        ExitNow(err = EMBER_ZCL_NETWORK_COMMISSIONING_ERROR_SUCCESS);
+    }
 
     for (networkSeq = 0; networkSeq < kMaxNetworks; networkSeq++)
     {
@@ -274,6 +283,10 @@ EmberAfNetworkCommissioningError OnEnableNetworkCommandCallbackInternal(app::Com
     }
     // TODO: We should encode response command here.
 exit:
+    if (err == EMBER_ZCL_NETWORK_COMMISSIONING_ERROR_SUCCESS)
+    {
+        DeviceLayer::Internal::DeviceControlServer::DeviceControlSvr().EnableNetworkForOperational(networkID);
+    }
     return err;
 }
 

+ 43 - 1
src/app/server/RendezvousServer.cpp

@@ -17,6 +17,7 @@
 
 #include <app/server/RendezvousServer.h>
 
+#include <app/server/Mdns.h>
 #include <app/server/StorablePeerConnection.h>
 #include <core/CHIPError.h>
 #include <support/CodeUtils.h>
@@ -33,9 +34,38 @@ using namespace ::chip::Transport;
 using namespace ::chip::DeviceLayer;
 
 namespace chip {
+
+namespace {
+void OnPlatformEventWrapper(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
+{
+    RendezvousServer * server = reinterpret_cast<RendezvousServer *>(arg);
+    server->OnPlatformEvent(event);
+}
+} // namespace
 static constexpr uint32_t kSpake2p_Iteration_Count = 100;
 static const char * kSpake2pKeyExchangeSalt        = "SPAKE2P Key Salt";
 
+void RendezvousServer::OnPlatformEvent(const DeviceLayer::ChipDeviceEvent * event)
+{
+    if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
+    {
+        if (event->CommissioningComplete.status == CHIP_NO_ERROR)
+        {
+            ChipLogProgress(Discovery, "Commissioning completed successfully");
+        }
+        else
+        {
+            ChipLogError(Discovery, "Commissioning errored out with error %u", event->CommissioningComplete.status);
+        }
+        // TODO: Commissioning complete means we can finalize the admin in our storage
+    }
+    else if (event->Type == DeviceLayer::DeviceEventType::kOperationalNetworkEnabled)
+    {
+        app::Mdns::AdvertiseOperational();
+        ChipLogError(Discovery, "Operational advertising enabled");
+    }
+}
+
 CHIP_ERROR RendezvousServer::WaitForPairing(const RendezvousParameters & params, Messaging::ExchangeManager * exchangeManager,
                                             TransportMgrBase * transportMgr, SecureSessionMgr * sessionMgr,
                                             Transport::AdminPairingInfo * admin)
@@ -129,7 +159,19 @@ void RendezvousServer::OnSessionEstablished()
         mDelegate->OnRendezvousStarted();
     }
 
-    Cleanup();
+    DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformEventWrapper, reinterpret_cast<intptr_t>(this));
+
+    if (mPairingSession.PeerConnection().GetPeerAddress().GetTransportType() == Transport::Type::kBle)
+    {
+        Cleanup();
+    }
+    else
+    {
+        // TODO: remove this once we move all tools / examples onto cluster-based IP commissioning.
+#if CONFIG_RENDEZVOUS_WAIT_FOR_COMMISSIONING_COMPLETE
+        Cleanup();
+#endif
+    }
 
     ChipLogProgress(AppServer, "Device completed Rendezvous process");
     StorablePeerConnection connection(mPairingSession, mAdmin->GetAdminId());

+ 1 - 0
src/app/server/RendezvousServer.h

@@ -47,6 +47,7 @@ public:
 
     uint16_t GetNextKeyId() const { return mNextKeyId; }
     void SetNextKeyId(uint16_t id) { mNextKeyId = id; }
+    void OnPlatformEvent(const DeviceLayer::ChipDeviceEvent * event);
 
 private:
     AppDelegate * mDelegate;

+ 1 - 0
src/app/server/Server.cpp

@@ -457,6 +457,7 @@ static void ChipEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_
     {
     case DeviceLayer::DeviceEventType::kInternetConnectivityChange:
         VerifyOrReturn(event->InternetConnectivityChange.IPv4 == DeviceLayer::kConnectivity_Established);
+        // TODO : Need to check if we're properly commissioned.
         advertise();
         break;
 #if CHIP_DEVICE_CONFIG_ENABLE_THREAD

+ 280 - 12
src/controller/CHIPDeviceController.cpp

@@ -35,8 +35,12 @@
 // module header, comes first
 #include <controller/CHIPDeviceController.h>
 
+#include <app/common/gen/enums.h>
+#include <controller/data_model/gen/CHIPClusters.h>
+
 #if CONFIG_DEVICE_LAYER
 #include <platform/CHIPDeviceLayer.h>
+#include <platform/ConfigurationManager.h>
 #endif
 
 #include <app/InteractionModelEngine.h>
@@ -365,11 +369,8 @@ CHIP_ERROR DeviceController::Shutdown()
     mAdmins.ReleaseAdminId(mAdminId);
 
 #if CHIP_DEVICE_CONFIG_ENABLE_MDNS
-    if (mDeviceAddressUpdateDelegate != nullptr)
-    {
-        Mdns::Resolver::Instance().SetResolverDelegate(nullptr);
-        mDeviceAddressUpdateDelegate = nullptr;
-    }
+    Mdns::Resolver::Instance().SetResolverDelegate(nullptr);
+    mDeviceAddressUpdateDelegate = nullptr;
 #endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS
 
     return CHIP_NO_ERROR;
@@ -729,6 +730,7 @@ void DeviceController::OnNodeIdResolved(const chip::Mdns::ResolvedNodeData & nod
     PersistDevice(device);
 
 exit:
+
     if (mDeviceAddressUpdateDelegate != nullptr)
     {
         mDeviceAddressUpdateDelegate->OnAddressUpdateComplete(nodeData.mPeerId.GetNodeId(), err);
@@ -784,6 +786,7 @@ ControllerDeviceInitParams DeviceController::GetControllerDeviceInitParams()
 }
 
 DeviceCommissioner::DeviceCommissioner() :
+    mSuccess(BasicSuccess, this), mFailure(BasicFailure, this),
     mOpCSRResponseCallback(OnOperationalCertificateSigningRequest, this),
     mOpCertResponseCallback(OnOperationalCertificateAddResponse, this), mRootCertResponseCallback(OnRootCertSuccessResponse, this),
     mOnCSRFailureCallback(OnCSRFailureResponse, this), mOnCertFailureCallback(OnAddOpCertFailureResponse, this),
@@ -1102,12 +1105,26 @@ void DeviceCommissioner::OnSessionEstablished()
     ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake\n");
 
     // TODO: Add code to receive OpCSR from the device, and process the signing request
-    err = SendOperationalCertificateSigningRequestCommand(device);
-    if (err != CHIP_NO_ERROR)
+    // For IP rendezvous, this is sent as part of the state machine.
+#if CONFIG_USE_CLUSTERS_FOR_IP_COMMISSIONING
+    bool sendOperationalCertsImmediately = !mIsIPRendezvous;
+#else
+    bool sendOperationalCertsImmediately = true;
+#endif
+
+    if (sendOperationalCertsImmediately)
     {
-        ChipLogError(Controller, "Failed in sending the CSR request to the device: err %s", ErrorStr(err));
-        OnSessionEstablishmentError(err);
-        return;
+        err = SendOperationalCertificateSigningRequestCommand(device);
+        if (err != CHIP_NO_ERROR)
+        {
+            ChipLogError(Ble, "Failed in sending opcsr request command to the device: err %s", ErrorStr(err));
+            OnSessionEstablishmentError(err);
+            return;
+        }
+    }
+    else
+    {
+        AdvanceCommissioningStage(CHIP_NO_ERROR);
     }
 }
 
@@ -1347,6 +1364,9 @@ CHIP_ERROR DeviceCommissioner::OnOperationalCredentialsProvisioningCompletion(De
     ChipLogProgress(Controller, "Operational credentials provisioned on device %p", device);
     VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
 
+#if CONFIG_USE_CLUSTERS_FOR_IP_COMMISSIONING
+    AdvanceCommissioningStage(CHIP_NO_ERROR);
+#else
     mPairingSession.ToSerializable(device->GetPairing());
     mSystemLayer->CancelTimer(OnSessionEstablishmentTimeoutCallback, this);
 
@@ -1359,13 +1379,12 @@ CHIP_ERROR DeviceCommissioner::OnOperationalCredentialsProvisioningCompletion(De
     // Also persist the device list at this time
     // This makes sure that a newly added device is immediately available
     PersistDeviceList();
-
     if (mPairingDelegate != nullptr)
     {
         mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess);
     }
-
     RendezvousCleanup(CHIP_NO_ERROR);
+#endif
 
     return CHIP_NO_ERROR;
 }
@@ -1505,6 +1524,255 @@ CHIP_ERROR DeviceControllerInteractionModelDelegate::CommandResponseProcessed(co
 
     return CHIP_NO_ERROR;
 }
+void BasicSuccess(void * context, uint16_t val)
+{
+    ChipLogProgress(Controller, "Received success response 0x%x\n", val);
+    DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
+    commissioner->AdvanceCommissioningStage(CHIP_NO_ERROR);
+}
+
+void BasicFailure(void * context, uint8_t status)
+{
+    ChipLogProgress(Controller, "Received failure response %d\n", (int) status);
+    DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
+    commissioner->OnSessionEstablishmentError(static_cast<CHIP_ERROR>(status));
+}
+
+#if CHIP_DEVICE_CONFIG_ENABLE_MDNS
+void DeviceCommissioner::OnNodeIdResolved(const chip::Mdns::ResolvedNodeData & nodeData)
+{
+    Device * device = nullptr;
+    if (mDeviceBeingPaired >= kNumMaxActiveDevices)
+    {
+        return;
+    }
+
+    device = &mActiveDevices[mDeviceBeingPaired];
+    if (device->GetDeviceId() == nodeData.mPeerId.GetNodeId() && mCommissioningStage == CommissioningStage::kFindOperational)
+    {
+        AdvanceCommissioningStage(CHIP_NO_ERROR);
+    }
+    DeviceController::OnNodeIdResolved(nodeData);
+}
+
+void DeviceCommissioner::OnNodeIdResolutionFailed(const chip::PeerId & peer, CHIP_ERROR error)
+{
+    Device * device = nullptr;
+    if (mDeviceBeingPaired >= kNumMaxActiveDevices)
+    {
+        return;
+    }
+
+    device = &mActiveDevices[mDeviceBeingPaired];
+    if (device->GetDeviceId() == peer.GetNodeId() && mCommissioningStage == CommissioningStage::kFindOperational)
+    {
+        OnSessionEstablishmentError(error);
+    }
+    DeviceController::OnNodeIdResolutionFailed(peer, error);
+}
+
+#endif
+
+CommissioningStage DeviceCommissioner::GetNextCommissioningStage()
+{
+    switch (mCommissioningStage)
+    {
+    case CommissioningStage::kSecurePairing:
+        return CommissioningStage::kArmFailsafe;
+    case CommissioningStage::kArmFailsafe:
+        return CommissioningStage::kConfigRegulatory;
+    case CommissioningStage::kConfigRegulatory:
+        return CommissioningStage::kCheckCertificates;
+    case CommissioningStage::kCheckCertificates:
+        return CommissioningStage::kNetworkEnable; // TODO : for softAP, this needs to be network setup
+    case CommissioningStage::kNetworkEnable:
+#if CHIP_DEVICE_CONFIG_ENABLE_MDNS
+        return CommissioningStage::kFindOperational; // TODO : once case is working, need to add stages to find and reconnect
+                                                     // here.
+#else
+        return CommissioningStage::kSendComplete;
+#endif
+    case CommissioningStage::kFindOperational:
+        return CommissioningStage::kSendComplete;
+    case CommissioningStage::kSendComplete:
+        return CommissioningStage::kCleanup;
+
+    // Currently unimplemented.
+    case CommissioningStage::kConfigACL:
+    case CommissioningStage::kNetworkSetup:
+    case CommissioningStage::kScanNetworks:
+        return CommissioningStage::kError;
+    // Neither of these have a next stage so return kError;
+    case CommissioningStage::kCleanup:
+    case CommissioningStage::kError:
+        return CommissioningStage::kError;
+    }
+    return CommissioningStage::kError;
+}
+
+void DeviceCommissioner::AdvanceCommissioningStage(CHIP_ERROR err)
+{
+    // For now, we ignore errors coming in from the device since not all commissioning clusters are implemented on the device
+    // side.
+    CommissioningStage nextStage = GetNextCommissioningStage();
+    if (nextStage == CommissioningStage::kError)
+    {
+        return;
+    }
+
+    if (!mIsIPRendezvous)
+    {
+        return;
+    }
+    Device * device = nullptr;
+    if (mDeviceBeingPaired >= kNumMaxActiveDevices)
+    {
+        return;
+    }
+
+    device = &mActiveDevices[mDeviceBeingPaired];
+
+    // TODO(cecille): We probably want something better than this for breadcrumbs.
+    uint64_t breadcrumb = static_cast<uint64_t>(nextStage);
+
+    // TODO(cecille): This should be customized per command.
+    constexpr uint32_t kCommandTimeoutMs = 3000;
+
+    switch (nextStage)
+    {
+    case CommissioningStage::kArmFailsafe: {
+        // TODO(cecille): This is NOT the right way to do this - we should consider attaching an im delegate per command or
+        // something. Per exchange context?
+        ChipLogProgress(Controller, "Arming failsafe");
+        // TODO(cecille): Find a way to enumerate the clusters here.
+        GeneralCommissioningCluster genCom;
+        uint16_t commissioningExpirySeconds = 5;
+        // TODO: should get the endpoint information from the descriptor cluster.
+        genCom.Associate(device, 0);
+        genCom.ArmFailSafe(mSuccess.Cancel(), mFailure.Cancel(), commissioningExpirySeconds, breadcrumb, kCommandTimeoutMs);
+    }
+    break;
+    case CommissioningStage::kConfigRegulatory: {
+        // To set during config phase:
+        // UTC time
+        // time zone
+        // dst offset
+        // Regulatory config
+        // TODO(cecille): Set time as well once the time cluster is implemented
+        // TODO(cecille): Worthwhile to keep this around as part of the class?
+        // TODO(cecille): Where is the country config actually set?
+        ChipLogProgress(Controller, "Setting Regulatory Config");
+        uint32_t regulatoryLocation = EMBER_ZCL_REGULATORY_LOCATION_TYPE_OUTDOOR;
+#if CONFIG_DEVICE_LAYER
+        CHIP_ERROR status = DeviceLayer::ConfigurationMgr().GetRegulatoryLocation(regulatoryLocation);
+#else
+        CHIP_ERROR status = CHIP_ERROR_NOT_IMPLEMENTED;
+#endif
+        if (status != CHIP_NO_ERROR)
+        {
+            ChipLogError(Controller, "Unable to find regulatory location, defaulting to outdoor");
+        }
+
+        static constexpr size_t kMaxCountryCodeSize = 3;
+        char countryCodeStr[kMaxCountryCodeSize]    = "WW";
+        size_t actualCountryCodeSize                = 2;
+
+#if CONFIG_DEVICE_LAYER
+        status = DeviceLayer::ConfigurationMgr().GetCountryCode(countryCodeStr, kMaxCountryCodeSize, actualCountryCodeSize);
+#else
+        status            = CHIP_ERROR_NOT_IMPLEMENTED;
+#endif
+        if (status != CHIP_NO_ERROR)
+        {
+            ChipLogError(Controller, "Unable to find country code, defaulting to WW");
+        }
+        chip::ByteSpan countryCode(reinterpret_cast<uint8_t *>(countryCodeStr), actualCountryCodeSize);
+
+        GeneralCommissioningCluster genCom;
+        genCom.Associate(device, 0);
+        genCom.SetRegulatoryConfig(mSuccess.Cancel(), mFailure.Cancel(), static_cast<uint8_t>(regulatoryLocation), countryCode,
+                                   breadcrumb, kCommandTimeoutMs);
+    }
+    break;
+    case CommissioningStage::kCheckCertificates: {
+        ChipLogProgress(Controller, "Exchanging certificates");
+        // TODO(cecille): Once this is implemented through the clusters, it should be moved to the proper stage and the callback
+        // should advance the commissioning stage
+        CHIP_ERROR status = SendOperationalCertificateSigningRequestCommand(device);
+        if (status != CHIP_NO_ERROR)
+        {
+            ChipLogError(Controller, "Failed in sending opcsr request command to the device: err %s", ErrorStr(err));
+            OnSessionEstablishmentError(err);
+            return;
+        }
+    }
+    break;
+    // TODO: Right now, these stages are not implemented as a separate stage because they are no-ops.
+    // Once these are implemented through the clusters, these should be moved into their separate stages and the callbacks
+    // should advance the commissioning stage.
+    case CommissioningStage::kConfigACL:
+    case CommissioningStage::kNetworkSetup:
+    case CommissioningStage::kScanNetworks:
+        // TODO: Implement
+        break;
+    case CommissioningStage::kNetworkEnable: {
+        ChipLogProgress(Controller, "Enabling Network");
+        // TODO: For ethernet, we actually need a scan stage to get the ethernet netif name. Right now, default to using a magic
+        // value to enable without checks.
+        NetworkCommissioningCluster netCom;
+        // TODO: should get the endpoint information from the descriptor cluster.
+        netCom.Associate(device, 0);
+        // TODO: Once network credential sending is implemented, attempting to set wifi credential on an ethernet only device
+        // will cause an error to be sent back. At that point, we should scan and we shoud see the proper ethernet network ID
+        // returned in the scan results. For now, we use magic.
+        char magicNetworkEnableCode[] = "ETH0";
+        netCom.EnableNetwork(mSuccess.Cancel(), mFailure.Cancel(),
+                             ByteSpan(reinterpret_cast<uint8_t *>(&magicNetworkEnableCode), sizeof(magicNetworkEnableCode)),
+                             breadcrumb, kCommandTimeoutMs);
+    }
+    break;
+    case CommissioningStage::kFindOperational: {
+#if CHIP_DEVICE_CONFIG_ENABLE_MDNS
+        ChipLogProgress(Controller, "Finding node on operational network");
+        Mdns::Resolver::Instance().ResolveNodeId(PeerId().SetFabricId(0).SetNodeId(device->GetDeviceId()),
+                                                 Inet::IPAddressType::kIPAddressType_Any);
+#endif
+    }
+    break;
+    case CommissioningStage::kSendComplete: {
+        // TODO this is actualy not correct - we must reconnect over CASE to send this command.
+        ChipLogProgress(Controller, "Calling commissioning complete");
+        GeneralCommissioningCluster genCom;
+        genCom.Associate(device, 0);
+        genCom.CommissioningComplete(mSuccess.Cancel(), mFailure.Cancel());
+    }
+    break;
+    case CommissioningStage::kCleanup:
+        ChipLogProgress(Controller, "Rendezvous cleanup");
+        mPairingSession.ToSerializable(device->GetPairing());
+        mSystemLayer->CancelTimer(OnSessionEstablishmentTimeoutCallback, this);
+
+        mPairedDevices.Insert(device->GetDeviceId());
+        mPairedDevicesUpdated = true;
+
+        // Note - This assumes storage is synchronous, the device must be in storage before we can cleanup
+        // the rendezvous session and mark pairing success
+        PersistDevice(device);
+        // Also persist the device list at this time
+        // This makes sure that a newly added device is immediately available
+        PersistDeviceList();
+        if (mPairingDelegate != nullptr)
+        {
+            mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess);
+        }
+        RendezvousCleanup(CHIP_NO_ERROR);
+        break;
+    case CommissioningStage::kSecurePairing:
+    case CommissioningStage::kError:
+        break;
+    }
+    mCommissioningStage = nextStage;
+}
 
 } // namespace Controller
 } // namespace chip

+ 46 - 7
src/controller/CHIPDeviceController.h

@@ -67,6 +67,12 @@ namespace Controller {
 constexpr uint16_t kNumMaxActiveDevices = 64;
 constexpr uint16_t kNumMaxPairedDevices = 128;
 
+// Raw functions for cluster callbacks
+typedef void (*BasicSuccessCallback)(void * context, uint16_t val);
+typedef void (*BasicFailureCallback)(void * context, uint8_t status);
+void BasicSuccess(void * context, uint16_t val);
+void BasicFailure(void * context, uint8_t status);
+
 struct ControllerInitParams
 {
     PersistentStorageDelegate * storageDelegate = nullptr;
@@ -83,6 +89,25 @@ struct ControllerInitParams
     OperationalCredentialsDelegate * operationalCredentialsDelegate = nullptr;
 };
 
+enum CommissioningStage : uint8_t
+{
+    kError,
+    kSecurePairing,
+    kArmFailsafe,
+    // kConfigTime,  // NOT YET IMPLEMENTED
+    // kConfigTimeZone,  // NOT YET IMPLEMENTED
+    // kConfigDST,  // NOT YET IMPLEMENTED
+    kConfigRegulatory,
+    kCheckCertificates,
+    kConfigACL,
+    kNetworkSetup,
+    kScanNetworks, // optional stage if network setup fails (not yet implemented)
+    kNetworkEnable,
+    kFindOperational,
+    kSendComplete,
+    kCleanup,
+};
+
 class DLL_EXPORT DevicePairingDelegate
 {
 public:
@@ -288,6 +313,13 @@ protected:
 
     uint16_t mNextKeyId = 0;
 
+#if CHIP_DEVICE_CONFIG_ENABLE_MDNS
+    //////////// ResolverDelegate Implementation ///////////////
+    void OnNodeIdResolved(const chip::Mdns::ResolvedNodeData & nodeData) override;
+    void OnNodeIdResolutionFailed(const chip::PeerId & peerId, CHIP_ERROR error) override;
+    void OnCommissionableNodeFound(const chip::Mdns::CommissionableNodeData & nodeData) override;
+#endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS
+
 private:
     //////////// ExchangeDelegate Implementation ///////////////
     void OnMessageReceived(Messaging::ExchangeContext * ec, const PacketHeader & packetHeader, const PayloadHeader & payloadHeader,
@@ -298,13 +330,6 @@ private:
     void OnNewConnection(SecureSessionHandle session, Messaging::ExchangeManager * mgr) override;
     void OnConnectionExpired(SecureSessionHandle session, Messaging::ExchangeManager * mgr) override;
 
-#if CHIP_DEVICE_CONFIG_ENABLE_MDNS
-    //////////// ResolverDelegate Implementation ///////////////
-    void OnNodeIdResolved(const chip::Mdns::ResolvedNodeData & nodeData) override;
-    void OnNodeIdResolutionFailed(const chip::PeerId & peerId, CHIP_ERROR error) override;
-    void OnCommissionableNodeFound(const chip::Mdns::CommissionableNodeData & nodeData) override;
-#endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS
-
     void ReleaseAllDevices();
 
     CHIP_ERROR LoadLocalCredentials(Transport::AdminPairingInfo * admin);
@@ -338,6 +363,7 @@ public:
  *   will be stored.
  */
 class DLL_EXPORT DeviceCommissioner : public DeviceController, public SessionEstablishmentDelegate
+
 {
 public:
     DeviceCommissioner();
@@ -397,6 +423,8 @@ public:
 
     void ReleaseDevice(Device * device) override;
 
+    void AdvanceCommissioningStage(CHIP_ERROR err);
+
 #if CONFIG_NETWORK_LAYER_BLE
     /**
      * @brief
@@ -438,6 +466,9 @@ public:
      */
     int GetMaxCommissionableNodesSupported() { return kMaxCommissionableNodes; }
 
+    void OnNodeIdResolved(const chip::Mdns::ResolvedNodeData & nodeData) override;
+    void OnNodeIdResolutionFailed(const chip::PeerId & peerId, CHIP_ERROR error) override;
+
 #endif
 
 private:
@@ -459,6 +490,8 @@ private:
        persist the device list */
     bool mPairedDevicesUpdated;
 
+    CommissioningStage mCommissioningStage = CommissioningStage::kSecurePairing;
+
     DeviceCommissionerRendezvousAdvertisementDelegate mRendezvousAdvDelegate;
 
     void PersistDeviceList();
@@ -534,6 +567,12 @@ private:
     CHIP_ERROR ProcessOpCSR(const ByteSpan & CSR, const ByteSpan & CSRNonce, const ByteSpan & VendorReserved1,
                             const ByteSpan & VendorReserved2, const ByteSpan & VendorReserved3, const ByteSpan & Signature);
 
+    // Cluster callbacks for advancing commissioning flows
+    Callback::Callback<BasicSuccessCallback> mSuccess;
+    Callback::Callback<BasicFailureCallback> mFailure;
+
+    CommissioningStage GetNextCommissioningStage();
+
     Callback::Callback<OperationalCredentialsClusterOpCSRResponseCallback> mOpCSRResponseCallback;
     Callback::Callback<OperationalCredentialsClusterOpCertResponseCallback> mOpCertResponseCallback;
     Callback::Callback<DefaultSuccessCallback> mRootCertResponseCallback;

+ 1 - 0
src/controller/data_model/BUILD.gn

@@ -22,4 +22,5 @@ chip_data_model("data_model") {
   zap_pregenerated_dir = "gen"
 
   use_default_client_callbacks = true
+  allow_circular_includes_from = [ "${chip_root}/src/controller" ]
 }

+ 11 - 0
src/include/platform/CHIPDeviceEvent.h

@@ -202,6 +202,11 @@ enum PublicEventTypes
      * Commissioning has completed either through timer expiry or by a call to the general commissioning cluster command.
      */
     kCommissioningComplete,
+
+    /**
+     *
+     */
+    kOperationalNetworkEnabled,
 };
 
 /**
@@ -411,6 +416,12 @@ struct ChipDeviceEvent final
         {
             CHIP_ERROR status;
         } CommissioningComplete;
+
+        struct
+        {
+            // TODO(cecille): This should just specify wifi or thread since we assume at most 1.
+            int network;
+        } OperationalNetwork;
     };
 
     void Clear() { memset(this, 0, sizeof(*this)); }

+ 2 - 0
src/include/platform/internal/DeviceControlServer.h

@@ -38,6 +38,8 @@ public:
     CHIP_ERROR CommissioningComplete();
     CHIP_ERROR SetRegulatoryConfig(uint8_t location, const char * countryCode, uint64_t breadcrumb);
 
+    CHIP_ERROR EnableNetworkForOperational(ByteSpan networkID);
+
     static DeviceControlServer & DeviceControlSvr();
 
 private:

+ 3 - 3
src/lib/mdns/Resolver_ImplMinimalMdns.cpp

@@ -506,9 +506,9 @@ CHIP_ERROR MinMdnsResolver::ResolveNodeId(const PeerId & peerId, Inet::IPAddress
         Query query(instanceQName);
 
         query
-            .SetClass(QClass::IN)      //
-            .SetType(QType::ANY)       //
-            .SetAnswerViaUnicast(true) //
+            .SetClass(QClass::IN)       //
+            .SetType(QType::ANY)        //
+            .SetAnswerViaUnicast(false) //
             ;
 
         // NOTE: type above is NOT A or AAAA because the name searched for is

+ 1 - 0
src/platform/BUILD.gn

@@ -72,6 +72,7 @@ if (chip_device_platform != "none") {
       "CHIP_ENABLE_OPENTHREAD=${chip_enable_openthread}",
       "CHIP_WITH_GIO=${chip_with_gio}",
       "OPENTHREAD_CONFIG_ENABLE_TOBLE=false",
+      "CONFIG_USE_CLUSTERS_FOR_IP_COMMISSIONING=${chip_use_clusters_for_ip_commissioning}",
     ]
 
     if (chip_device_platform == "linux" || chip_device_platform == "darwin") {

+ 13 - 1
src/platform/DeviceControlServer.cpp

@@ -91,7 +91,19 @@ exit:
         ChipLogError(DeviceLayer, "SetRegulatoryConfig failed with error: %s", ErrorStr(err));
     }
 
-    return err;
+    // TODO(cecille): This command fails on ESP32, but it's blocking IP cluster-based commissioning so for now just return a success
+    // status.
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR DeviceControlServer::EnableNetworkForOperational(ByteSpan networkID)
+{
+    ChipDeviceEvent event;
+    event.Type = DeviceEventType::kOperationalNetworkEnabled;
+    // TODO(cecille): This should be some way to specify thread or wifi.
+    event.OperationalNetwork.network = 0;
+    PlatformMgr().DispatchEvent(&event);
+    return CHIP_NO_ERROR;
 }
 
 } // namespace Internal

+ 3 - 0
src/platform/device.gni

@@ -56,6 +56,9 @@ declare_args() {
   } else {
     chip_mdns = "none"
   }
+
+  # Enables full cluster-based commissioning for already-on-network devices.
+  chip_use_clusters_for_ip_commissioning = false
 }
 
 _chip_device_layer = "none"