Linux tv-casting-app v1.3 IdentificationDeclaration message (#33283)

diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java
index a706780..65d6705 100644
--- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java
+++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java
@@ -327,8 +327,9 @@
               TAG,
               "OnClickListener.onClick() called for CastingPlayer with deviceId: "
                   + castingPlayer.getDeviceId());
-          DiscoveryExampleFragment.Callback callback1 = (DiscoveryExampleFragment.Callback) context;
-          callback1.handleConnectionButtonClicked(castingPlayer);
+          DiscoveryExampleFragment.Callback onClickCallback =
+              (DiscoveryExampleFragment.Callback) context;
+          onClickCallback.handleConnectionButtonClicked(castingPlayer);
         };
     playerDescription.setOnClickListener(clickListener);
     return view;
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp
index caa64b4..1c4a949 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp
@@ -53,9 +53,14 @@
     CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz);
     VerifyOrReturnValue(castingPlayer != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT));
 
+    matter::casting::core::IdentificationDeclarationOptions idOptions;
+
+    // TODO: In the following PRs. Replace EndpointFilter Java class with IdentificationDeclarationOptions Java class.
     matter::casting::core::EndpointFilter desiredEndpointFilter;
     if (desiredEndpointFilterJavaObject != nullptr)
     {
+        chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo;
+
         // Convert the EndpointFilter Java class to a C++ EndpointFilter
         jclass endpointFilterJavaClass = env->GetObjectClass(desiredEndpointFilterJavaObject);
         jfieldID vendorIdFieldId       = env->GetFieldID(endpointFilterJavaClass, "vendorId", "Ljava/lang/Integer;");
@@ -66,20 +71,28 @@
         // "Ljava/util/List;");
 
         // Value of 0 means unspecified
-        desiredEndpointFilter.vendorId  = vendorIdIntegerObject != nullptr
+        targetAppInfo.vendorId  = vendorIdIntegerObject != nullptr
              ? static_cast<uint16_t>(env->CallIntMethod(
                   vendorIdIntegerObject, env->GetMethodID(env->GetObjectClass(vendorIdIntegerObject), "intValue", "()I")))
              : 0;
-        desiredEndpointFilter.productId = productIdIntegerObject != nullptr
+        targetAppInfo.productId = productIdIntegerObject != nullptr
             ? static_cast<uint16_t>(env->CallIntMethod(
                   productIdIntegerObject, env->GetMethodID(env->GetObjectClass(productIdIntegerObject), "intValue", "()I")))
             : 0;
-        // TODO: In following PRs. Translate the Java requiredDeviceTypes list to a C++ requiredDeviceTypes vector. For now we're
-        // passing an empty list of DeviceTypeStruct.
+
+        CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo);
+        if (result != CHIP_NO_ERROR)
+        {
+            ChipLogError(AppServer,
+                         "MatterCastingPlayer-JNI::verifyOrEstablishConnection() failed to add targetAppInfo: %" CHIP_ERROR_FORMAT,
+                         result.Format());
+        }
     }
 
     MatterCastingPlayerJNIMgr().mConnectionSuccessHandler.SetUp(env, jSuccessCallback);
     MatterCastingPlayerJNIMgr().mConnectionFailureHandler.SetUp(env, jFailureCallback);
+
+    // TODO: In the following PRs. Add optional CommissionerDeclarationHandler callback parameter.
     castingPlayer->VerifyOrEstablishConnection(
         [](CHIP_ERROR err, CastingPlayer * playerPtr) {
             ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::verifyOrEstablishConnection() ConnectCallback called");
@@ -96,7 +109,7 @@
                 MatterCastingPlayerJNIMgr().mConnectionFailureHandler.Handle(err);
             }
         },
-        static_cast<unsigned long long int>(commissioningWindowTimeoutSec), desiredEndpointFilter);
+        static_cast<unsigned long long int>(commissioningWindowTimeoutSec), idOptions);
     return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR);
 }
 
diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm
index b69bbfb..3bc7223 100644
--- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm
+++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm
@@ -52,19 +52,29 @@
 
     dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue];
     dispatch_sync(workQueue, ^{
+        matter::casting::core::IdentificationDeclarationOptions idOptions;
+
+        // TODO: In the following PRs. Replace EndpointFilter objC class with IdentificationDeclarationOptions objC class.
         __block matter::casting::core::EndpointFilter cppDesiredEndpointFilter;
         if (desiredEndpointFilter != nil) {
-            cppDesiredEndpointFilter.vendorId = desiredEndpointFilter.vendorId;
-            cppDesiredEndpointFilter.productId = desiredEndpointFilter.productId;
+            chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo;
+            targetAppInfo.vendorId = desiredEndpointFilter.vendorId;
+            targetAppInfo.productId = desiredEndpointFilter.productId;
+
+            CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo);
+            if (result != CHIP_NO_ERROR) {
+                ChipLogError(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCompletionBlock failed to add targetAppInfo: %" CHIP_ERROR_FORMAT, result.Format());
+            }
         }
 
+        // TODO: In the following PRs. Add optional CommissionerDeclarationHandler callback parameter.
         _cppCastingPlayer->VerifyOrEstablishConnection(
             [completion](CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) {
                 dispatch_queue_t clientQueue = [[MCCastingApp getSharedInstance] getClientQueue];
                 dispatch_async(clientQueue, ^{
                     completion(err == CHIP_NO_ERROR ? nil : [MCErrorUtils NSErrorFromChipError:err]);
                 });
-            }, timeout, cppDesiredEndpointFilter);
+            }, timeout, idOptions);
     });
 }
 
diff --git a/examples/tv-casting-app/linux/simple-app-helper.cpp b/examples/tv-casting-app/linux/simple-app-helper.cpp
index de8c255..ceb6623 100644
--- a/examples/tv-casting-app/linux/simple-app-helper.cpp
+++ b/examples/tv-casting-app/linux/simple-app-helper.cpp
@@ -49,8 +49,10 @@
     if (commissionersCount == 0)
     {
         ChipLogProgress(AppServer, "Select discovered Casting Player (start index = 0) to request commissioning");
+        ChipLogProgress(AppServer, "Include the cgp flag to attempt the Commissioner-Generated Passcode commissioning flow");
 
-        ChipLogProgress(AppServer, "Example: cast request 0");
+        ChipLogProgress(AppServer, "Example1 Commissionee Passcode: cast request 0");
+        ChipLogProgress(AppServer, "Example2 Commissioner Passcode: cast request 0 cgp");
     }
     ChipLogProgress(AppServer, "Discovered CastingPlayer #%d", commissionersCount);
     ++commissionersCount;
@@ -170,10 +172,12 @@
 {
     VerifyOrReturn(err == CHIP_NO_ERROR,
                    ChipLogProgress(AppServer,
-                                   "ConnectionHandler: Failed to connect to CastingPlayer(ID: %s) with err %" CHIP_ERROR_FORMAT,
+                                   "ConnectionHandler(): Failed to connect to CastingPlayer(ID: %s) with err %" CHIP_ERROR_FORMAT,
                                    castingPlayer->GetId(), err.Format()));
 
-    ChipLogProgress(AppServer, "ConnectionHandler: Successfully connected to CastingPlayer(ID: %s)", castingPlayer->GetId());
+    ChipLogProgress(AppServer, "ConnectionHandler(): Successfully connected to CastingPlayer(ID: %s)", castingPlayer->GetId());
+    ChipLogProgress(AppServer, "ConnectionHandler(): Triggering demo interactions with CastingPlayer(ID: %s)",
+                    castingPlayer->GetId());
 
     std::vector<matter::casting::memory::Strong<matter::casting::core::Endpoint>> endpoints = castingPlayer->GetEndpoints();
     // Find the desired Endpoint and auto-trigger some Matter Casting demo interactions
@@ -219,18 +223,18 @@
     }
     if (strcmp(argv[0], "discover") == 0)
     {
-        ChipLogProgress(AppServer, "discover");
+        ChipLogProgress(AppServer, "CommandHandler() discover");
 
         return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType);
     }
     if (strcmp(argv[0], "stop-discovery") == 0)
     {
-        ChipLogProgress(AppServer, "stop-discovery");
+        ChipLogProgress(AppServer, "CommandHandler() stop-discovery");
         return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StopDiscovery();
     }
     if (strcmp(argv[0], "request") == 0)
     {
-        ChipLogProgress(AppServer, "request");
+        ChipLogProgress(AppServer, "CommandHandler() request");
         if (argc < 2)
         {
             return PrintAllCommands();
@@ -243,10 +247,40 @@
                             ChipLogError(AppServer, "Invalid casting player index provided: %lu", index));
         std::shared_ptr<matter::casting::core::CastingPlayer> targetCastingPlayer = castingPlayers.at(index);
 
-        matter::casting::core::EndpointFilter desiredEndpointFilter;
-        desiredEndpointFilter.vendorId = kDesiredEndpointVendorId;
+        matter::casting::core::IdentificationDeclarationOptions idOptions;
+        if (argc == 3)
+        {
+            if (strcmp(argv[2], "cgp") == 0)
+            {
+                // Attempt Commissioner-Generated Passcode (cgp) commissioning flow only if the CastingPlayer indicates support for
+                // it.
+                if (targetCastingPlayer->GetSupportsCommissionerGeneratedPasscode())
+                {
+                    ChipLogProgress(
+                        AppServer,
+                        "CommandHandler() request %lu cgp. Attempting the Commissioner-Generated Passcode commissioning flow",
+                        index);
+                    idOptions.mCommissionerPasscode = true;
+                }
+                else
+                {
+                    ChipLogError(AppServer,
+                                 "CommandHandler() request %lu cgp. Selected CastingPLayer does not support the "
+                                 "Commissioner-Generated Passcode commissioning flow",
+                                 index);
+                }
+            }
+        }
+        chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo;
+        targetAppInfo.vendorId = kDesiredEndpointVendorId;
+        CHIP_ERROR result      = idOptions.addTargetAppInfo(targetAppInfo);
+        if (result != CHIP_NO_ERROR)
+        {
+            ChipLogError(AppServer, "CommandHandler() request, failed to add targetAppInfo: %" CHIP_ERROR_FORMAT, result.Format());
+        }
+
         targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, matter::casting::core::kCommissioningWindowTimeoutSec,
-                                                         desiredEndpointFilter);
+                                                         idOptions);
         return CHIP_NO_ERROR;
     }
     if (strcmp(argv[0], "print-bindings") == 0)
@@ -280,8 +314,12 @@
         "  delete-fabric <index>     Delete a fabric from the casting client's fabric store. Usage: cast delete-fabric 1\r\n");
     streamer_printf(sout, "  discover             Discover Casting Players. Usage: cast discover\r\n");
     streamer_printf(sout, "  stop-discovery       Stop Discovery of Casting Players. Usage: cast stop-discovery\r\n");
-    streamer_printf(
-        sout, "  request <index>      Request connecting to discovered Casting Player with [index]. Usage: cast request 0\r\n");
+    streamer_printf(sout,
+                    "  request <index>      Request connecting to discovered Casting Player with [index] using the "
+                    "Commissionee-Generated Passcode commissioning flow. Usage: cast request 0\r\n");
+    streamer_printf(sout,
+                    "  request <index> cgp  Request connecting to discovered Casting Player with [index] using the "
+                    "Commissioner-Generated Passcode commissioning flow. Usage: cast request 0 cgp\r\n");
     streamer_printf(sout, "\r\n");
 
     return CHIP_NO_ERROR;
diff --git a/examples/tv-casting-app/linux/simple-app-helper.h b/examples/tv-casting-app/linux/simple-app-helper.h
index b1dbbe6..f8826b5 100644
--- a/examples/tv-casting-app/linux/simple-app-helper.h
+++ b/examples/tv-casting-app/linux/simple-app-helper.h
@@ -20,6 +20,7 @@
 
 #include "core/CastingPlayer.h"
 #include "core/CastingPlayerDiscovery.h"
+#include "core/IdentificationDeclarationOptions.h"
 #include "core/Types.h"
 #include <platform/CHIPDeviceLayer.h>
 
diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn
index 72f14fb..a58d272 100644
--- a/examples/tv-casting-app/tv-casting-common/BUILD.gn
+++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn
@@ -106,6 +106,7 @@
     "core/CastingPlayerDiscovery.cpp",
     "core/CastingPlayerDiscovery.h",
     "core/Command.h",
+    "core/CommissionerDeclarationHandler.cpp",
     "core/Endpoint.cpp",
     "core/Endpoint.h",
     "core/Types.h",
diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp
index f786591..7e00013 100644
--- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp
+++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp
@@ -18,6 +18,7 @@
 
 #include "CastingApp.h"
 
+#include "CommissionerDeclarationHandler.h"
 #include "support/CastingStore.h"
 #include "support/ChipDeviceEventHandler.h"
 
@@ -100,6 +101,9 @@
     // reconnect (or verify connection) to the CastingPlayer that the app was connected to before being stopped, if any
     if (CastingPlayer::GetTargetCastingPlayer() != nullptr)
     {
+        ChipLogProgress(
+            Discovery,
+            "CastingApp::Start() calling VerifyOrEstablishConnection() to reconnect (or verify connection) to a CastingPlayer");
         CastingPlayer::GetTargetCastingPlayer()->VerifyOrEstablishConnection(
             [](CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) {
                 if (err != CHIP_NO_ERROR)
@@ -136,6 +140,12 @@
     // Register DeviceEvent Handler
     ReturnErrorOnFailure(chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(ChipDeviceEventHandler::Handle, 0));
 
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    // Set a handler for Commissioner's CommissionerDeclaration messages.
+    chip::Server::GetInstance().GetUserDirectedCommissioningClient()->SetCommissionerDeclarationHandler(
+        CommissionerDeclarationHandler::GetInstance());
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+
     mState = CASTING_APP_RUNNING; // CastingApp started successfully, set state to RUNNING
     return CHIP_NO_ERROR;
 }
@@ -145,6 +155,11 @@
     ChipLogProgress(Discovery, "CastingApp::Stop() called");
     VerifyOrReturnError(mState == CASTING_APP_RUNNING, CHIP_ERROR_INCORRECT_STATE);
 
+#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+    // Remove the handler previously set for Commissioner's CommissionerDeclaration messages.
+    chip::Server::GetInstance().GetUserDirectedCommissioningClient()->SetCommissionerDeclarationHandler(nullptr);
+#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
+
     // Shutdown the Matter server
     chip::Server::GetInstance().Shutdown();
 
diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp
index c9f938d..0f455d6 100644
--- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp
+++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp
@@ -30,7 +30,7 @@
 CastingPlayer * CastingPlayer::mTargetCastingPlayer = nullptr;
 
 void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec,
-                                                EndpointFilter desiredEndpointFilter)
+                                                IdentificationDeclarationOptions idOptions)
 {
     ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection() called");
 
@@ -50,12 +50,14 @@
     mOnCompleted                   = onCompleted;
     mCommissioningWindowTimeoutSec = commissioningWindowTimeoutSec;
     mTargetCastingPlayer           = this;
+    mIdOptions                     = idOptions;
 
     // If *this* CastingPlayer was previously connected to, its nodeId, fabricIndex and other attributes should be present
     // in the CastingStore cache. If that is the case, AND, the cached data contains the endpoint desired by the client, if any,
-    // as per desiredEndpointFilter, simply Find or Re-establish the CASE session and return early
+    // as per IdentificationDeclarationOptions.mTargetAppInfos, simply Find or Re-establish the CASE session and return early.
     if (cachedCastingPlayers.size() != 0)
     {
+        ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection() Re-establishing CASE with cached CastingPlayer");
         it = std::find_if(cachedCastingPlayers.begin(), cachedCastingPlayers.end(),
                           [this](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == *this; });
 
@@ -63,7 +65,7 @@
         if (it != cachedCastingPlayers.end())
         {
             unsigned index = (unsigned int) std::distance(cachedCastingPlayers.begin(), it);
-            if (ContainsDesiredEndpoint(&cachedCastingPlayers[index], desiredEndpointFilter))
+            if (ContainsDesiredTargetApp(&cachedCastingPlayers[index], idOptions.getTargetAppInfoList()))
             {
                 ChipLogProgress(
                     AppServer,
@@ -163,14 +165,15 @@
 #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
 CHIP_ERROR CastingPlayer::SendUserDirectedCommissioningRequest()
 {
+    ChipLogProgress(AppServer, "CastingPlayer::SendUserDirectedCommissioningRequest()");
     chip::Inet::IPAddress * ipAddressToUse = GetIpAddressForUDCRequest();
     VerifyOrReturnValue(ipAddressToUse != nullptr, CHIP_ERROR_INCORRECT_STATE,
                         ChipLogError(AppServer, "No IP Address found to send UDC request to"));
 
     ReturnErrorOnFailure(support::ChipDeviceEventHandler::SetUdcStatus(true));
 
-    // TODO: expose options to the higher layer
-    chip::Protocols::UserDirectedCommissioning::IdentificationDeclaration id;
+    chip::Protocols::UserDirectedCommissioning::IdentificationDeclaration id = mIdOptions.buildIdentificationDeclarationMessage();
+
     ReturnErrorOnFailure(chip::Server::GetInstance().SendUserDirectedCommissioningRequest(
         chip::Transport::PeerAddress::UDP(*ipAddressToUse, mAttributes.port, mAttributes.interfaceId), id));
 
@@ -216,21 +219,27 @@
         connectionContext->mOnConnectionFailureCallback);
 }
 
-bool CastingPlayer::ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter desiredEndpointFilter)
+bool CastingPlayer::ContainsDesiredTargetApp(
+    core::CastingPlayer * cachedCastingPlayer,
+    std::vector<chip::Protocols::UserDirectedCommissioning::TargetAppInfo> desiredTargetApps)
 {
     std::vector<memory::Strong<Endpoint>> cachedEndpoints = cachedCastingPlayer->GetEndpoints();
-    for (const auto & cachedEndpoint : cachedEndpoints)
+    for (size_t i = 0; i < desiredTargetApps.size(); i++)
     {
-        bool match = true;
-        match = match && (desiredEndpointFilter.vendorId == 0 || cachedEndpoint->GetVendorId() == desiredEndpointFilter.vendorId);
-        match =
-            match && (desiredEndpointFilter.productId == 0 || cachedEndpoint->GetProductId() == desiredEndpointFilter.productId);
-        // TODO: check deviceTypeList
-        if (match)
+        for (const auto & cachedEndpoint : cachedEndpoints)
         {
-            return true;
+            bool match = true;
+            match = match && (desiredTargetApps[i].vendorId == 0 || cachedEndpoint->GetVendorId() == desiredTargetApps[i].vendorId);
+            match =
+                match && (desiredTargetApps[i].productId == 0 || cachedEndpoint->GetProductId() == desiredTargetApps[i].productId);
+            if (match)
+            {
+                ChipLogProgress(AppServer, "CastingPlayer::ContainsDesiredTargetApp() matching cached CastingPlayer found");
+                return true;
+            }
         }
     }
+    ChipLogProgress(AppServer, "CastingPlayer::ContainsDesiredTargetApp() matching cached CastingPlayer not found");
     return false;
 }
 
diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h
index 783d50f..2748eaf 100644
--- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h
+++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h
@@ -18,7 +18,9 @@
 
 #pragma once
 
+#include "CommissionerDeclarationHandler.h"
 #include "Endpoint.h"
+#include "IdentificationDeclarationOptions.h"
 #include "Types.h"
 #include "support/ChipDeviceEventHandler.h"
 #include "support/EndpointListLoader.h"
@@ -123,13 +125,15 @@
      * For failure - called back with an error and nullptr.
      * @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window open, if commissioning is
      * required. Needs to be >= kCommissioningWindowTimeoutSec.
-     * @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint that the client wants to
-     * interact with after commissioning. If this value is passed in, the VerifyOrEstablishConnection will force User Directed
-     * Commissioning, in case the desired Endpoint is not found in the on device CastingStore.
+     * @param idOptions (Optional) Parameters in the IdentificationDeclaration message sent by the Commissionee to the Commissioner.
+     * These parameters specify the information relating to the requested commissioning session.
+     * Furthermore, attributes (such as VendorId) describe the TargetApp that the client wants to interact with after commissioning.
+     * If this value is passed in, VerifyOrEstablishConnection() will force User Directed Commissioning, in case the desired
+     * TargetApp is not found in the on-device CastingStore.
      */
     void VerifyOrEstablishConnection(ConnectCallback onCompleted,
                                      unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec,
-                                     EndpointFilter desiredEndpointFilter                 = EndpointFilter());
+                                     IdentificationDeclarationOptions idOptions           = IdentificationDeclarationOptions());
 
     /**
      * @brief Sets the internal connection state of this CastingPlayer to "disconnected"
@@ -197,6 +201,7 @@
     std::vector<memory::Strong<Endpoint>> mEndpoints;
     ConnectionState mConnectionState = CASTING_PLAYER_NOT_CONNECTED;
     CastingPlayerAttributes mAttributes;
+    IdentificationDeclarationOptions mIdOptions;
     static CastingPlayer * mTargetCastingPlayer;
     unsigned long long int mCommissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec;
     ConnectCallback mOnCompleted                          = {};
@@ -217,12 +222,12 @@
 #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
 
     /**
-     * @brief Checks if the cachedCastingPlayer contains an Endpoint that matches the description of the desiredEndpointFilter
-     *
-     * @return true - cachedCastingPlayer contains at least one endpoint that matches all the (non-default) values in
-     * desiredEndpointFilter, false otherwise
+     * @brief Checks if the cachedCastingPlayer contains at least one Endpoint/TargetApp described in the desiredTargetApps list.
+     * @return true - cachedCastingPlayer contains at least one endpoints with matching (non-default) values for vendorID and
+     * productID as described in the desiredTargetApps list, false otherwise.
      */
-    bool ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter desiredEndpointFilter);
+    bool ContainsDesiredTargetApp(core::CastingPlayer * cachedCastingPlayer,
+                                  std::vector<chip::Protocols::UserDirectedCommissioning::TargetAppInfo> desiredTargetApps);
 
     // ChipDeviceEventHandler handles chip::DeviceLayer::ChipDeviceEvent events and helps the CastingPlayer class commission with
     // and connect to a CastingPlayer
diff --git a/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.cpp b/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.cpp
new file mode 100644
index 0000000..1bedc6c
--- /dev/null
+++ b/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.cpp
@@ -0,0 +1,49 @@
+/*
+ *
+ *    Copyright (c) 2024 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.
+ */
+
+#include "CommissionerDeclarationHandler.h"
+
+#include "Types.h"
+
+namespace matter {
+namespace casting {
+namespace core {
+
+CommissionerDeclarationHandler * CommissionerDeclarationHandler::sCommissionerDeclarationHandler_ = nullptr;
+
+CommissionerDeclarationHandler * CommissionerDeclarationHandler::GetInstance()
+{
+    if (sCommissionerDeclarationHandler_ == nullptr)
+    {
+        sCommissionerDeclarationHandler_ = new CommissionerDeclarationHandler();
+    }
+    return sCommissionerDeclarationHandler_;
+}
+
+// TODO: In the following PRs. Implement setHandler() for CommissionerDeclaration messages and expose messages to higher layers for
+// Linux, Android and iOS.
+void CommissionerDeclarationHandler::OnCommissionerDeclarationMessage(
+    const chip::Transport::PeerAddress & source, chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd)
+{
+    ChipLogProgress(AppServer, "CommissionerDeclarationHandler::OnCommissionerDeclarationMessage() called TODO: handle message");
+    cd.DebugLog();
+}
+
+}; // namespace core
+}; // namespace casting
+}; // namespace matter
diff --git a/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.h b/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.h
new file mode 100644
index 0000000..e84ca59
--- /dev/null
+++ b/examples/tv-casting-app/tv-casting-common/core/CommissionerDeclarationHandler.h
@@ -0,0 +1,49 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "Types.h"
+
+namespace matter {
+namespace casting {
+namespace core {
+
+/**
+ * @brief React to the Commissioner's CommissionerDeclaration messages with this singleton.
+ */
+class CommissionerDeclarationHandler : public chip::Protocols::UserDirectedCommissioning::CommissionerDeclarationHandler
+{
+public:
+    CommissionerDeclarationHandler(const CommissionerDeclarationHandler &) = delete;
+    void operator=(const CommissionerDeclarationHandler &)                 = delete;
+
+    static CommissionerDeclarationHandler * GetInstance();
+
+    void OnCommissionerDeclarationMessage(const chip::Transport::PeerAddress & source,
+                                          chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) override;
+
+private:
+    static CommissionerDeclarationHandler * sCommissionerDeclarationHandler_;
+    CommissionerDeclarationHandler() {}
+    ~CommissionerDeclarationHandler() {}
+};
+
+}; // namespace core
+}; // namespace casting
+}; // namespace matter
diff --git a/examples/tv-casting-app/tv-casting-common/core/IdentificationDeclarationOptions.h b/examples/tv-casting-app/tv-casting-common/core/IdentificationDeclarationOptions.h
new file mode 100644
index 0000000..af1ae2a
--- /dev/null
+++ b/examples/tv-casting-app/tv-casting-common/core/IdentificationDeclarationOptions.h
@@ -0,0 +1,126 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "Types.h"
+#include <vector>
+
+namespace matter {
+namespace casting {
+namespace core {
+
+/**
+ * This class contains the optional parameters used in the IdentificationDeclaration Message, sent by the Commissionee to the
+ * Commissioner. The options specify information relating to the requested UDC commissioning session.
+ */
+class IdentificationDeclarationOptions
+{
+public:
+    /**
+     * Feature: Target Content Application
+     * Flag to instruct the Commissioner not to display a Passcode input dialog, and instead send a CommissionerDeclaration message
+     * if a commissioning Passcode is needed.
+     */
+    bool mNoPasscode = false;
+    /**
+     * Feature: Coordinate Passcode Dialogs
+     * Flag to instruct the Commissioner to send a CommissionerDeclaration message when the Passcode input dialog on the
+     * Commissioner has been shown to the user.
+     */
+    bool mCdUponPasscodeDialog = false;
+    /**
+     * Feature: Commissioner-Generated Passcode
+     * Flag to instruct the Commissioner to use the Commissioner-generated Passcode for commissioning.
+     */
+    bool mCommissionerPasscode = false;
+    /**
+     * Feature: Commissioner-Generated Passcode
+     * Flag to indicate whether or not the Commissionee has obtained the Commissioner Passcode from the user and is therefore ready
+     * for commissioning.
+     */
+    bool mCommissionerPasscodeReady = false;
+    /**
+     * Feature: Coordinate Passcode Dialogs
+     * Flag to indicate when the Commissionee user has decided to exit the commissioning process.
+     */
+    bool mCancelPasscode = false;
+
+    CHIP_ERROR addTargetAppInfo(const chip::Protocols::UserDirectedCommissioning::TargetAppInfo & targetAppInfo)
+    {
+        if (mTargetAppInfos.size() >= CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS)
+        {
+            ChipLogError(AppServer,
+                         "IdentificationDeclarationOptions::addTargetAppInfo() failed to add TargetAppInfo, max vector size is %d",
+                         CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS);
+            return CHIP_ERROR_NO_MEMORY;
+        }
+
+        mTargetAppInfos.push_back(targetAppInfo);
+        return CHIP_NO_ERROR;
+    }
+
+    std::vector<chip::Protocols::UserDirectedCommissioning::TargetAppInfo> getTargetAppInfoList() const { return mTargetAppInfos; }
+
+    /**
+     * @brief Builds an IdentificationDeclaration message to be sent to a CastingPlayer, given the options state specified in this
+     * object.
+     */
+    chip::Protocols::UserDirectedCommissioning::IdentificationDeclaration buildIdentificationDeclarationMessage()
+    {
+        ChipLogProgress(AppServer, "IdentificationDeclarationOptions::buildIdentificationDeclarationMessage()");
+        chip::Protocols::UserDirectedCommissioning::IdentificationDeclaration id;
+
+        std::vector<chip::Protocols::UserDirectedCommissioning::TargetAppInfo> targetAppInfos = getTargetAppInfoList();
+        for (size_t i = 0; i < targetAppInfos.size(); i++)
+        {
+            id.AddTargetAppInfo(targetAppInfos[i]);
+        }
+        id.SetNoPasscode(mNoPasscode);
+        id.SetCdUponPasscodeDialog(mCdUponPasscodeDialog);
+        id.SetCancelPasscode(mCancelPasscode);
+
+        ChipLogProgress(AppServer,
+                        "IdentificationDeclarationOptions::buildIdentificationDeclarationMessage() mCommissionerPasscode: %s",
+                        mCommissionerPasscode ? "true" : "false");
+        id.SetCommissionerPasscode(mCommissionerPasscode);
+
+        ChipLogProgress(AppServer,
+                        "IdentificationDeclarationOptions::buildIdentificationDeclarationMessage() mCommissionerPasscodeReady: %s",
+                        mCommissionerPasscodeReady ? "true" : "false");
+        if (mCommissionerPasscodeReady)
+        {
+            id.SetCommissionerPasscodeReady(true);
+            mCommissionerPasscodeReady = false;
+        }
+        return id;
+    }
+
+private:
+    /**
+     * Feature: Target Content Application
+     * The set of content app Vendor IDs (and optionally, Product IDs) that can be used for authentication.
+     * Also, if TargetAppInfo is passed in, VerifyOrEstablishConnection() will force User Directed Commissioning, in case the
+     * desired TargetApp is not found in the on-device CastingStore.
+     */
+    std::vector<chip::Protocols::UserDirectedCommissioning::TargetAppInfo> mTargetAppInfos;
+};
+
+}; // namespace core
+}; // namespace casting
+}; // namespace matter
diff --git a/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h b/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h
index 74926b0..f5887bc 100644
--- a/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h
+++ b/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h
@@ -53,6 +53,10 @@
 
 #define CHIP_DEVICE_CONFIG_ENABLE_PAIRING_AUTOSTART 0
 
+// TVs can handle the memory impact of supporting a larger list of target apps. See
+// examples/tv-app/tv-common/include/CHIPProjectAppConfig.h
+#define CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS 10
+
 // For casting, we need to allow more ACL entries, and more complex entries
 #define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY 20
 #define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY 20
diff --git a/examples/tv-casting-app/tv-casting-common/support/AppParameters.h b/examples/tv-casting-app/tv-casting-common/support/AppParameters.h
index 9539a29..080011f 100644
--- a/examples/tv-casting-app/tv-casting-common/support/AppParameters.h
+++ b/examples/tv-casting-app/tv-casting-common/support/AppParameters.h
@@ -42,6 +42,7 @@
                       chip::Credentials::DeviceAttestationCredentialsProvider * deviceAttestationCredentialsProvider,
                       chip::Credentials::DeviceAttestationVerifier * deviceAttestationVerifier,
                       ServerInitParamsProvider * serverInitParamsProvider)
+    // TODO: In the following PRs. Add CommissionerDeclarationHandler.
     {
         VerifyOrReturnError(commissionableDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
         VerifyOrReturnError(deviceAttestationCredentialsProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index 1ad98d2..ad085d5 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -621,7 +621,7 @@
 CHIP_ERROR Server::SendUserDirectedCommissioningRequest(chip::Transport::PeerAddress commissioner,
                                                         Protocols::UserDirectedCommissioning::IdentificationDeclaration & id)
 {
-    ChipLogDetail(AppServer, "SendUserDirectedCommissioningRequest2");
+    ChipLogDetail(AppServer, "Server::SendUserDirectedCommissioningRequest()");
 
     CHIP_ERROR err;
 
diff --git a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
index a06bffc..fb31049 100644
--- a/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
+++ b/src/protocols/user_directed_commissioning/UserDirectedCommissioningServer.cpp
@@ -367,6 +367,7 @@
                 {
                     ChipLogProgress(AppServer, "TLV end of array");
                     ReturnErrorOnFailure(reader.ExitContainer(listContainerType));
+                    err = CHIP_NO_ERROR;
                 }
             }
             break;