Fix 32957 : Matter TV: commissioner passcode flow does not accept CommissionerPasscodeReady flag (#32958)

* Temp fix for 32875

* Clarify comments

* Fix commissioner passcode flow

* Make test shell commands for commissionerpasscode always trigger passcode dialog

* Make commissioner passcode commissioning work for casting app using shell commands

* Fix CI, address comments, wire CDC callback to CastingServer

* Fix CI

* Fix CI

* Restyled by clang-format (#32968)

Co-authored-by: Restyled.io <commits@restyled.io>

* Fix CI

---------

Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/platform/linux/CommissionerMain.cpp b/examples/platform/linux/CommissionerMain.cpp
index d4155ac..3ffac89 100644
--- a/examples/platform/linux/CommissionerMain.cpp
+++ b/examples/platform/linux/CommissionerMain.cpp
@@ -277,6 +277,13 @@
         break;
     case DevicePairingDelegate::Status::SecurePairingFailed:
         ChipLogError(AppServer, "Secure Pairing Failed");
+#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
+        CommissionerDiscoveryController * cdc = GetCommissionerDiscoveryController();
+        if (cdc != nullptr)
+        {
+            cdc->CommissioningFailed(CHIP_ERROR_CONNECTION_ABORTED);
+        }
+#endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
         break;
     }
 }
diff --git a/examples/tv-casting-app/linux/CastingShellCommands.cpp b/examples/tv-casting-app/linux/CastingShellCommands.cpp
index bf20ccd..e82c049 100644
--- a/examples/tv-casting-app/linux/CastingShellCommands.cpp
+++ b/examples/tv-casting-app/linux/CastingShellCommands.cpp
@@ -24,6 +24,7 @@
 #include "CastingServer.h"
 #include "CastingUtils.h"
 #include "app/clusters/bindings/BindingManager.h"
+#include <CommissionableInit.h>
 #include <inttypes.h>
 #include <lib/core/CHIPCore.h>
 #include <lib/shell/Commands.h>
@@ -34,6 +35,13 @@
 #include <lib/support/CodeUtils.h>
 #include <platform/CHIPDeviceLayer.h>
 
+using namespace chip;
+using namespace chip::DeviceLayer;
+
+namespace {
+LinuxCommissionableDataProvider gCommissionableDataProvider;
+}
+
 namespace chip {
 namespace Shell {
 
@@ -120,6 +128,17 @@
         int index = (int) strtol(argv[1], &eptr, 10);
         return RequestCommissioning(index);
     }
+    if (strcmp(argv[0], "setusecommissionerpasscode") == 0)
+    {
+        ChipLogProgress(DeviceLayer, "setusecommissionerpasscode");
+        if (argc < 2)
+        {
+            return PrintAllCommands();
+        }
+        char * eptr;
+        int useCP = (int) strtol(argv[1], &eptr, 10);
+        CastingServer::GetInstance()->SetCommissionerPasscodeEnabled(useCP == 1);
+    }
     if (strcmp(argv[0], "launch") == 0)
     {
         ChipLogProgress(DeviceLayer, "launch");
@@ -179,6 +198,8 @@
 
         Protocols::UserDirectedCommissioning::IdentificationDeclaration id;
         id.SetCommissionerPasscode(true);
+        id.SetVendorId(1244); // set non-standard vid-pid to prevent dummy content apps from returning a passcode
+        id.SetProductId(2234);
         if (argc > 3)
         {
             id.SetCommissionerPasscodeReady(strcmp(argv[3], "t") == 0);
@@ -202,6 +223,20 @@
         return Server::GetInstance().SendUserDirectedCommissioningRequest(chip::Transport::PeerAddress::UDP(commissioner, port),
                                                                           id);
     }
+    if (strcmp(argv[0], "setcommissionerpasscode") == 0)
+    {
+
+        char * eptr;
+        uint32_t passcode                                      = (uint32_t) strtol(argv[1], &eptr, 10);
+        LinuxDeviceOptions::GetInstance().payload.setUpPINCode = passcode;
+
+        VerifyOrDie(chip::examples::InitCommissionableDataProvider(gCommissionableDataProvider,
+                                                                   LinuxDeviceOptions::GetInstance()) == CHIP_NO_ERROR);
+
+        DeviceLayer::SetCommissionableDataProvider(&gCommissionableDataProvider);
+
+        CastingServer::GetInstance()->SetCommissionerPasscodeReady();
+    }
     if (strcmp(argv[0], "testudc") == 0)
     {
         char * eptr;
diff --git a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
index d64f504..49eb65d 100644
--- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
+++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
@@ -50,6 +50,10 @@
  *  as a singleton and is to be used across Linux, Android and iOS.
  */
 class CastingServer : public AppDelegate
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    ,
+                      chip::Protocols::UserDirectedCommissioning::CommissionerDeclarationHandler
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
 {
 public:
     CastingServer(CastingServer & other)  = delete;
@@ -61,6 +65,13 @@
     CHIP_ERROR InitBindingHandlers();
     void InitAppDelegation();
 
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    void SetCommissionerPasscodeEnabled(bool enabled) { mUdcCommissionerPasscodeEnabled = enabled; };
+    void SetCommissionerPasscodeReady() { mUdcCommissionerPasscodeReady = true; };
+    void OnCommissionerDeclarationMessage(const chip::Transport::PeerAddress & source,
+                                          chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) override;
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+
     CHIP_ERROR DiscoverCommissioners(chip::Controller::DeviceDiscoveryDelegate * deviceDiscoveryDelegate = nullptr);
     const chip::Dnssd::DiscoveredNodeData *
     GetDiscoveredCommissioner(int index, chip::Optional<TargetVideoPlayerInfo *> & outAssociatedConnectableVideoPlayer);
@@ -470,6 +481,12 @@
     PersistenceManager mPersistenceManager;
     bool mInited        = false;
     bool mUdcInProgress = false;
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    bool mUdcCommissionerPasscodeEnabled                                                           = false;
+    bool mUdcCommissionerPasscodeReady                                                             = false;
+    char mUdcCommissionerPasscodeInstanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1] = "";
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+
     chip::Dnssd::DiscoveredNodeData mStrNodeDataList[kMaxCachedVideoPlayers];
     TargetVideoPlayerInfo mActiveTargetVideoPlayerInfo;
     TargetVideoPlayerInfo mCachedTargetVideoPlayerInfo[kMaxCachedVideoPlayers];
diff --git a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
index b98647d..3c77048 100644
--- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
+++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
@@ -20,6 +20,7 @@
 #include "ConversionUtils.h"
 
 #include "app/clusters/bindings/BindingManager.h"
+#include <app/server/Dnssd.h>
 
 using namespace chip;
 using namespace chip::Controller;
@@ -67,6 +68,10 @@
     // Add callback to send Content casting commands after commissioning completes
     ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0));
 
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    Server::GetInstance().GetUserDirectedCommissioningClient()->SetCommissionerDeclarationHandler(this);
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+
     mInited = true;
     return CHIP_NO_ERROR;
 }
@@ -189,10 +194,40 @@
 }
 
 #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+void CastingServer::OnCommissionerDeclarationMessage(const chip::Transport::PeerAddress & source,
+                                                     chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd)
+{
+    ChipLogProgress(AppServer, "CastingServer::OnCommissionerDeclarationMessage");
+    // TODO: call a mCommissioningCallbacks
+}
+
 CHIP_ERROR CastingServer::SendUserDirectedCommissioningRequest(chip::Transport::PeerAddress commissioner)
 {
     // TODO: expose options to the higher layer
     Protocols::UserDirectedCommissioning::IdentificationDeclaration id;
+    if (mUdcCommissionerPasscodeEnabled)
+    {
+        id.SetCommissionerPasscode(true);
+        if (mUdcCommissionerPasscodeReady)
+        {
+            id.SetCommissionerPasscodeReady(true);
+            id.SetInstanceName(mUdcCommissionerPasscodeInstanceName);
+            mUdcCommissionerPasscodeReady = false;
+        }
+        else
+        {
+            CHIP_ERROR err = app::DnssdServer::Instance().GetCommissionableInstanceName(
+                mUdcCommissionerPasscodeInstanceName, sizeof(mUdcCommissionerPasscodeInstanceName));
+            if (err != CHIP_NO_ERROR)
+            {
+                ChipLogError(AppServer, "Failed to get mdns instance name error: %" CHIP_ERROR_FORMAT, err.Format());
+            }
+            else
+            {
+                id.SetInstanceName(mUdcCommissionerPasscodeInstanceName);
+            }
+        }
+    }
     return Server::GetInstance().SendUserDirectedCommissioningRequest(commissioner, id);
 }
 
diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp
index a4d874f..4df133c 100644
--- a/src/controller/CommissionerDiscoveryController.cpp
+++ b/src/controller/CommissionerDiscoveryController.cpp
@@ -61,54 +61,99 @@
     ResetState();
 }
 
+void CommissionerDiscoveryController::OnCancel(UDCClientState state)
+{
+    if (mReady)
+    {
+        // if state was ready for a new session,
+        // then we have lost our discovery controller context and can't perform the commissioning request
+        ChipLogDetail(Controller, "CommissionerDiscoveryController::OnCancel received when no current instance.");
+        return;
+    }
+
+    if (strncmp(mCurrentInstance, state.GetInstanceName(), sizeof(mCurrentInstance)) != 0)
+    {
+        // if the instance doesn't match the one in our discovery controller context,
+        // then we can't perform the commissioning request
+        ChipLogDetail(Controller, "CommissionerDiscoveryController::OnCancel received mismatched instance. Current instance=%s",
+                      mCurrentInstance);
+        return;
+    }
+
+    ChipLogDetail(Controller, "------PROMPT USER: %s cancelled commissioning [" ChipLogFormatMEI "," ChipLogFormatMEI ",%s]",
+                  state.GetDeviceName(), ChipLogValueMEI(state.GetVendorId()), ChipLogValueMEI(state.GetProductId()),
+                  state.GetInstanceName());
+    if (mUserPrompter != nullptr)
+    {
+        mUserPrompter->HidePromptsOnCancel(state.GetVendorId(), state.GetProductId(), state.GetDeviceName());
+    }
+    return;
+}
+
+void CommissionerDiscoveryController::OnCommissionerPasscodeReady(UDCClientState state)
+{
+    if (mReady)
+    {
+        // if state was ready for a new session,
+        // then we have lost our discovery controller context and can't perform the commissioning request
+        ChipLogDetail(Controller,
+                      "CommissionerDiscoveryController::OnCommissionerPasscodeReady received when no current instance.");
+        return;
+    }
+
+    if (strncmp(mCurrentInstance, state.GetInstanceName(), sizeof(mCurrentInstance)) != 0)
+    {
+        // if the instance doesn't match the one in our discovery controller context,
+        // then we can't perform the commissioning request
+        ChipLogDetail(
+            Controller,
+            "CommissionerDiscoveryController::OnCommissionerPasscodeReady received mismatched instance. Current instance=%s",
+            mCurrentInstance);
+        return;
+    }
+
+    if (state.GetCdPort() == 0)
+    {
+        ChipLogDetail(Controller, "CommissionerDiscoveryController::OnCommissionerPasscodeReady no port");
+        return;
+    }
+
+    uint32_t passcode = state.GetCachedCommissionerPasscode();
+    if (passcode == 0)
+    {
+        ChipLogError(AppServer, "On UDC: commissioner passcode ready but no passcode");
+        CommissionerDeclaration cd;
+        cd.SetErrorCode(CommissionerDeclaration::CdError::kUnexpectedCommissionerPasscodeReady);
+
+        if (mUdcServer == nullptr)
+        {
+            ChipLogError(AppServer, "On UDC: no udc server");
+            return;
+        }
+        mUdcServer->SendCDCMessage(cd, Transport::PeerAddress::UDP(state.GetPeerAddress().GetIPAddress(), state.GetCdPort()));
+        return;
+    }
+    else
+    {
+        // can only get here is ok() has already been called
+        ChipLogDetail(AppServer, "On UDC: commissioner passcode ready with passcode - commissioning");
+
+        // start commissioning using the cached passcode
+        CommissionWithPasscode(passcode);
+        return;
+    }
+}
+
 void CommissionerDiscoveryController::OnUserDirectedCommissioningRequest(UDCClientState state)
 {
     ValidateSession();
 
     if (!mReady)
     {
+        // we must currently have discovery controller context (a UDC prompt under way)
         ChipLogDetail(Controller, "CommissionerDiscoveryController not ready. Current instance=%s", mCurrentInstance);
         return;
     }
-    // first check if this is a cancel
-    if (state.GetCancelPasscode())
-    {
-        ChipLogDetail(Controller, "------PROMPT USER: %s cancelled commissioning [" ChipLogFormatMEI "," ChipLogFormatMEI ",%s]",
-                      state.GetDeviceName(), ChipLogValueMEI(state.GetVendorId()), ChipLogValueMEI(state.GetProductId()),
-                      state.GetInstanceName());
-        if (mUserPrompter != nullptr)
-        {
-            mUserPrompter->HidePromptsOnCancel(state.GetVendorId(), state.GetProductId(), state.GetDeviceName());
-        }
-        return;
-    }
-    if (state.GetCommissionerPasscodeReady() && state.GetCdPort() != 0)
-    {
-        uint32_t passcode = state.GetCachedCommissionerPasscode();
-        if (!mReady || passcode == 0)
-        {
-            ChipLogError(AppServer, "On UDC: commissioner passcode ready but no passcode");
-            CommissionerDeclaration cd;
-            cd.SetErrorCode(CommissionerDeclaration::CdError::kUnexpectedCommissionerPasscodeReady);
-
-            if (mUdcServer == nullptr)
-            {
-                ChipLogError(AppServer, "On UDC: no udc server");
-                return;
-            }
-            mUdcServer->SendCDCMessage(cd, Transport::PeerAddress::UDP(state.GetPeerAddress().GetIPAddress(), state.GetCdPort()));
-            return;
-        }
-        else
-        {
-            // can only get here is ok() has already been called
-            ChipLogDetail(AppServer, "On UDC: commissioner passcode ready with passcode - commissioning");
-
-            // start commissioning using the cached passcode
-            CommissionWithPasscode(passcode);
-            return;
-        }
-    }
 
     mReady = false;
     Platform::CopyString(mCurrentInstance, state.GetInstanceName());
@@ -398,6 +443,7 @@
                 return;
             }
             client->SetCachedCommissionerPasscode(passcode);
+            client->SetUDCClientProcessingState(UDCClientProcessingState::kWaitingForCommissionerPasscodeReady);
 
             CommissionerDeclaration cd;
             cd.SetCommissionerPasscode(true);
@@ -482,7 +528,7 @@
 
     if (!mPendingConsent)
     {
-        ChipLogError(AppServer, "UX Cancel: no current instance");
+        ChipLogError(AppServer, "UX CommissionWithPasscode: no current instance");
         return;
     }
     if (mUdcServer == nullptr)
@@ -493,7 +539,7 @@
     UDCClientState * client = mUdcServer->GetUDCClients().FindUDCClientState(mCurrentInstance);
     if (client == nullptr)
     {
-        ChipLogError(AppServer, "UX Ok: could not find instance=%s", mCurrentInstance);
+        ChipLogError(AppServer, "UX CommissionWithPasscode: could not find instance=%s", mCurrentInstance);
         return;
     }
     // state needs to be either kPromptingUser or kObtainingOnboardingPayload
@@ -566,13 +612,18 @@
     }
     if (mUdcServer == nullptr)
     {
-        ChipLogError(AppServer, "UX Cancel: no udc server");
+        ChipLogError(AppServer, "UX CommissioningFailed: no udc server");
         return;
     }
     UDCClientState * client = mUdcServer->GetUDCClients().FindUDCClientState(mCurrentInstance);
-    if (client == nullptr || client->GetUDCClientProcessingState() != UDCClientProcessingState::kCommissioningNode)
+    if (client == nullptr)
     {
-        ChipLogError(AppServer, "UX Cancel: invalid state for cancel");
+        ChipLogError(AppServer, "UX CommissioningFailed: no client");
+        return;
+    }
+    if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kCommissioningNode)
+    {
+        ChipLogError(AppServer, "UX CommissioningFailed: invalid state");
         return;
     }
     client->SetUDCClientProcessingState(UDCClientProcessingState::kCommissioningFailed);
diff --git a/src/controller/CommissionerDiscoveryController.h b/src/controller/CommissionerDiscoveryController.h
index c4c90c3..521fe5c 100644
--- a/src/controller/CommissionerDiscoveryController.h
+++ b/src/controller/CommissionerDiscoveryController.h
@@ -276,6 +276,24 @@
     void OnUserDirectedCommissioningRequest(UDCClientState state) override;
 
     /**
+     * UserConfirmationProvider callback.
+     *
+     * Notification that a Cancel UDC protocol message was received.
+     *
+     * This code will call the registered UserPrompter's HidePromptsOnCancel
+     */
+    void OnCancel(UDCClientState state) override;
+
+    /**
+     * UserConfirmationProvider callback.
+     *
+     * Notification that a CommissionerPasscodeReady UDC protocol message was received.
+     *
+     * This code will trigger the Commissioner to begin commissioning
+     */
+    void OnCommissionerPasscodeReady(UDCClientState state) override;
+
+    /**
      * This method should be called after the user has given consent for commissioning of the client
      * indicated in the UserPrompter's PromptForCommissionOKPermission callback
      */
diff --git a/src/protocols/user_directed_commissioning/UDCClientState.h b/src/protocols/user_directed_commissioning/UDCClientState.h
index e87e303..259f6ee 100644
--- a/src/protocols/user_directed_commissioning/UDCClientState.h
+++ b/src/protocols/user_directed_commissioning/UDCClientState.h
@@ -37,6 +37,7 @@
     kPromptingUser,
     kUserDeclined,
     kObtainingOnboardingPayload,
+    kWaitingForCommissionerPasscodeReady,
     kCommissioningNode,
     kCommissioningFailed,
 };
diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h b/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h
index 7f22c92..86fd4d5 100644
--- a/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h
+++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioning.h
@@ -448,6 +448,30 @@
      */
     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;
 };
 
@@ -625,6 +649,9 @@
     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
diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
index f63a10c..b96c456 100644
--- a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
+++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
@@ -61,9 +61,25 @@
     IdentificationDeclaration id;
     id.ReadPayload(udcPayload, sizeof(udcPayload));
 
-    char * instanceName = (char *) id.GetInstanceName();
+    if (id.GetCancelPasscode())
+    {
+        HandleUDCCancel(id);
+        return;
+    }
 
-    ChipLogProgress(AppServer, "UDC instance=%s ", id.GetInstanceName());
+    if (id.GetCommissionerPasscodeReady())
+    {
+        HandleUDCCommissionerPasscodeReady(id);
+        return;
+    }
+
+    HandleNewUDC(source, id);
+}
+
+void UserDirectedCommissioningServer::HandleNewUDC(const Transport::PeerAddress & source, IdentificationDeclaration & id)
+{
+    char * instanceName = (char *) id.GetInstanceName();
+    ChipLogProgress(AppServer, "HandleNewUDC instance=%s ", id.GetInstanceName());
 
     UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
     if (client == nullptr)
@@ -109,10 +125,60 @@
             ChipLogError(AppServer, "UserDirectedCommissioningServer::OnMessageReceived no mInstanceNameResolver registered");
         }
     }
-
     mUdcClients.MarkUDCClientActive(client);
 }
 
+void UserDirectedCommissioningServer::HandleUDCCancel(IdentificationDeclaration & id)
+{
+    char * instanceName = (char *) id.GetInstanceName();
+    ChipLogProgress(AppServer, "HandleUDCCancel instance=%s ", id.GetInstanceName());
+
+    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
+    if (client == nullptr)
+    {
+        ChipLogProgress(AppServer, "UDC no matching instance found");
+        return;
+    }
+    id.DebugLog();
+    mUdcClients.MarkUDCClientActive(client);
+
+    // Call the registered mUserConfirmationProvider, if any.
+    if (mUserConfirmationProvider != nullptr)
+    {
+        mUserConfirmationProvider->OnCancel(*client);
+    }
+
+    // reset this entry so that the client can try again without waiting an hour
+    client->Reset();
+}
+
+void UserDirectedCommissioningServer::HandleUDCCommissionerPasscodeReady(IdentificationDeclaration & id)
+{
+    char * instanceName = (char *) id.GetInstanceName();
+    ChipLogProgress(AppServer, "HandleUDCCommissionerPasscodeReady instance=%s ", id.GetInstanceName());
+
+    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
+    if (client == nullptr)
+    {
+        ChipLogProgress(AppServer, "UDC no matching instance found");
+        return;
+    }
+    if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kWaitingForCommissionerPasscodeReady)
+    {
+        ChipLogProgress(AppServer, "UDC instance not in waiting for passcode ready state");
+        return;
+    }
+    id.DebugLog();
+    mUdcClients.MarkUDCClientActive(client);
+    client->SetUDCClientProcessingState(UDCClientProcessingState::kObtainingOnboardingPayload);
+
+    // Call the registered mUserConfirmationProvider, if any.
+    if (mUserConfirmationProvider != nullptr)
+    {
+        mUserConfirmationProvider->OnCommissionerPasscodeReady(*client);
+    }
+}
+
 CHIP_ERROR UserDirectedCommissioningServer::SendCDCMessage(CommissionerDeclaration cd, chip::Transport::PeerAddress peerAddress)
 {
     if (mTransportMgr == nullptr)
diff --git a/src/protocols/user_directed_commissioning/tests/TestUdcMessages.cpp b/src/protocols/user_directed_commissioning/tests/TestUdcMessages.cpp
index 9c171b5..a62aa52 100644
--- a/src/protocols/user_directed_commissioning/tests/TestUdcMessages.cpp
+++ b/src/protocols/user_directed_commissioning/tests/TestUdcMessages.cpp
@@ -37,6 +37,10 @@
         mState                                    = state;
     }
 
+    void OnCancel(UDCClientState state) {}
+
+    void OnCommissionerPasscodeReady(UDCClientState state) {}
+
     void FindCommissionableNode(char * instanceName)
     {
         mFindCommissionableNodeCalled = true;