diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/ConnectionCallbacks.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/ConnectionCallbacks.java index 3d4152bf106cce..fc3aec0a93a304 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/ConnectionCallbacks.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/ConnectionCallbacks.java @@ -13,23 +13,40 @@ */ package com.matter.casting.support; -/** @brief A container struct for User Directed Commissioning (UDC) callbacks. */ +/** @brief A container class for User Directed Commissioning (UDC) callbacks. */ public class ConnectionCallbacks { - /** (Required) The callback called when the connection is established successfully. */ + /** The callback called when the connection is established successfully. */ public final MatterCallback onSuccess; - /** (Required) The callback called with MatterError when the connection is fails to establish. */ + /** The callback called with MatterError when the connection is fails to establish. */ public final MatterCallback onFailure; /** - * (Optional) The callback called when the Client/Commissionee receives a CommissionerDeclaration - * message from the CastingPlayer/Commissioner. This callback is needed to support UDC features - * where a reply from the Commissioner is expected. It provides information indicating the - * Commissioner’s pre-commissioning state. + * The callback called when the Client/Commissionee receives a CommissionerDeclaration message + * from the CastingPlayer/Commissioner. This callback is needed to support UDC features where a + * reply from the Commissioner is expected. It provides information indicating the Commissioner’s + * pre-commissioning state. */ public MatterCallback onCommissionerDeclaration; + /** + * The constructor for ConnectionCallbacks, the container class for User Directed Commissioning + * (UDC) callbacks. + * + * @param onSuccess (Required) The callback called when the connection is established + * successfully. + * @param onFailure (Required) The callback called with MatterError when the connection is fails + * to establish. + * @param onCommissionerDeclaration (Optional) The callback called when the Client/Commissionee + * receives a CommissionerDeclaration message from the CastingPlayer/Commissioner. This + * callback is needed to support UDC features where a reply from the Commissioner is expected. + * It provides information indicating the Commissioner’s pre-commissioning state. + *

For example: During CastingPlayer/Commissioner-Generated passcode commissioning, the + * Commissioner replies with a CommissionerDeclaration message with PasscodeDialogDisplayed + * and CommissionerPasscode set to true. Given these Commissioner state details, the client is + * expected to perform some actions and responf accrdingly. + */ public ConnectionCallbacks( MatterCallback onSuccess, MatterCallback onFailure, diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/IdentificationDeclarationOptions.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/IdentificationDeclarationOptions.java index 18567374c66a17..9b64b898459dbd 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/IdentificationDeclarationOptions.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/IdentificationDeclarationOptions.java @@ -19,8 +19,8 @@ /** * 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. + * by the Commissionee (CastingApp) to the Commissioner (CastingPlayer). The options specify + * information relating to the requested UDC commissioning session. */ public class IdentificationDeclarationOptions { private final String TAG = IdentificationDeclarationOptions.class.getSimpleName(); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/TargetAppInfo.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/TargetAppInfo.java index 1f605a89833bb1..e8b0553b322c47 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/TargetAppInfo.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/TargetAppInfo.java @@ -14,8 +14,8 @@ package com.matter.casting.support; /** - * Feature: Target Content Application - The set of content app Vendor IDs (and optionally, Product - * IDs) that can be used for authentication. + * Feature: Target Content Application - An entry in the IdentificationDeclarationOptions.java + * TargetAppList which contains a TargetVendorId and an optional TargetProductId. */ public class TargetAppInfo { /** Target Target Content Application Vendor ID, null means unspecified */ diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h index 286d380cf7d942..87d14921ffd30c 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h @@ -50,7 +50,14 @@ class MatterCastingPlayerJNI private: friend MatterCastingPlayerJNI & MatterCastingPlayerJNIMgr(); static MatterCastingPlayerJNI sInstance; + + // Handles the connection complete event and calls the ConnectionCallbacks onSuccess or onFailure callback provided by the Java + // client. This callback is called by the cpp layer when the connection process has ended, regardless of whether it was + // successful or not. static void ConnectCallback(CHIP_ERROR err, CastingPlayer * playerPtr); + // Handles the Commissioner Declaration event and calls the ConnectionCallbacks onCommissionerDeclaration callback provided by + // the Java client. This callback is called by the cpp layer when the Commissionee receives a CommissionerDeclaration message + // from the CastingPlayer/Commissioner. static void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source, chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd); }; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge.xcodeproj/project.pbxproj b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge.xcodeproj/project.pbxproj index 5df8ce26bf4019..28793f5f60b8e7 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge.xcodeproj/project.pbxproj +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge.xcodeproj/project.pbxproj @@ -7,8 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 393039D52C1CF3C500DE8681 /* MCIdentificationDeclarationOptions_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 393039D42C1CF3C500DE8681 /* MCIdentificationDeclarationOptions_Internal.h */; }; + 3938C1ED2C1A1C8D009B2B35 /* MCCommissionerDeclaration_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3938C1EC2C1A1C8D009B2B35 /* MCCommissionerDeclaration_Internal.h */; }; 39589F162B91556B00BE040C /* MCInteractionModelStructs.h in Headers */ = {isa = PBXBuildFile; fileRef = 39589F152B91556B00BE040C /* MCInteractionModelStructs.h */; }; 39589F182B91557700BE040C /* MCInteractionModelStructs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39589F172B91557700BE040C /* MCInteractionModelStructs.mm */; }; + 395BF6AF2C18EC7C00C73CE2 /* MCTargetAppInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 395BF6AE2C18EC7C00C73CE2 /* MCTargetAppInfo.mm */; }; + 397099D92C2255FE00B34428 /* MCCastingApp_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 397099D82C2255FE00B34428 /* MCCastingApp_Internal.h */; }; + 399BCE1E2C13CBAD0075AA85 /* MCTargetAppInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 399BCE1D2C13CBAD0075AA85 /* MCTargetAppInfo.h */; }; 39BF57C52B8CFF790081653C /* MCAttributeObjects.h in Headers */ = {isa = PBXBuildFile; fileRef = 39BF57C42B8CFF790081653C /* MCAttributeObjects.h */; }; 39BF57C72B8CFFB90081653C /* MCAttributeObjects.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39BF57C62B8CFFB90081653C /* MCAttributeObjects.mm */; }; 39BF57C92B8D66540081653C /* NSStringSpanConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 39BF57C82B8D66540081653C /* NSStringSpanConversion.h */; }; @@ -19,6 +24,12 @@ 39D4D2522B97943D00BF3CFE /* MCCommandObjects.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39D4D2512B97943D00BF3CFE /* MCCommandObjects.mm */; }; 39DB29E92B9A3B1D0071334A /* MCCommandPayloads.h in Headers */ = {isa = PBXBuildFile; fileRef = 39DB29E82B9A3B1D0071334A /* MCCommandPayloads.h */; }; 39DB29EB2B9A3B2D0071334A /* MCCommandPayloads.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39DB29EA2B9A3B2D0071334A /* MCCommandPayloads.mm */; }; + 39F589DA2C176EA4000AAADD /* MCIdentificationDeclarationOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 39F589D92C176EA4000AAADD /* MCIdentificationDeclarationOptions.h */; }; + 39F589DC2C176EC0000AAADD /* MCIdentificationDeclarationOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39F589DB2C176EC0000AAADD /* MCIdentificationDeclarationOptions.mm */; }; + 39F589DE2C1781C8000AAADD /* MCCommissionerDeclaration.h in Headers */ = {isa = PBXBuildFile; fileRef = 39F589DD2C1781C8000AAADD /* MCCommissionerDeclaration.h */; }; + 39F589E02C1781E4000AAADD /* MCCommissionerDeclaration.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39F589DF2C1781E4000AAADD /* MCCommissionerDeclaration.mm */; }; + 39F589E22C179A09000AAADD /* MCConnectionCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 39F589E12C179A09000AAADD /* MCConnectionCallbacks.h */; }; + 39F589E42C179A18000AAADD /* MCConnectionCallbacks.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39F589E32C179A18000AAADD /* MCConnectionCallbacks.mm */; }; 3C0474062B3F7E5F0012AE95 /* MCEndpointFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C0474052B3F7E5F0012AE95 /* MCEndpointFilter.h */; }; 3C04740C2B4604CF0012AE95 /* MCCryptoUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C04740B2B4604CF0012AE95 /* MCCryptoUtils.h */; }; 3C04740E2B4605B40012AE95 /* MCCryptoUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C04740D2B4605B40012AE95 /* MCCryptoUtils.mm */; }; @@ -81,9 +92,14 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 393039D42C1CF3C500DE8681 /* MCIdentificationDeclarationOptions_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCIdentificationDeclarationOptions_Internal.h; sourceTree = ""; }; + 3938C1EC2C1A1C8D009B2B35 /* MCCommissionerDeclaration_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCommissionerDeclaration_Internal.h; sourceTree = ""; }; 39589F152B91556B00BE040C /* MCInteractionModelStructs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCInteractionModelStructs.h; sourceTree = ""; }; 39589F172B91557700BE040C /* MCInteractionModelStructs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCInteractionModelStructs.mm; sourceTree = ""; }; + 395BF6AE2C18EC7C00C73CE2 /* MCTargetAppInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCTargetAppInfo.mm; sourceTree = ""; }; + 397099D82C2255FE00B34428 /* MCCastingApp_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCastingApp_Internal.h; sourceTree = ""; }; 399049A62B9FC4ED000C91F0 /* MCCommandPayloads_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCommandPayloads_Internal.h; sourceTree = ""; }; + 399BCE1D2C13CBAD0075AA85 /* MCTargetAppInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCTargetAppInfo.h; sourceTree = ""; }; 39BF57C42B8CFF790081653C /* MCAttributeObjects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCAttributeObjects.h; sourceTree = ""; }; 39BF57C62B8CFFB90081653C /* MCAttributeObjects.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCAttributeObjects.mm; sourceTree = ""; }; 39BF57C82B8D66540081653C /* NSStringSpanConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSStringSpanConversion.h; sourceTree = ""; }; @@ -94,6 +110,12 @@ 39D4D2512B97943D00BF3CFE /* MCCommandObjects.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCCommandObjects.mm; sourceTree = ""; }; 39DB29E82B9A3B1D0071334A /* MCCommandPayloads.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCommandPayloads.h; sourceTree = ""; }; 39DB29EA2B9A3B2D0071334A /* MCCommandPayloads.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCCommandPayloads.mm; sourceTree = ""; }; + 39F589D92C176EA4000AAADD /* MCIdentificationDeclarationOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCIdentificationDeclarationOptions.h; sourceTree = ""; }; + 39F589DB2C176EC0000AAADD /* MCIdentificationDeclarationOptions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCIdentificationDeclarationOptions.mm; sourceTree = ""; }; + 39F589DD2C1781C8000AAADD /* MCCommissionerDeclaration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCommissionerDeclaration.h; sourceTree = ""; }; + 39F589DF2C1781E4000AAADD /* MCCommissionerDeclaration.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCCommissionerDeclaration.mm; sourceTree = ""; }; + 39F589E12C179A09000AAADD /* MCConnectionCallbacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCConnectionCallbacks.h; sourceTree = ""; }; + 39F589E32C179A18000AAADD /* MCConnectionCallbacks.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCConnectionCallbacks.mm; sourceTree = ""; }; 3C0474052B3F7E5F0012AE95 /* MCEndpointFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCEndpointFilter.h; sourceTree = ""; }; 3C04740B2B4604CF0012AE95 /* MCCryptoUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCCryptoUtils.h; sourceTree = ""; }; 3C04740D2B4605B40012AE95 /* MCCryptoUtils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MCCryptoUtils.mm; sourceTree = ""; }; @@ -255,6 +277,7 @@ 395241F42B854EA700F7DB91 /* zap-generated */, 3CCB87202869085400771BAD /* MatterTvCastingBridge.h */, 3CF71C092A992D0D003A5CE5 /* MCCastingApp.h */, + 397099D82C2255FE00B34428 /* MCCastingApp_Internal.h */, 3CF71C0B2A992D25003A5CE5 /* MCCastingApp.mm */, 3C2346222B362B9500FA276E /* MCCastingPlayerDiscovery.h */, 3C2346242B362BBB00FA276E /* MCCastingPlayerDiscovery.mm */, @@ -276,6 +299,16 @@ 3C4F52132B4F31DC00BB8A10 /* MCDeviceTypeStruct.h */, 3C4F52152B4F31FA00BB8A10 /* MCDeviceTypeStruct.m */, 3C0474052B3F7E5F0012AE95 /* MCEndpointFilter.h */, + 399BCE1D2C13CBAD0075AA85 /* MCTargetAppInfo.h */, + 395BF6AE2C18EC7C00C73CE2 /* MCTargetAppInfo.mm */, + 39F589E12C179A09000AAADD /* MCConnectionCallbacks.h */, + 39F589E32C179A18000AAADD /* MCConnectionCallbacks.mm */, + 39F589DD2C1781C8000AAADD /* MCCommissionerDeclaration.h */, + 3938C1EC2C1A1C8D009B2B35 /* MCCommissionerDeclaration_Internal.h */, + 39F589DF2C1781E4000AAADD /* MCCommissionerDeclaration.mm */, + 39F589D92C176EA4000AAADD /* MCIdentificationDeclarationOptions.h */, + 393039D42C1CF3C500DE8681 /* MCIdentificationDeclarationOptions_Internal.h */, + 39F589DB2C176EC0000AAADD /* MCIdentificationDeclarationOptions.mm */, 3C2696FA2B4A5FC50026E771 /* MCEndpointFilter.m */, 3CF71C0D2A992DA2003A5CE5 /* MCDataSource.h */, 3CF71C0F2A99312D003A5CE5 /* MCCommissionableData.h */, @@ -312,19 +345,25 @@ 3C2346212B362B4F00FA276E /* MCCastingPlayer.h in Headers */, 3C2346232B362B9500FA276E /* MCCastingPlayerDiscovery.h in Headers */, 3CF71C0E2A992DA2003A5CE5 /* MCDataSource.h in Headers */, + 3938C1ED2C1A1C8D009B2B35 /* MCCommissionerDeclaration_Internal.h in Headers */, 3C4F52142B4F31DC00BB8A10 /* MCDeviceTypeStruct.h in Headers */, 39BF57CD2B8FC0EF0081653C /* MCClusterObjects.h in Headers */, 39D4D2502B97942D00BF3CFE /* MCCommandObjects.h in Headers */, 3C621CA52B605A6A005CDBA3 /* MCAttribute.h in Headers */, + 397099D92C2255FE00B34428 /* MCCastingApp_Internal.h in Headers */, 3CD73F1C2A9E8396009D82D1 /* MCCommissionableDataProvider.h in Headers */, 3C4F521E2B4F4B3B00BB8A10 /* MCEndpoint_Internal.h in Headers */, 39BF57C92B8D66540081653C /* NSStringSpanConversion.h in Headers */, + 399BCE1E2C13CBAD0075AA85 /* MCTargetAppInfo.h in Headers */, 39DB29E92B9A3B1D0071334A /* MCCommandPayloads.h in Headers */, + 39F589DE2C1781C8000AAADD /* MCCommissionerDeclaration.h in Headers */, 3C4F52262B50899700BB8A10 /* MCCluster_Internal.h in Headers */, + 39F589DA2C176EA4000AAADD /* MCIdentificationDeclarationOptions.h in Headers */, 3CF71C0A2A992D0D003A5CE5 /* MCCastingApp.h in Headers */, 3C4F521C2B4F4B1B00BB8A10 /* MCCastingPlayer_Internal.h in Headers */, 3CD73F172A9E6884009D82D1 /* MCRotatingDeviceIdUniqueIdProvider.h in Headers */, 3C4F52102B4E18C800BB8A10 /* MCEndpoint.h in Headers */, + 393039D52C1CF3C500DE8681 /* MCIdentificationDeclarationOptions_Internal.h in Headers */, 3C4F52282B51DB3000BB8A10 /* MCCommand.h in Headers */, 39BF57CB2B8E54F80081653C /* NSDataSpanConversion.h in Headers */, 3C621CA92B605C52005CDBA3 /* MCAttribute_Internal.h in Headers */, @@ -336,6 +375,7 @@ 39589F162B91556B00BE040C /* MCInteractionModelStructs.h in Headers */, 3C9437922B3B478E0096E5F4 /* MCErrorUtils.h in Headers */, 3C4F52222B507C9300BB8A10 /* MCCluster.h in Headers */, + 39F589E22C179A09000AAADD /* MCConnectionCallbacks.h in Headers */, 3CCB8741286A593700771BAD /* DiscoveredNodeData.h in Headers */, 3C0474062B3F7E5F0012AE95 /* MCEndpointFilter.h in Headers */, 3CCB87212869085400771BAD /* MatterTvCastingBridge.h in Headers */, @@ -449,18 +489,22 @@ 39BF57CF2B8FC1030081653C /* MCClusterObjects.mm in Sources */, 3CD73F192A9E68A7009D82D1 /* MCRotatingDeviceIdUniqueIdProvider.mm in Sources */, 39589F182B91557700BE040C /* MCInteractionModelStructs.mm in Sources */, + 39F589DC2C176EC0000AAADD /* MCIdentificationDeclarationOptions.mm in Sources */, 39DB29EB2B9A3B2D0071334A /* MCCommandPayloads.mm in Sources */, 3CF8532728E37F1000F07B9F /* MatterError.mm in Sources */, 3C4F522C2B51E02800BB8A10 /* MCCommand.mm in Sources */, + 395BF6AF2C18EC7C00C73CE2 /* MCTargetAppInfo.mm in Sources */, 3C4E53B628E5595A00F293E8 /* ContentLauncherTypes.mm in Sources */, 3C81C75028F7A7D3001CB9D1 /* VideoPlayer.m in Sources */, 3CF71C0C2A992D25003A5CE5 /* MCCastingApp.mm in Sources */, 3CC3979E2BE9857C00465462 /* MCCastingPlayer.mm in Sources */, 3C621CAB2B605C6E005CDBA3 /* MCAttribute.mm in Sources */, + 39F589E42C179A18000AAADD /* MCConnectionCallbacks.mm in Sources */, 3C4F52122B4E18ED00BB8A10 /* MCEndpoint.mm in Sources */, 3C4E53B028E4F28100F293E8 /* MediaPlaybackTypes.mm in Sources */, 3C04740E2B4605B40012AE95 /* MCCryptoUtils.mm in Sources */, 3CD73F1E2A9E83C1009D82D1 /* MCCommissionableDataProvider.mm in Sources */, + 39F589E02C1781E4000AAADD /* MCCommissionerDeclaration.mm in Sources */, 3CD73F222A9EA078009D82D1 /* MCDeviceAttestationCredentials.mm in Sources */, 39BF57C72B8CFFB90081653C /* MCAttributeObjects.mm in Sources */, 3C2346252B362BBB00FA276E /* MCCastingPlayerDiscovery.mm in Sources */, diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.mm index d124f7658fdca5..347fdb2d7475a3 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.mm @@ -15,7 +15,7 @@ * limitations under the License. */ -#import "MCCastingApp.h" +#import "MCCastingApp_Internal.h" #import "MCCommissionableDataProvider.h" #import "MCCommonCaseDeviceServerInitParamsProvider.h" @@ -43,6 +43,9 @@ @interface MCCastingApp () // queue used to perform all work performed by the MatterTvCastingBridge @property (atomic) dispatch_queue_t workQueue; +// Client defiend data source used to initialize the MCCommissionableDataProvider and, if needed, update the MCCommissionableDataProvider post initialization. This is necessary for the Commissioner-Generated passcode commissioning feature. +@property (nonatomic, strong) id dataSource; + @end @implementation MCCastingApp @@ -69,7 +72,9 @@ - (dispatch_queue_t)getClientQueue - (NSError *)initializeWithDataSource:(id)dataSource { - ChipLogProgress(AppServer, "MCCastingApp.initializeWithDataSource called"); + ChipLogProgress(AppServer, "MCCastingApp.initializeWithDataSource() called"); + // store the data source provided by the client + _dataSource = dataSource; // get the clientQueue VerifyOrReturnValue([dataSource clientQueue] != nil, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); @@ -78,6 +83,7 @@ - (NSError *)initializeWithDataSource:(id)dataSource // Initialize cpp Providers VerifyOrReturnValue(_uniqueIdProvider.Initialize(dataSource) == CHIP_NO_ERROR, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); + ChipLogProgress(AppServer, "MCCastingApp.initializeWithDataSource() calling MCCommissionableDataProvider.Initialize()"); _commissionableDataProvider = new matter::casting::support::MCCommissionableDataProvider(); VerifyOrReturnValue(_commissionableDataProvider->Initialize(dataSource) == CHIP_NO_ERROR, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); @@ -92,6 +98,7 @@ - (NSError *)initializeWithDataSource:(id)dataSource == CHIP_NO_ERROR, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); + ChipLogProgress(AppServer, "MCCastingApp.initializeWithDataSource() calling cpp CastingApp::Initialize()"); // Initialize cpp CastingApp VerifyOrReturnValue(matter::casting::core::CastingApp::GetInstance()->Initialize(_appParameters) == CHIP_NO_ERROR, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); @@ -102,6 +109,22 @@ - (NSError *)initializeWithDataSource:(id)dataSource return [MCErrorUtils NSErrorFromChipError:CHIP_NO_ERROR]; } +- (NSError *)updateCommissionableDataProvider +{ + ChipLogProgress(AppServer, "MCCastingApp.UpdateCommissionableDataProvider() called"); + + _commissionableDataProvider = new matter::casting::support::MCCommissionableDataProvider(); + VerifyOrReturnValue(_commissionableDataProvider->Initialize(_dataSource) == CHIP_NO_ERROR, + [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); + + ChipLogProgress(AppServer, "MCCastingApp.initializeWithDataSource() calling cpp CastingApp::UpdateCommissionableDataProvider()"); + // Update cpp CastingApp CommissionableDataProvider + VerifyOrReturnValue(matter::casting::core::CastingApp::GetInstance()->UpdateCommissionableDataProvider(_commissionableDataProvider) == CHIP_NO_ERROR, + [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); + + return [MCErrorUtils NSErrorFromChipError:CHIP_NO_ERROR]; +} + - (void)startWithCompletionBlock:(void (^)(NSError *))completion { ChipLogProgress(AppServer, "MCCastingApp.startWithCompletionBlock called"); diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp_Internal.h new file mode 100644 index 00000000000000..d9a4d8d0bc15d6 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp_Internal.h @@ -0,0 +1,37 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCCastingApp.h" + +#import + +#ifndef MCCastingApp_Internal_h +#define MCCastingApp_Internal_h + +@interface MCCastingApp () + +/** + * @brief (Private internal method) Updates the MCCommissionableDataProvider stored in this CastingApp with the updated CommissionableData + * to be used for the next commissioning session. Calling this function is mandatory for the + * Casting Player/Commissioner-Generated passcode commissioning flow, since the commissioning session's PAKE + * verifier needs to be updated with the user entered passcode. + */ +- (NSError *)updateCommissionableDataProvider; + +@end + +#endif /* MCCastingApp_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h index e796f25da609a0..a8439935a34360 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h @@ -15,7 +15,9 @@ * limitations under the License. */ +#import "MCConnectionCallbacks.h" #import "MCEndpointFilter.h" +#import "MCIdentificationDeclarationOptions.h" #import @@ -33,32 +35,113 @@ + (NSInteger)kMinCommissioningWindowTimeoutSec; /** - * @brief (async) Verifies that a connection exists with this CastingPlayer, or triggers a new session request. If the - * CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on disk, this will execute the user - * directed commissioning process. - * - * @param completion - called back when the connection process completes. Parameter is nil if it completed successfully - * @param timeout - time (in sec) to keep the commissioning window open, if commissioning is required. - * Needs to be >= CastingPlayer.kMinCommissioningWindowTimeoutSec. - * @param desiredEndpointFilter - 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 cached information about the CastingPlayer - * (if any) + * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new + * commissioning session request. If the CastingApp does not have the nodeId and fabricIndex + * of this CastingPlayer cached on disk, this will execute the User Directed Commissioning + * (UDC) process by sending an IdentificationDeclaration message to the Commissioner. For + * certain UDC features, where a Commissioner reply is expected, this API needs to be followed + * up with the continueConnecting() API defiend below. See the Matter UDC specification or + * parameter class definitions for details on features not included in the description below. + * @param connectionCallbacks contains the connectionCompleteCallback (Required) and + * commissionerDeclarationCallback (Optional) callbacks defiend in MCConnectionCallbacks. + *

For example: During CastingPlayer/Commissioner-Generated passcode commissioning, the + * Commissioner replies with a CommissionerDeclaration message with PasscodeDialogDisplayed + * and CommissionerPasscode set to true. Given these Commissioner state details, the client is + * expected to perform some actions, detailed in the continueConnecting() API below, and then + * call the continueConnecting() API to complete the process. + * @param timeout time (in sec) to keep the commissioning window open, if commissioning is required. + * Needs to be >= MCCastingPlayer.kMinCommissioningWindowTimeoutSec. + * @param identificationDeclarationOptions (Optional) Parameters in the IdentificationDeclaration + * message sent by the Commissionee to the Commissioner. These parameters specify the + * information relating to the requested commissioning session. + *

For example: To invoke the CastingPlayer/Commissioner-Generated passcode commissioning + * flow, the client would call this API with IdentificationDeclarationOptions containing + * CommissionerPasscode set to true. See IdentificationDeclarationOptions.java for a complete + * list of optional parameters. + *

Furthermore, attributes (such as VendorId) describe the TargetApp/Endpoint that the client + * wants to interact with after commissioning. If this value is passed in, + * verifyOrEstablishConnection() will force UDC, in case the desired TargetApp is not found in + * the on-device cached information/CastingStore. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. */ -- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion timeout:(long long)timeout desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter; +- (NSError * _Nullable)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks + timeout:(long)timeout + identificationDeclarationOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions; /** - * @brief (async) Verifies that a connection exists with this MCCastingPlayer, or triggers a new session request. If the - * MCCastingApp does not have the nodeId and fabricIndex of this MCCastingPlayer cached on disk, this will execute the user - * directed commissioning process. - * - * @param completion - called back when the connection process completes. Parameter is nil if it completed successfully - * @param desiredEndpointFilter - Attributes (such as VendorId) describing an MCEndpoint 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 cached information about the MCCastingPlayer - * (if any) + * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new + * commissioning session request. If the CastingApp does not have the nodeId and fabricIndex + * of this CastingPlayer cached on disk, this will execute the User Directed Commissioning + * (UDC) process by sending an IdentificationDeclaration message to the Commissioner. This method + * will run verifyOrEstablishConnection() with a default timeout of + * MCCastingPlayer.kMinCommissioningWindowTimeoutSec. + * @param identificationDeclarationOptions (Optional) Parameters in the IdentificationDeclaration + * message sent by the Commissionee to the Commissioner. These parameters specify the + * information relating to the requested commissioning session. + *

For example: To invoke the CastingPlayer/Commissioner-Generated passcode commissioning + * flow, the client would call this API with IdentificationDeclarationOptions containing + * CommissionerPasscode set to true. See IdentificationDeclarationOptions.java for a complete + * list of optional parameters. + *

Furthermore, attributes (such as VendorId) describe the TargetApp/Endpoint that the client + * wants to interact with after commissioning. If this value is passed in, + * verifyOrEstablishConnection() will force UDC, in case the desired TargetApp is not found in + * the on-device cached information/CastingStore. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. + * @see verifyOrEstablishConnectionWithCallbacks:timeout:identificationDeclarationOptions: + */ +- (NSError * _Nullable)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks + identificationDeclarationOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions; + +/** + * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new + * commissioning session request. If the CastingApp does not have the nodeId and fabricIndex + * of this CastingPlayer cached on disk, this will execute the User Directed Commissioning + * (UDC) process by sending an IdentificationDeclaration message to the Commissioner. This method + * will run verifyOrEstablishConnection() with a default timeout of + * MCCastingPlayer.kMinCommissioningWindowTimeoutSec and MCIdentificationDeclarationOptions + * initailized with the defualt values. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. + * @see verifyOrEstablishConnectionWithCallbacks:timeout:identificationDeclarationOptions: + */ +- (NSError * _Nullable)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks; + +/** + * @brief This is a continuation of the CastingPlayer/Commissioner-Generated passcode + * commissioning flow started via the verifyOrEstablishConnection() API above. It continues + * the UDC process by sending a second IdentificationDeclaration message to Commissioner + * containing CommissionerPasscode and CommissionerPasscodeReady set to true. At this point it + * is assumed that the following have occurred: + *

1. Client (Commissionee) has sent the first IdentificationDeclaration message, via + * verifyOrEstablishConnection(), to the Commissioner containing CommissionerPasscode set to + * true. + *

2. Commissioner generated and displayed a passcode. + *

3. The Commissioner replied with a CommissionerDecelration message with + * PasscodeDialogDisplayed and CommissionerPasscode set to true. + *

4. Client has handled the Commissioner's CommissionerDecelration message. + *

5. Client prompted user to input Passcode from Commissioner. + *

6. Client has updated the CastingApp's MCCommissionableDataProvider with the user entered + * passcode via the following function call: + * MCDataSource.update(MCCommissionableData). This updates the + * commissioning session's PAKE verifier with the user entered passcode. + *

Note: The same connectionCallbacks and commissioningWindowTimeoutSec parameters passed + * into verifyOrEstablishConnection() will be used. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. + */ +- (NSError * _Nullable)continueConnecting; + +/** + * @brief This cancels the CastingPlayer/Commissioner-Generated passcode commissioning flow + * started via the VerifyOrEstablishConnection() API above. It constructs and sends an + * IdentificationDeclaration message to the CastingPlayer/Commissioner containing + * CancelPasscode set to true. It is used to indicate that the user, and thus the + * Client/Commissionee, have cancelled the commissioning process. This indicates that the + * CastingPlayer/Commissioner can dismiss any dialogs corresponding to commissioning, such as + * a Passcode input dialog or a Passcode display dialog. + *

Note: stopConnecting() does not call the connectionCompleteCallback() callback passed to the + * VerifyOrEstablishConnection() API above since no connection is established. + * @return nil if request submitted successfully, otherwise a NSError object corresponding to the error. */ -- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter; +- (NSError * _Nullable)stopConnecting; /** * @brief Sets the internal connection state of this MCCastingPlayer to "disconnected" @@ -79,6 +162,7 @@ * @brief Returns the NSArray of MCEndpoints associated with this MCCastingPlayer */ - (NSArray * _Nonnull)endpoints; +- (void)logAllEndpoints; - (nonnull instancetype)init UNAVAILABLE_ATTRIBUTE; + (nonnull instancetype)new UNAVAILABLE_ATTRIBUTE; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm index f36c51728100ab..793f76ff7ee791 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm @@ -18,11 +18,16 @@ #import "MCCastingPlayer.h" #import "MCCastingApp.h" +#import "MCCastingApp_Internal.h" +#import "MCCommissionerDeclaration_Internal.h" +#import "MCConnectionCallbacks.h" #import "MCEndpoint_Internal.h" #import "MCErrorUtils.h" +#import "MCIdentificationDeclarationOptions_Internal.h" #import "core/CastingPlayer.h" // from tv-casting-common #import "core/ConnectionCallbacks.h" // from tv-casting-common +#import "core/IdentificationDeclarationOptions.h" // from tv-casting-common #import @@ -30,6 +35,8 @@ @interface MCCastingPlayer () @property (nonatomic, readwrite) matter::casting::memory::Strong cppCastingPlayer; +- (matter::casting::core::IdentificationDeclarationOptions)setupCppIdOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions; + @end @implementation MCCastingPlayer @@ -41,53 +48,141 @@ + (NSInteger)kMinCommissioningWindowTimeoutSec return kMinCommissioningWindowTimeoutSec; } -- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter +- (NSError *)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks { - [self verifyOrEstablishConnectionWithCompletionBlock:completion timeout:kMinCommissioningWindowTimeoutSec desiredEndpointFilter:desiredEndpointFilter]; + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() called, MCConnectionCallbacks parameter only"); + return [self verifyOrEstablishConnectionWithCallbacks:connectionCallbacks + timeout:kMinCommissioningWindowTimeoutSec + identificationDeclarationOptions:nil]; } -- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion timeout:(long long)timeout desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter +- (NSError *)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks + identificationDeclarationOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions { - ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCompletionBlock() called"); - VerifyOrReturn([[MCCastingApp getSharedInstance] isRunning], ChipLogError(AppServer, "MCCastingApp NOT running")); + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() called, MCConnectionCallbacks and MCIdentificationDeclarationOptions parameters"); + return [self verifyOrEstablishConnectionWithCallbacks:connectionCallbacks + timeout:kMinCommissioningWindowTimeoutSec + identificationDeclarationOptions:identificationDeclarationOptions]; +} + +- (NSError *)verifyOrEstablishConnectionWithCallbacks:(MCConnectionCallbacks * _Nonnull)connectionCallbacks + timeout:(long)timeout + identificationDeclarationOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions +{ + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() called, MCConnectionCallbacks, timeout and MCIdentificationDeclarationOptions parameters"); + VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], + [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE], + ChipLogError(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() MCCastingApp NOT running")); 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) { - 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()); - } - } + matter::casting::core::IdentificationDeclarationOptions cppIdOptions = [self setupCppIdOptions:identificationDeclarationOptions]; + // Handles the connection complete event and calls the MCConnectionCallbacks connectionCompleteCallback callback provided by + // the Swift client. This callback is called by the cpp layer when the connection process has ended, regardless of whether it + // was successful or not. void (^connectCallback)(CHIP_ERROR, matter::casting::core::CastingPlayer *) = ^(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) { - ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCompletionBlock() ConnectCallback()"); + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() connectCallback() called"); + dispatch_queue_t clientQueue = [[MCCastingApp getSharedInstance] getClientQueue]; + dispatch_async(clientQueue, ^{ + if (connectionCallbacks.connectionCompleteCallback) { + connectionCallbacks.connectionCompleteCallback(err == CHIP_NO_ERROR ? nil : [MCErrorUtils NSErrorFromChipError:err]); + } else { + ChipLogError(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() connectCallback(), client failed to set the connectionCompleteCallback() callback"); + } + }); + }; + // Handles the Commissioner Declaration event and calls the MCConnectionCallbacks commissionerDeclarationCallback callback + // provided by the Swift client. This callback is called by the cpp layer when the Commissionee receives a + // CommissionerDeclaration message from the CastingPlayer/Commissioner. + void (^commissionerDeclarationCallback)(const chip::Transport::PeerAddress & source, const chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cppCommissionerDeclaration) = ^(const chip::Transport::PeerAddress & + source, + const chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cppCommissionerDeclaration) { + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() commissionerDeclarationCallback() called with cpp CommissionerDeclaration message"); dispatch_queue_t clientQueue = [[MCCastingApp getSharedInstance] getClientQueue]; dispatch_async(clientQueue, ^{ - completion(err == CHIP_NO_ERROR ? nil : [MCErrorUtils NSErrorFromChipError:err]); + if (connectionCallbacks.commissionerDeclarationCallback) { + // convert cppCommissionerDeclaration to a shared_ptr and pass it to the client callback + auto cppCommissionerDeclarationPtr = std::make_shared(cppCommissionerDeclaration); + MCCommissionerDeclaration * objcCommissionerDeclaration = [[MCCommissionerDeclaration alloc] + initWithCppCommissionerDeclaration:cppCommissionerDeclarationPtr]; + connectionCallbacks.commissionerDeclarationCallback(objcCommissionerDeclaration); + } else { + ChipLogError(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() commissionerDeclarationCallback(), client failed to set the optional commissionerDeclarationCallback() callback"); + } }); }; - matter::casting::core::ConnectionCallbacks connectionCallbacks; - connectionCallbacks.mOnConnectionComplete = connectCallback; + matter::casting::core::ConnectionCallbacks cppConnectionCallbacks; + cppConnectionCallbacks.mOnConnectionComplete = connectCallback; + if (connectionCallbacks.commissionerDeclarationCallback) { + cppConnectionCallbacks.mCommissionerDeclarationCallback = commissionerDeclarationCallback; + } else { + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks(), client did not set the optional commissionerDeclarationCallback()"); + } - // TODO: In the following PRs. Add optional CommissionerDeclarationHandler callback parameter for the Commissioner-Generated passcode commissioning flow. - _cppCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, timeout, idOptions); + ChipLogProgress(AppServer, "MCCastingPlayer.verifyOrEstablishConnectionWithCallbacks() calling cpp CastingPlayer.VerifyOrEstablishConnection()"); + _cppCastingPlayer->VerifyOrEstablishConnection(cppConnectionCallbacks, timeout, cppIdOptions); }); + return nil; +} + +- (matter::casting::core::IdentificationDeclarationOptions)setupCppIdOptions:(MCIdentificationDeclarationOptions * _Nullable)identificationDeclarationOptions +{ + matter::casting::core::IdentificationDeclarationOptions cppIdOptions; + if (identificationDeclarationOptions != nil) { + cppIdOptions = [identificationDeclarationOptions getCppIdentificationDeclarationOptions]; + } else { + ChipLogProgress(AppServer, "MCCastingPlayer.setupCppIdOptions() Client did not set the optional MCIdentificationDeclarationOptions using default options"); + } + return cppIdOptions; +} + +- (NSError *)continueConnecting +{ + ChipLogProgress(AppServer, "MCCastingPlayer.continueConnecting() called"); + VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE], + ChipLogError(AppServer, "MCCastingPlayer.continueConnecting() MCCastingApp NOT running")); + + ChipLogProgress(AppServer, "MCCastingPlayer.continueConnecting() calling MCCastingApp.updateCommissionableDataProvider()"); + NSError * updateError = [[MCCastingApp getSharedInstance] updateCommissionableDataProvider]; + VerifyOrReturnValue(updateError == nil, updateError, ChipLogError(AppServer, "MCCastingPlayer.continueConnecting() call to updateCommissionableDataProvider() failed with error: %@", updateError)); + + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue]; + dispatch_sync(workQueue, ^{ + err = _cppCastingPlayer->ContinueConnecting(); + }); + if (err != CHIP_NO_ERROR) { + ChipLogError(AppServer, "MCCastingPlayer.continueConnecting() call to cppCastingPlayer->ContinueConnecting() failed due to %" CHIP_ERROR_FORMAT, + err.Format()); + return [MCErrorUtils NSErrorFromChipError:err]; + } + return nil; +} + +- (NSError *)stopConnecting +{ + ChipLogProgress(AppServer, "MCCastingPlayer.stopConnecting() called"); + VerifyOrReturnValue([[MCCastingApp getSharedInstance] isRunning], [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE], ChipLogError(AppServer, "MCCastingPlayer.stopConnecting() MCCastingApp NOT running")); + + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue]; + dispatch_sync(workQueue, ^{ + err = _cppCastingPlayer->StopConnecting(); + }); + if (err != CHIP_NO_ERROR) { + ChipLogError(AppServer, "MCCastingPlayer.continueConnecting() call to cppCastingPlayer->StopConnecting() failed due to %" CHIP_ERROR_FORMAT, + err.Format()); + return [MCErrorUtils NSErrorFromChipError:err]; + } + return nil; } - (void)disconnect { - ChipLogProgress(AppServer, "MCCastingPlayer.disconnect called"); - VerifyOrReturn([[MCCastingApp getSharedInstance] isRunning], ChipLogError(AppServer, "MCCastingApp NOT running")); + ChipLogProgress(AppServer, "MCCastingPlayer.disconnect() called"); + VerifyOrReturn([[MCCastingApp getSharedInstance] isRunning], ChipLogError(AppServer, "MCCastingPlayer.disconnect() MCCastingApp NOT running")); dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue]; dispatch_sync(workQueue, ^{ @@ -119,8 +214,9 @@ + (MCCastingPlayer * _Nullable)getTargetCastingPlayer - (NSString * _Nonnull)description { - return [NSString stringWithFormat:@"%@ with Product ID: %hu and Vendor ID: %hu. Resolved IPAddr?: %@. Supports Commissioner Generated Passcode?: %@.", - self.deviceName, self.productId, self.vendorId, self.ipAddresses != nil && self.ipAddresses.count > 0 ? @"YES" : @"NO", self.supportsCommissionerGeneratedPasscode ? @"YES" : @"NO"]; + return [NSString stringWithFormat:@"%@ with Product ID: %hu and Vendor ID: %hu. Resolved IPAddr?: %@. Supports Commissioner-Generated Passcode?: %@.", + self.deviceName, self.productId, self.vendorId, self.ipAddresses != nil && self.ipAddresses.count > 0 ? @"YES" : @"NO", + self.supportsCommissionerGeneratedPasscode ? @"YES" : @"NO"]; } - (NSString * _Nonnull)identifier @@ -217,4 +313,13 @@ - (NSUInteger)hash return result; } +- (void)logAllEndpoints +{ + NSArray * endpointsArray = [self endpoints]; + ChipLogDetail(AppServer, "MCCastingPlayer logAllEndpoints():"); + for (MCEndpoint * endpoint in endpointsArray) { + ChipLogDetail(AppServer, "MCCastingPlayer MCEndpoint details: %@", [endpoint description]); + } +} + @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionableDataProvider.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionableDataProvider.mm index 83159b2801f09a..5498ac98e9c38c 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionableDataProvider.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionableDataProvider.mm @@ -50,9 +50,11 @@ CHIP_ERROR GeneratePaseSalt(std::vector & spake2pSaltVector) CHIP_ERROR MCCommissionableDataProvider::Initialize(id dataSource) { + ChipLogProgress(Support, "MCCommissionableDataProvider Initialize()"); VerifyOrReturnLogError(dataSource != nil, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnLogError(mDataSource == nullptr, CHIP_ERROR_INCORRECT_STATE); + ChipLogProgress(Support, "MCCommissionableDataProvider Initialize() calling MCCommissionableData castingAppDidReceiveRequestForCommissionableData()"); mDataSource = dataSource; MCCommissionableData * commissionableData = [mDataSource castingAppDidReceiveRequestForCommissionableData:@"MCCommissionableDataProvider.Initialize()"]; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.h new file mode 100644 index 00000000000000..85bc465c667ab8 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.h @@ -0,0 +1,92 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#import + +#ifndef MCCommissionerDeclaration_h +#define MCCommissionerDeclaration_h + +/** + * Represents the Commissioner Declaration message sent by a User Directed Commissioning server + * (CastingPlayer/Commissioner) to a UDC client (Casting Client/Commissionee). + */ +@interface MCCommissionerDeclaration : NSObject + +/** The allowed values for the ErrorCode field are the following */ +typedef NS_ENUM(NSInteger, CdError) { + kNoError = 0, + kCommissionableDiscoveryFailed = 1, + kPaseConnectionFailed = 2, + kPaseAuthFailed = 3, + kDacValidationFailed = 4, + kAlreadyOnFabric = 5, + kOperationalDiscoveryFailed = 6, + kCaseConnectionFailed = 7, + kCaseAuthFailed = 8, + kConfigurationFailed = 9, + kBindingConfigurationFailed = 10, + kCommissionerPasscodeNotSupported = 11, + kInvalidIdentificationDeclarationParams = 12, + kAppInstallConsentPending = 13, + kAppInstalling = 14, + kAppInstallFailed = 15, + kAppInstalledRetryNeeded = 16, + kCommissionerPasscodeDisabled = 17, + kUnexpectedCommissionerPasscodeReady = 18 +}; + +/** Feature: All - Indicates errors incurred during commissioning. */ +@property (nonatomic, readonly) CdError errorCode; +/** + * Feature: Coordinate PIN Dialogs - When NoPasscode field set to true, and the Commissioner + * determines that a Passcode code will be needed for commissioning. + */ +@property (nonatomic, readonly) BOOL needsPasscode; +/** + * Feature: Target Content Application - No apps with AccountLogin cluster implementation were + * found for the last IdentificationDeclaration request. Only apps which provide access to the + * vendor id of the Commissionee will be considered. + */ +@property (nonatomic, readonly) BOOL noAppsFound; +/** + * Feature: Coordinate PIN Dialogs - A Passcode input dialog is now displayed for the user on the + * Commissioner. + */ +@property (nonatomic, readonly) BOOL passcodeDialogDisplayed; +/** + * Feature: Commissioner-Generated Passcode - A Passcode is now displayed for the user by the + * CastingPlayer/Commissioner. + */ +@property (nonatomic, readonly) BOOL commissionerPasscode; +/** + * Feature: Commissioner-Generated Passcode - The user experience conveying a Passcode to the user + * also displays a QR code. + */ +@property (nonatomic, readonly) BOOL qRCodeDisplayed; + +- (instancetype)initWithOptions:(NSInteger)errorCode + needsPasscode:(BOOL)needsPasscode + noAppsFound:(BOOL)noAppsFound + passcodeDialogDisplayed:(BOOL)passcodeDialogDisplayed + commissionerPasscode:(BOOL)commissionerPasscode + qRCodeDisplayed:(BOOL)qRCodeDisplayed; + +- (NSString *)description; +- (void)logDetail; + +@end + +#endif /* MCCommissionerDeclaration_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.mm new file mode 100644 index 00000000000000..1663f1626bb1f8 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration.mm @@ -0,0 +1,124 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCCommissionerDeclaration_Internal.h" +#import "core/Types.h" + +@interface MCCommissionerDeclaration () + +@property (nonatomic, readwrite) matter::casting::memory::Strong cppCommissionerDeclaration; + +@end + +@implementation MCCommissionerDeclaration + +- (instancetype _Nonnull)initWithCppCommissionerDeclaration:(std::shared_ptr)cppCommissionerDeclaration +{ + if (self = [super init]) { + _cppCommissionerDeclaration = cppCommissionerDeclaration; + _errorCode = static_cast(cppCommissionerDeclaration->GetErrorCode()); + _needsPasscode = cppCommissionerDeclaration->GetNeedsPasscode(); + _noAppsFound = cppCommissionerDeclaration->GetNoAppsFound(); + _passcodeDialogDisplayed = cppCommissionerDeclaration->GetPasscodeDialogDisplayed(); + _commissionerPasscode = cppCommissionerDeclaration->GetCommissionerPasscode(); + _qRCodeDisplayed = cppCommissionerDeclaration->GetQRCodeDisplayed(); + } + return self; +} + +- (instancetype)initWithOptions:(NSInteger)errorCode + needsPasscode:(BOOL)needsPasscode + noAppsFound:(BOOL)noAppsFound + passcodeDialogDisplayed:(BOOL)passcodeDialogDisplayed + commissionerPasscode:(BOOL)commissionerPasscode + qRCodeDisplayed:(BOOL)qRCodeDisplayed +{ + self = [super init]; + if (self) { + _errorCode = (CdError) errorCode; + _needsPasscode = needsPasscode; + _noAppsFound = noAppsFound; + _passcodeDialogDisplayed = passcodeDialogDisplayed; + _commissionerPasscode = commissionerPasscode; + _qRCodeDisplayed = qRCodeDisplayed; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"MCCommissionerDeclaration::errorCode: %@\nMCCommissionerDeclaration::needsPasscode: %d\nMCCommissionerDeclaration::noAppsFound: %d\nMCCommissionerDeclaration::passcodeDialogDisplayed: %d\nMCCommissionerDeclaration::commissionerPasscode: %d\nMCCommissionerDeclaration::qRCodeDisplayed: %d", + [self stringForErrorCode:self.errorCode], + self.needsPasscode, + self.noAppsFound, + self.passcodeDialogDisplayed, + self.commissionerPasscode, + self.qRCodeDisplayed]; +} + +- (NSString *)stringForErrorCode:(CdError)errorCode +{ + switch (errorCode) { + case kNoError: + return @"kNoError"; + case kCommissionableDiscoveryFailed: + return @"kCommissionableDiscoveryFailed"; + case kPaseConnectionFailed: + return @"kPaseConnectionFailed"; + case kPaseAuthFailed: + return @"kPaseAuthFailed"; + case kDacValidationFailed: + return @"kDacValidationFailed"; + case kAlreadyOnFabric: + return @"kAlreadyOnFabric"; + case kOperationalDiscoveryFailed: + return @"kOperationalDiscoveryFailed"; + case kCaseConnectionFailed: + return @"kCaseConnectionFailed"; + case kCaseAuthFailed: + return @"kCaseAuthFailed"; + case kConfigurationFailed: + return @"kConfigurationFailed"; + case kBindingConfigurationFailed: + return @"kBindingConfigurationFailed"; + case kCommissionerPasscodeNotSupported: + return @"kCommissionerPasscodeNotSupported"; + case kInvalidIdentificationDeclarationParams: + return @"kInvalidIdentificationDeclarationParams"; + case kAppInstallConsentPending: + return @"kAppInstallConsentPending"; + case kAppInstalling: + return @"kAppInstalling"; + case kAppInstallFailed: + return @"kAppInstallFailed"; + case kAppInstalledRetryNeeded: + return @"kAppInstalledRetryNeeded"; + case kCommissionerPasscodeDisabled: + return @"kCommissionerPasscodeDisabled"; + case kUnexpectedCommissionerPasscodeReady: + return @"kUnexpectedCommissionerPasscodeReady"; + default: + return @"Unknown Error"; + } +} + +- (void)logDetail +{ + ChipLogDetail(AppServer, "MCCommissionerDeclaration::logDetail()\n%@", [self description]); +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration_Internal.h new file mode 100644 index 00000000000000..51b895f2424fb0 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommissionerDeclaration_Internal.h @@ -0,0 +1,32 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCCommissionerDeclaration.h" +#import "core/Types.h" + +#import + +#ifndef MCCommissionerDeclaration_Internal_h +#define MCCommissionerDeclaration_Internal_h + +@interface MCCommissionerDeclaration () + +- (instancetype _Nonnull)initWithCppCommissionerDeclaration:(std::shared_ptr)cppCommissionerDeclaration; + +@end + +#endif /* MCCommissionerDeclaration_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.h new file mode 100644 index 00000000000000..e5a0007b2c7e2c --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.h @@ -0,0 +1,62 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCCommissionerDeclaration.h" + +#import + +#ifndef MCConnectionCallbacks_h +#define MCConnectionCallbacks_h + +/** @brief A container class for User Directed Commissioning (UDC) callbacks. */ +@interface MCConnectionCallbacks : NSObject + +/** + * @param connectionCompleteCallback (Required) The callback called when the connection process + * has ended, regardless of whether it was successful or not. + * @param commissionerDeclarationCallback (Optional) The callback called when the Client/Commissionee + * receives a CommissionerDeclaration message from the CastingPlayer/Commissioner. This callback is + * needed to support UDC features where a reply from the Commissioner is expected. It provides information + * indicating the Commissioner’s pre-commissioning state. + * + * For example: During CastingPlayer/Commissioner-Generated passcode commissioning, the Commissioner + * replies with a CommissionerDeclaration message with PasscodeDialogDisplayed and CommissionerPasscode + * set to true. Given these Commissioner state details, the client is expected to perform some actions + * and responf accrdingly. + * + * @return A new instance of MCConnectionCallbacks. + */ +- (instancetype _Nonnull)initWithCallbacks:(void (^_Nonnull)(NSError * _Nonnull))connectionCompleteCallback + commissionerDeclarationCallback:(void (^_Nullable)(MCCommissionerDeclaration * _Nonnull))commissionerDeclarationCallback; + +/** + * The callback called when the connection process has ended, regardless of whether it was + * successful or not. + */ +@property void (^_Nullable connectionCompleteCallback)(NSError * _Nonnull); + +/** + * The callback called when the Client/Commissionee receives a CommissionerDeclaration + * message from the CastingPlayer/Commissioner. This callback is needed to support UDC features + * where a reply from the Commissioner is expected. It provides information indicating the + * Commissioner’s pre-commissioning state. + */ +@property void (^_Nullable commissionerDeclarationCallback)(MCCommissionerDeclaration * _Nonnull); + +@end + +#endif /* MCConnectionCallbacks_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.mm new file mode 100644 index 00000000000000..5a5e1e541efbb1 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCConnectionCallbacks.mm @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCConnectionCallbacks.h" + +@implementation MCConnectionCallbacks + +- (instancetype _Nonnull)initWithCallbacks:(void (^_Nonnull)(NSError * _Nonnull))connectionCompleteCallback + commissionerDeclarationCallback:(void (^_Nullable)(MCCommissionerDeclaration * _Nonnull))commissionerDeclarationCallback +{ + self = [super init]; + if (self) { + self.connectionCompleteCallback = connectionCompleteCallback; + self.commissionerDeclarationCallback = commissionerDeclarationCallback; + } + return self; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h index db340f097764f4..cacc1da4d8a750 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h @@ -41,6 +41,8 @@ - (BOOL)hasCluster:(MCEndpointClusterType)type; - (MCCluster * _Nullable)clusterForType:(MCEndpointClusterType)type; +- (NSString * _Nonnull)description; + @end #endif /* MCEndpoint_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm index 76d1eaf70724f9..24b5c8639aa6e2 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm @@ -154,4 +154,10 @@ - (NSUInteger)hash return result; } + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", [self identifier], [self vendorId], [self productId]]; +} + @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.h new file mode 100644 index 00000000000000..98969576a256ed --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.h @@ -0,0 +1,79 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#import "MCTargetAppInfo.h" +#import + +#ifndef MCIdentificationDeclarationOptions_h +#define MCIdentificationDeclarationOptions_h + +/** + * This class contains the optional parameters used in the IdentificationDeclaration Message, sent + * by the Commissionee (CastingApp) to the Commissioner (CastingPlayer). The options specify + * information relating to the requested UDC commissioning session. + */ +@interface MCIdentificationDeclarationOptions : NSObject + +/** + * 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. + */ +@property (nonatomic, readonly) BOOL noPasscode; +/** + * 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. + */ +@property (nonatomic, readonly) BOOL cdUponPasscodeDialog; +/** + * Feature: Commissioner-Generated Passcode - Flag to instruct the Commissioner to use the + * Commissioner-generated Passcode for commissioning. + */ +@property (nonatomic, readonly) BOOL commissionerPasscode; +/** + * 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. + */ +@property (nonatomic, readonly) BOOL commissionerPasscodeReady; +/** + * Feature: Coordinate Passcode Dialogs Flag - to indicate when the Commissionee user has decided + * to exit the commissioning process. + */ +@property (nonatomic, readonly) BOOL cancelPasscode; + +- (instancetype)init; + +- (instancetype)initWithCommissionerPasscodeOnly:(BOOL)commissionerPasscode; + +/** + * @brief Adds a TargetAppInfo to the IdentificationDeclarationOptions.java TargetAppInfos list, + * up to a maximum of CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS. + */ +- (BOOL)addTargetAppInfo:(MCTargetAppInfo *)targetAppInfo NS_SWIFT_NAME(addTargetAppInfo(_:)); +/** + * 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. + */ +- (NSArray *)getTargetAppInfoList; + +- (NSString *)description; + +@end + +#endif /* MCIdentificationDeclarationOptions_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.mm new file mode 100644 index 00000000000000..9d7d1f53ef5110 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions.mm @@ -0,0 +1,144 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#import "MCIdentificationDeclarationOptions.h" + +#import "core/IdentificationDeclarationOptions.h" // from tv-casting-common +#import "core/Types.h" // from tv-casting-common + +@interface MCIdentificationDeclarationOptions () + +/** + * 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. + */ +@property (nonatomic, strong) NSMutableArray * targetAppInfos; + +@end + +@implementation MCIdentificationDeclarationOptions + +- (instancetype)init +{ + self = [super init]; + if (self) { + _noPasscode = NO; + _cdUponPasscodeDialog = NO; + _commissionerPasscode = NO; + _commissionerPasscodeReady = NO; + _cancelPasscode = NO; + _targetAppInfos = [[NSMutableArray alloc] init]; + } + return self; +} + +- (instancetype)initWithCommissionerPasscodeOnly:(BOOL)commissionerPasscode +{ + self = [super init]; + if (self) { + _noPasscode = NO; + _cdUponPasscodeDialog = NO; + _commissionerPasscode = commissionerPasscode; + _commissionerPasscodeReady = NO; + _cancelPasscode = NO; + _targetAppInfos = [[NSMutableArray alloc] init]; + } + return self; +} + +// Getter methods +- (BOOL)getNoPasscode +{ + return _noPasscode; +} + +- (BOOL)getCdUponPasscodeDialog +{ + return _cdUponPasscodeDialog; +} + +- (BOOL)getCommissionerPasscode +{ + return _commissionerPasscode; +} + +- (BOOL)getCommissionerPasscodeReady +{ + return _commissionerPasscodeReady; +} + +- (BOOL)getCancelPasscode +{ + return _cancelPasscode; +} + +- (BOOL)addTargetAppInfo:(MCTargetAppInfo *)targetAppInfo +{ + if (self.targetAppInfos.count >= CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS) { + ChipLogError(AppServer, "MCIdentificationDeclarationOptions addTargetAppInfo() failed to add TargetAppInfo, max targetAppInfos list size is: %d", CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS); + return NO; + } + [self.targetAppInfos addObject:targetAppInfo]; + return YES; +} + +- (NSArray *)getTargetAppInfoList +{ + return [self.targetAppInfos copy]; +} + +- (NSString *)description +{ + NSMutableString * sb = [NSMutableString stringWithFormat:@"MCIdentificationDeclarationOptions::noPasscode: %d\n", self.noPasscode]; + [sb appendFormat:@"MCIdentificationDeclarationOptions::cdUponPasscodeDialog: %d\n", self.cdUponPasscodeDialog]; + [sb appendFormat:@"MCIdentificationDeclarationOptions::commissionerPasscode: %d\n", self.commissionerPasscode]; + [sb appendFormat:@"MCIdentificationDeclarationOptions::commissionerPasscodeReady: %d\n", self.commissionerPasscodeReady]; + [sb appendFormat:@"MCIdentificationDeclarationOptions::cancelPasscode: %d\n", self.cancelPasscode]; + [sb appendString:@"MCIdentificationDeclarationOptions::targetAppInfos list:\n"]; + + for (MCTargetAppInfo * targetAppInfo in self.targetAppInfos) { + [sb appendFormat:@"\t\t%@\n", [targetAppInfo description]]; + } + + return [sb copy]; +} + +- (matter::casting::core::IdentificationDeclarationOptions)getCppIdentificationDeclarationOptions +{ + matter::casting::core::IdentificationDeclarationOptions cppIdOptions; + cppIdOptions.mNoPasscode = [self getNoPasscode]; + cppIdOptions.mCdUponPasscodeDialog = [self getCdUponPasscodeDialog]; + cppIdOptions.mCommissionerPasscode = [self getCommissionerPasscode]; + cppIdOptions.mCommissionerPasscodeReady = [self getCommissionerPasscodeReady]; + cppIdOptions.mCancelPasscode = [self getCancelPasscode]; + + NSArray * targetAppInfos = [self getTargetAppInfoList]; + for (MCTargetAppInfo * appInfo in targetAppInfos) { + chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; + targetAppInfo.vendorId = appInfo.vendorId; + targetAppInfo.productId = appInfo.productId; + CHIP_ERROR err = cppIdOptions.addTargetAppInfo(targetAppInfo); + if (err != CHIP_NO_ERROR) { + ChipLogError(AppServer, "MCIdentificationDeclarationOptions.getCppIdentificationDeclarationOptions() Failed to add TargetAppInfo: %@", appInfo.description); + } + } + + return cppIdOptions; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions_Internal.h new file mode 100644 index 00000000000000..8de257b0cbdbf8 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCIdentificationDeclarationOptions_Internal.h @@ -0,0 +1,32 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCIdentificationDeclarationOptions.h" +#import "core/IdentificationDeclarationOptions.h" // from tv-casting-common + +#import + +#ifndef MCIdentificationDeclarationOptions_Internal_h +#define MCIdentificationDeclarationOptions_Internal_h + +@interface MCIdentificationDeclarationOptions () + +- (matter::casting::core::IdentificationDeclarationOptions)getCppIdentificationDeclarationOptions; + +@end + +#endif /* MCIdentificationDeclarationOptions_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.h new file mode 100644 index 00000000000000..b5f0cb052c458e --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.h @@ -0,0 +1,49 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import + +#ifndef MCTargetAppInfo_h +#define MCTargetAppInfo_h + +/** + * @brief Feature: Target Content Application - An entry in the TargetAppList which contains + * a Target vendorId and an optional Target productId. + */ +@interface MCTargetAppInfo : NSObject + +/** Target Target Content Application Vendor ID, 0 means unspecified */ +@property (nonatomic, readonly) uint16_t vendorId; + +/** Target Target Content Application Product ID, 0 means unspecified */ +@property (nonatomic, readonly) uint16_t productId; + +/** Initialize with vendorId and productId set to 0 */ +- (instancetype)init; + +/** Initialize with vendorId, productId defaults to 0 */ +- (instancetype _Nonnull)initWithVendorId:(uint16_t)vendorId; + +/** Initialize with vendorId and productId */ +- (instancetype _Nonnull)initWithVendorId:(uint16_t)vendorId productId:(uint16_t)productId NS_DESIGNATED_INITIALIZER; + +/** Description method */ +- (NSString * _Nonnull)description; + +@end + +#endif /* MCTargetAppInfo_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.mm new file mode 100644 index 00000000000000..8c35d433b8318a --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCTargetAppInfo.mm @@ -0,0 +1,52 @@ +/** + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MCTargetAppInfo.h" + +@implementation MCTargetAppInfo + +- (instancetype)init +{ + self = [super init]; + if (self) { + _vendorId = 0; + _productId = 0; + } + return self; +} + +- (instancetype)initWithVendorId:(uint16_t)vendorId +{ + return [self initWithVendorId:vendorId productId:0]; +} + +- (instancetype)initWithVendorId:(uint16_t)vendorId productId:(uint16_t)productId +{ + self = [super init]; + if (self) { + _vendorId = vendorId; + _productId = productId; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"MCTargetAppInfo: vendorId: %d, productId: %d", self.vendorId, self.productId]; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h index 5e83cea90b0728..ed168df027fe48 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h @@ -33,11 +33,15 @@ FOUNDATION_EXPORT const unsigned char MatterTvCastingBridgeVersionString[]; #import "MCCluster.h" #import "MCCommand.h" #import "MCCommissionableData.h" +#import "MCCommissionerDeclaration.h" +#import "MCConnectionCallbacks.h" #import "MCCryptoUtils.h" #import "MCDataSource.h" #import "MCDeviceAttestationCredentials.h" #import "MCEndpoint.h" #import "MCEndpointFilter.h" +#import "MCIdentificationDeclarationOptions.h" +#import "MCTargetAppInfo.h" #import "zap-generated/MCAttributeObjects.h" #import "zap-generated/MCClusterObjects.h" #import "zap-generated/MCCommandObjects.h" diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/compat-shim/CastingServerBridge.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/compat-shim/CastingServerBridge.mm index ca7ccb3162fa8c..d4d1482d354c52 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/compat-shim/CastingServerBridge.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/compat-shim/CastingServerBridge.mm @@ -24,8 +24,10 @@ #import "MCCastingApp.h" #import "MCCastingPlayerDiscovery.h" #import "MCCastingPlayer_Internal.h" +#import "MCConnectionCallbacks.h" #import "MCEndpoint.h" #import "MCErrorUtils.h" +#import "MCIdentificationDeclarationOptions.h" #import "MatterCallbacks.h" #import "OnboardingPayload.h" @@ -226,11 +228,19 @@ - (void)sendUserDirectedCommissioningRequest:(DiscoveredNodeData * _Nonnull)comm { ChipLogProgress(AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() called with desiredContentAppVendorId: %d", desiredContentAppVendorId); - MCEndpointFilter * filter = [MCEndpointFilter new]; - filter.vendorId = desiredContentAppVendorId; + MCIdentificationDeclarationOptions * identificationDeclarationOptions = [[MCIdentificationDeclarationOptions alloc] init]; + MCTargetAppInfo * targetAppInfo = [[MCTargetAppInfo alloc] initWithVendorId:desiredContentAppVendorId]; + BOOL success = [identificationDeclarationOptions addTargetAppInfo:targetAppInfo]; + if (success) { + ChipLogProgress(AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() Target app info added successfully"); + } else { + ChipLogProgress(AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() Failed to add target app info"); + } + ChipLogProgress(AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() MCIdentificationDeclarationOptions: \n%@", [identificationDeclarationOptions description]); - [commissioner.getCastingPlayer verifyOrEstablishConnectionWithCompletionBlock:^(NSError * _Nullable err) { + void (^connectionCompletionBlock)(NSError * _Nullable) = ^(NSError * _Nullable err) { dispatch_async(clientQueue, ^{ + ChipLogError(AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() connectionCompleteCallback() completed with error: %@", err.description); if (err == nil) { if (self->_commissioningCallbackHandlers != nil && self->_commissioningCallbackHandlers.commissioningCompleteCallback != nil) { self->_commissioningCallbackHandlers.commissioningCompleteCallback(MATTER_NO_ERROR); @@ -249,7 +259,11 @@ - (void)sendUserDirectedCommissioningRequest:(DiscoveredNodeData * _Nonnull)comm } } }); - } desiredEndpointFilter:filter]; + }; + + MCConnectionCallbacks * connectionCallbacks = [[MCConnectionCallbacks alloc] initWithCallbacks:connectionCompletionBlock commissionerDeclarationCallback:nil]; + + [commissioner.getCastingPlayer verifyOrEstablishConnectionWithCallbacks:connectionCallbacks identificationDeclarationOptions:identificationDeclarationOptions]; dispatch_async(clientQueue, ^{ udcRequestSentHandler(MATTER_NO_ERROR); diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj index 2d4a142cd515a5..7716d722be4c43 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 39231BEA2C24F90200ADFB3A /* MCEndpointSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39231BE92C24F90200ADFB3A /* MCEndpointSelector.swift */; }; 3C40586E2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C40586D2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift */; }; 3C4058702B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C40586F2B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift */; }; 3C4F52302B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */; }; @@ -54,6 +55,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 39231BE92C24F90200ADFB3A /* MCEndpointSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCEndpointSelector.swift; sourceTree = ""; }; 3C40586D2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCMediaPlaybackSubscribeToCurrentStateExampleView.swift; sourceTree = ""; }; 3C40586F2B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift; sourceTree = ""; }; 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCContentLauncherLaunchURLExampleViewModel.swift; sourceTree = ""; }; @@ -158,6 +160,7 @@ 3C94377C2B364D380096E5F4 /* MCDiscoveryExampleViewModel.swift */, 3C94378F2B3B3FF90096E5F4 /* MCConnectionExampleView.swift */, 3C94377E2B364D510096E5F4 /* MCConnectionExampleViewModel.swift */, + 39231BE92C24F90200ADFB3A /* MCEndpointSelector.swift */, 3C621CB42B607FFD005CDBA3 /* MCActionSelectorView.swift */, 3C4F52312B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift */, 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */, @@ -266,6 +269,7 @@ 3C94377F2B364D510096E5F4 /* MCConnectionExampleViewModel.swift in Sources */, 3C4058702B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift in Sources */, 3CCB8745286A5D0F00771BAD /* CommissionerDiscoveryView.swift in Sources */, + 39231BEA2C24F90200ADFB3A /* MCEndpointSelector.swift in Sources */, 3C621CB12B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift in Sources */, 3CCB8746286A5D0F00771BAD /* CommissionerDiscoveryViewModel.swift in Sources */, 3C4F52302B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift in Sources */, diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift index 32e20e4b324ca3..a95580f5aaf7cd 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift @@ -30,66 +30,68 @@ class MCApplicationBasicReadVendorIDExampleViewModel: ObservableObject { func read(castingPlayer: MCCastingPlayer) { - // select the MCEndpoint on the MCCastingPlayer to invoke the command on - if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first + self.Log.info("MCApplicationBasicReadVendorIDExampleViewModel.read()") + castingPlayer.logAllEndpoints() + + // Use MCEndpointSelector to select the endpoint + guard let endpoint = MCEndpointSelector.selectEndpoint(from: castingPlayer, sampleEndpointVid: sampleEndpointVid) else { + self.Log.error("MCApplicationBasicReadVendorIDExampleViewModel.read() No endpoint matching the example VID or identifier 1 found") + DispatchQueue.main.async { + self.status = "No endpoint matching the example VID or identifier 1 found" + } + return + } + + self.Log.info("MCApplicationBasicReadVendorIDExampleViewModel.read() selected endpoint: \(endpoint.description)") + + // validate that the selected endpoint supports the ApplicationBasic cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeApplicationBasic)) { - // validate that the selected endpoint supports the ApplicationBasic cluster - if(!endpoint.hasCluster(MCEndpointClusterTypeApplicationBasic)) + self.Log.error("No ApplicationBasic cluster supporting endpoint found") + DispatchQueue.main.async { - self.Log.error("No ApplicationBasic cluster supporting endpoint found") - DispatchQueue.main.async - { - self.status = "No ApplicationBasic cluster supporting endpoint found" - } - return + self.status = "No ApplicationBasic cluster supporting endpoint found" } - - // get ApplicationBasic cluster from the endpoint - let applicationBasiccluster: MCApplicationBasicCluster = endpoint.cluster(for: MCEndpointClusterTypeApplicationBasic) as! MCApplicationBasicCluster + return + } + + // get ApplicationBasic cluster from the endpoint + let applicationBasiccluster: MCApplicationBasicCluster = endpoint.cluster(for: MCEndpointClusterTypeApplicationBasic) as! MCApplicationBasicCluster - // get the vendorIDAttribute from the applicationBasiccluster - let vendorIDAttribute: MCApplicationBasicClusterVendorIDAttribute? = applicationBasiccluster.vendorIDAttribute() - if(vendorIDAttribute == nil) + // get the vendorIDAttribute from the applicationBasiccluster + let vendorIDAttribute: MCApplicationBasicClusterVendorIDAttribute? = applicationBasiccluster.vendorIDAttribute() + if(vendorIDAttribute == nil) + { + self.Log.error("VendorID attribute not supported on cluster") + DispatchQueue.main.async { - self.Log.error("VendorID attribute not supported on cluster") - DispatchQueue.main.async - { - self.status = "VendorID attribute not supported on cluster" - } - return - } - - - // call read on vendorIDAttribute and pass in a completion block - vendorIDAttribute!.read(nil) { context, before, after, err in - DispatchQueue.main.async - { - if(err != nil) - { - self.Log.error("Error when reading VendorID value \(String(describing: err))") - self.status = "Error when reading VendorID value \(String(describing: err))" - return - } - - if(before != nil) - { - self.Log.info("Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))") - self.status = "Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))" - } - else - { - self.Log.info("Read VendorID value: \(String(describing: after))") - self.status = "Read VendorID value: \(String(describing: after))" - } - } + self.status = "VendorID attribute not supported on cluster" } + return } - else - { - self.Log.error("No endpoint matching the example VID found") + + + // call read on vendorIDAttribute and pass in a completion block + vendorIDAttribute!.read(nil) { context, before, after, err in DispatchQueue.main.async { - self.status = "No endpoint matching the example VID found" + if(err != nil) + { + self.Log.error("Error when reading VendorID value \(String(describing: err))") + self.status = "Error when reading VendorID value \(String(describing: err))" + return + } + + if(before != nil) + { + self.Log.info("Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))") + self.status = "Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))" + } + else + { + self.Log.info("Read VendorID value: \(String(describing: after))") + self.status = "Read VendorID value: \(String(describing: after))" + } } } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift index 87448bf46b4790..36ac837df1fa77 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift @@ -17,19 +17,32 @@ import SwiftUI +import os.log struct MCConnectionExampleView: View { + let Log = Logger(subsystem: "com.matter.casting", + category: "MCConnectionExampleView") var selectedCastingPlayer: MCCastingPlayer? + var useCommissionerGeneratedPasscode: Bool @StateObject var viewModel = MCConnectionExampleViewModel(); - init(_selectedCastingPlayer: MCCastingPlayer?) { + init(_selectedCastingPlayer: MCCastingPlayer?, _useCommissionerGeneratedPasscode: Bool) { self.selectedCastingPlayer = _selectedCastingPlayer + self.useCommissionerGeneratedPasscode = _useCommissionerGeneratedPasscode } var body: some View { VStack(alignment: .leading) { - Text("Verifying or Establishing Connection to Casting Player: \(self.selectedCastingPlayer!.deviceName())").padding() + if self.useCommissionerGeneratedPasscode { + if selectedCastingPlayer?.supportsCommissionerGeneratedPasscode() == true { + Text("Verifying or Establishing Connection, using Casting Player/Commissioner-Generated passcode, with Casting Player: \(self.selectedCastingPlayer!.deviceName())\n\nEnter the passcode displayed on the Casting Player when prompted.").padding() + } else { + Text("\(self.selectedCastingPlayer!.deviceName()) does not support Casting Player/Commissioner-Generated passcode commissioning. \n\nSelect a different Casting Player.").padding() + } + } else { + Text("Verifying or Establishing Connection to Casting Player: \(self.selectedCastingPlayer!.deviceName())").padding() + } if let connectionSuccess = viewModel.connectionSuccess { if let connectionStatus = viewModel.connectionStatus @@ -56,13 +69,23 @@ struct MCConnectionExampleView: View { .navigationTitle("Connecting...") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) .onAppear(perform: { - viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer) + if self.useCommissionerGeneratedPasscode { + if selectedCastingPlayer?.supportsCommissionerGeneratedPasscode() == true { + self.Log.info("MCConnectionExampleView calling MCConnectionExampleViewModel.connect() with useCommissionerGeneratedPasscode: \(String(describing: self.useCommissionerGeneratedPasscode))") + viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer, useCommissionerGeneratedPasscode: self.useCommissionerGeneratedPasscode) + } else { + self.Log.error("MCConnectionExampleView \(self.selectedCastingPlayer!.deviceName()) does not support Casting Player/Commissioner-Generated passcode commissioning. Select a different Casting Player.") + } + } else { + self.Log.info("MCConnectionExampleView calling MCConnectionExampleViewModel.connect() with useCommissionerGeneratedPasscode: \(String(describing: self.useCommissionerGeneratedPasscode))") + viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer, useCommissionerGeneratedPasscode: self.useCommissionerGeneratedPasscode) + } }) } } struct MCConnectionExampleView_Previews: PreviewProvider { static var previews: some View { - MCConnectionExampleView(_selectedCastingPlayer: nil) + MCConnectionExampleView(_selectedCastingPlayer: nil, _useCommissionerGeneratedPasscode: false) } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 13cdad3d8e60f1..13293d9b7ca1ee 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -15,9 +15,9 @@ * limitations under the License. */ - import Foundation import os.log +import UIKit class MCConnectionExampleViewModel: ObservableObject { let Log = Logger(subsystem: "com.matter.casting", @@ -25,29 +25,175 @@ class MCConnectionExampleViewModel: ObservableObject { // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection let kDesiredEndpointVendorId: UInt16 = 65521; + + // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connecting + // using the MCCastingPlayer/Commissioner-Generated passcode (CGP) commissioning flow. Use this Target Content + // Application Vendor ID, which is configured on the tv-app. This Target Content Application Vendor ID (1111), does + // not implement the AccountLogin cluster, which would otherwise auto commission using the Commissionee-Generated + // passcode upon recieving the IdentificationDeclaration Message. See + // connectedhomeip/examples/tv-app/tv-common/include/AppTv.h. + let kDesiredEndpointVendorIdCGP: UInt16 = 1111; @Published var connectionSuccess: Bool?; @Published var connectionStatus: String?; - func connect(selectedCastingPlayer: MCCastingPlayer?) { - let desiredEndpointFilter: MCEndpointFilter = MCEndpointFilter() - desiredEndpointFilter.vendorId = kDesiredEndpointVendorId - selectedCastingPlayer?.verifyOrEstablishConnection(completionBlock: { err in - self.Log.error("MCConnectionExampleViewModel connect() completed with \(err)") - DispatchQueue.main.async - { - if(err == nil) - { + func connect(selectedCastingPlayer: MCCastingPlayer?, useCommissionerGeneratedPasscode: Bool) { + self.Log.info("MCConnectionExampleViewModel.connect() useCommissionerGeneratedPasscode: \(String(describing: useCommissionerGeneratedPasscode))") + + let connectionCompleteCallback: (Swift.Error?) -> Void = { err in + self.Log.error("MCConnectionExampleViewModel connect() completed with: \(err)") + DispatchQueue.main.async { + if err == nil { self.connectionSuccess = true - self.connectionStatus = "Connected!" - } - else - { + if useCommissionerGeneratedPasscode { + self.connectionStatus = "Successfully connected to Casting Player using the Casting Player/Commissioner-Generated passcode!" + self.Log.info("MCConnectionExampleViewModel connect() Successfully connected to Casting Player using the Casting Player/CommissioneR-Generated passcode!") + } else { + self.connectionStatus = "Successfully connected to Casting Player!" + self.Log.info("MCConnectionExampleViewModel connect() Successfully connected to Casting Player using the Casting App/CommissioneE-Generated passcode!") + } + } else { self.connectionSuccess = false - self.connectionStatus = "Connection failed with \(String(describing: err))" + self.connectionStatus = "Connection to Casting Player failed with: \(String(describing: err))" + } + } + } + + let commissionerDeclarationCallback: (MCCommissionerDeclaration) -> Void = { commissionerDeclarationMessage in + DispatchQueue.main.async { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, recived a message form the MCCastingPlayer:\n\(commissionerDeclarationMessage)") + if commissionerDeclarationMessage.commissionerPasscode { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, calling getTopMostViewController()") + if let topViewController = self.getTopMostViewController() { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, calling displayPasscodeInputDialog()") + self.displayPasscodeInputDialog(on: topViewController, continueConnecting: { passcode in + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Continuing to connect with user entered MCCastingPlayer/Commissioner-Generated passcode: \(passcode)") + + // Update the CommissionableData in the client defined MCAppParametersDataSource with the user + // entered CastingPlayer/Commissioner-Generated setup passcode. This is mandatory for the + // Commissioner-Generated passcode commissioning flow since the commissioning session's PAKE + // verifier needs to be updated with the entered passcode. Get the singleton instane of the + // MCInitializationExample. + let initializationExample = MCInitializationExample.shared + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback calling MCInitializationExample.getAppParametersDataSource()") + if let dataSource = initializationExample.getAppParametersDataSource() { + let newCommissionableData = MCCommissionableData( + passcode: UInt32(passcode) ?? 0, + discriminator: 0, + spake2pIterationCount: 1000, + spake2pVerifier: nil, + spake2pSalt: nil + ) + dataSource.update(newCommissionableData) + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Updated MCAppParametersDataSource instance with new MCCommissionableData.") + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed") + self.connectionStatus = "Failed to update the MCAppParametersDataSource with the user entered passcode: \n\nRoute back and try again." + } + + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, calling MCCastingPlayer.continueConnecting()") + let errContinue = selectedCastingPlayer?.continueConnecting() + if errContinue == nil { + self.connectionStatus = "Continuing to connect with user entered passcode: \(passcode)" + } else { + self.connectionStatus = "Continue Connecting to Casting Player failed with: \(String(describing: errContinue)) \n\nRoute back and try again." + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed due to: \(errContinue)") + } + }, cancelConnecting: { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user, calling MCCastingPlayer.stopConnecting()") + let err = selectedCastingPlayer?.stopConnecting() + self.connectionSuccess = false + if err == nil { + self.connectionStatus = "User cancelled the connection attempt with CastingPlayer. \n\nRoute back to exit." + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, User cancelled the connection attempt with MCCastingPlayer, MCCastingPlayer.stopConnecting() succeeded.") + } else { + self.connectionStatus = "Cancel connection failed due to: \(String(describing: err)) \n\nRoute back to exit." + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.stopConnecting() failed due to: \(err)") + } + }) + } } } - }, desiredEndpointFilter: desiredEndpointFilter) + } + + let identificationDeclarationOptions: MCIdentificationDeclarationOptions + let targetAppInfo: MCTargetAppInfo + let connectionCallbacks: MCConnectionCallbacks + + if useCommissionerGeneratedPasscode { + identificationDeclarationOptions = MCIdentificationDeclarationOptions(commissionerPasscodeOnly: true) + targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorIdCGP) + connectionCallbacks = MCConnectionCallbacks( + callbacks: connectionCompleteCallback, + commissionerDeclarationCallback: commissionerDeclarationCallback + ) + } else { + identificationDeclarationOptions = MCIdentificationDeclarationOptions() + targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) + connectionCallbacks = MCConnectionCallbacks( + callbacks: connectionCompleteCallback, + commissionerDeclarationCallback: nil + ) + } + + identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) + self.Log.info("MCConnectionExampleViewModel.connect() MCIdentificationDeclarationOptions description: \n\(identificationDeclarationOptions.description)") + + self.Log.info("MCConnectionExampleViewModel.connect() calling MCCastingPlayer.verifyOrEstablishConnection()") + let err = selectedCastingPlayer?.verifyOrEstablishConnection(with: connectionCallbacks, identificationDeclarationOptions: identificationDeclarationOptions) + if err != nil { + self.Log.error("MCConnectionExampleViewModel connect(), MCCastingPlayer.verifyOrEstablishConnection() failed due to: \(err)") + } + } + + // Function to display the passcode input dialog + func displayPasscodeInputDialog(on viewController: UIViewController, continueConnecting: @escaping (String) -> Void, cancelConnecting: @escaping () -> Void) { + self.Log.info("MCConnectionExampleViewModel displayPasscodeInputDialog()") + + // Create the alert controller + let alertController = UIAlertController(title: "Enter Passcode", message: nil, preferredStyle: .alert) + + // Add the text field with the default passcode + alertController.addTextField { textField in + textField.placeholder = "Enter Passcode" + textField.text = "12345678" + // textField.isSecureTextEntry = true // Makes the passcode invisible + } + + // Add the "Continue Connecting" button + let continueAction = UIAlertAction(title: "Continue Connecting", style: .default) { _ in + if let passcode = alertController.textFields?.first?.text { + self.Log.info("MCConnectionExampleViewModel displayPasscodeInputDialog() User entered passcode: \(passcode)") + continueConnecting(passcode) + } + } + alertController.addAction(continueAction) + + // Add the "Cancel" button + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in + self.Log.info("MCConnectionExampleViewModel displayPasscodeInputDialog() User cancelled the passcode input dialog.") + cancelConnecting() + } + alertController.addAction(cancelAction) + + // Present the alert controller + viewController.present(alertController, animated: true, completion: nil) + } + + // Function to get the top-most view controller + func getTopMostViewController() -> UIViewController? { + self.Log.info("MCConnectionExampleViewModel getTopMostViewController()") + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { + return nil + } + + var topController = window.rootViewController + while let presentedController = topController?.presentedViewController { + topController = presentedController + } + + return topController } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift index 302ec886b4e115..aaf18375bb2c82 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift @@ -30,65 +30,67 @@ class MCContentLauncherLaunchURLExampleViewModel: ObservableObject { func invokeCommand(castingPlayer: MCCastingPlayer, contentUrl: String, displayString: String) { - // select the MCEndpoint on the MCCastingPlayer to invoke the command on - if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first - { - // validate that the selected endpoint supports the ContentLauncher cluster - if(!endpoint.hasCluster(MCEndpointClusterTypeContentLauncher)) - { - self.Log.error("No ContentLauncher cluster supporting endpoint found") - DispatchQueue.main.async - { - self.status = "No ContentLauncher cluster supporting endpoint found" - } - return + self.Log.info("MCContentLauncherLaunchURLExampleViewModel.invokeCommand()") + castingPlayer.logAllEndpoints() + + // Use MCEndpointSelector to select the endpoint + guard let endpoint = MCEndpointSelector.selectEndpoint(from: castingPlayer, sampleEndpointVid: sampleEndpointVid) else { + self.Log.error("MCContentLauncherLaunchURLExampleViewModel.invokeCommand() No endpoint matching the example VID or identifier 1 found") + DispatchQueue.main.async { + self.status = "No endpoint matching the example VID or identifier 1 found" } - - // get ContentLauncher cluster from the endpoint - let contentLaunchercluster: MCContentLauncherCluster = endpoint.cluster(for: MCEndpointClusterTypeContentLauncher) as! MCContentLauncherCluster + return + } - // get the launchURLCommand from the contentLauncherCluster - let launchURLCommand: MCContentLauncherClusterLaunchURLCommand? = contentLaunchercluster.launchURLCommand() - if(launchURLCommand == nil) + self.Log.info("MCContentLauncherLaunchURLExampleViewModel.invokeCommand() selected endpoint: \(endpoint.description)") + + // validate that the selected endpoint supports the ContentLauncher cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeContentLauncher)) + { + self.Log.error("No ContentLauncher cluster supporting endpoint found") + DispatchQueue.main.async { - self.Log.error("LaunchURL not supported on cluster") - DispatchQueue.main.async - { - self.status = "LaunchURL not supported on cluster" - } - return + self.status = "No ContentLauncher cluster supporting endpoint found" } - - // create the LaunchURL request - let request: MCContentLauncherClusterLaunchURLParams = MCContentLauncherClusterLaunchURLParams() - request.contentURL = contentUrl - request.displayString = displayString - - // call invoke on launchURLCommand while passing in a completion block - launchURLCommand!.invoke(request, context: nil, completion: { context, err, response in - DispatchQueue.main.async - { - if(err == nil) - { - self.Log.info("LaunchURLCommand invoke completion success with \(String(describing: response))") - self.status = "Success. Response data: \(String(describing: response?.data))" - } - else - { - self.Log.error("LaunchURLCommand invoke completion failure with \(String(describing: err))") - self.status = "Failure: \(String(describing: err))" - } - } - }, - timedInvokeTimeoutMs: 5000) // time out after 5000ms + return } - else + + // get ContentLauncher cluster from the endpoint + let contentLaunchercluster: MCContentLauncherCluster = endpoint.cluster(for: MCEndpointClusterTypeContentLauncher) as! MCContentLauncherCluster + + // get the launchURLCommand from the contentLauncherCluster + let launchURLCommand: MCContentLauncherClusterLaunchURLCommand? = contentLaunchercluster.launchURLCommand() + if(launchURLCommand == nil) { - self.Log.error("No endpoint matching the example VID found") + self.Log.error("LaunchURL not supported on cluster") DispatchQueue.main.async { - self.status = "No endpoint matching the example VID found" + self.status = "LaunchURL not supported on cluster" } + return } + + // create the LaunchURL request + let request: MCContentLauncherClusterLaunchURLParams = MCContentLauncherClusterLaunchURLParams() + request.contentURL = contentUrl + request.displayString = displayString + + // call invoke on launchURLCommand while passing in a completion block + launchURLCommand!.invoke(request, context: nil, completion: { context, err, response in + DispatchQueue.main.async + { + if(err == nil) + { + self.Log.info("LaunchURLCommand invoke completion success with \(String(describing: response))") + self.status = "Success. Response data: \(String(describing: response?.data))" + } + else + { + self.Log.error("LaunchURLCommand invoke completion failure with \(String(describing: err))") + self.status = "Failure: \(String(describing: err))" + } + } + }, + timedInvokeTimeoutMs: 5000) // time out after 5000ms } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCDiscoveryExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCDiscoveryExampleView.swift index 769b84c2a35963..9c36ca553c18b5 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCDiscoveryExampleView.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCDiscoveryExampleView.swift @@ -25,25 +25,29 @@ extension MCCastingPlayer : Identifiable { struct MCDiscoveryExampleView: View { @StateObject var viewModel = MCDiscoveryExampleViewModel() + @State private var selectedCastingPlayer: MCCastingPlayer? + // Enable navigating to the MCConnectionExampleView with or without the use + // Commissioner-Generated Passcode (CGP) flag. + @State private var navigateWithUseCGP = false var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .center, spacing: 16) { Button("Start Discovery", action: viewModel.startDiscovery) - .frame(width: 350, height: 30, alignment: .center) + .frame(width: 350, height: 30) .border(Color.black, width: 1) .background(Color.blue) .foregroundColor(Color.white) .padding(1) Button("Stop Discovery", action: viewModel.stopDiscovery) - .frame(width: 350, height: 30, alignment: .center) + .frame(width: 350, height: 30) .border(Color.black, width: 1) .background(Color.blue) .foregroundColor(Color.white) .padding(1) Button("Clear Results", action: viewModel.clearResults) - .frame(width: 350, height: 30, alignment: .center) + .frame(width: 350, height: 30) .border(Color.black, width: 1) .background(Color.blue) .foregroundColor(Color.white) @@ -55,28 +59,57 @@ struct MCDiscoveryExampleView: View { } else if(!viewModel.displayedCastingPlayers.isEmpty) { - Text("Select a Casting player...") + Text("Select a Casting player:") ForEach(viewModel.displayedCastingPlayers) { castingPlayer in - NavigationLink( - destination: { - MCConnectionExampleView(_selectedCastingPlayer: castingPlayer) - }, - label: { - Text(castingPlayer.description) - .frame(minHeight: 50) - .padding() - } - ) - .frame(width: 350, alignment: .center) - .border(Color.black, width: 1) - .background(Color.blue) - .foregroundColor(Color.white) + ZStack { + NavigationLink( + destination: MCConnectionExampleView( + _selectedCastingPlayer: selectedCastingPlayer, + _useCommissionerGeneratedPasscode: navigateWithUseCGP + ), + isActive: Binding( + get: { selectedCastingPlayer?.id == castingPlayer.id }, + set: { newValue in + if newValue { + selectedCastingPlayer = castingPlayer + navigateWithUseCGP = false + } else { + selectedCastingPlayer = nil + navigateWithUseCGP = false + } + } + ), + label: { EmptyView() } + ) + Text(castingPlayer.description) + .frame(minHeight: 50) + .multilineTextAlignment(.center) + .padding(10) + .background(Color.blue) + .foregroundColor(Color.white) + .border(Color.black, width: 1) + .onTapGesture { + selectedCastingPlayer = castingPlayer + navigateWithUseCGP = false + } + .gesture( + LongPressGesture() + .onEnded { _ in + selectedCastingPlayer = castingPlayer + navigateWithUseCGP = true + } + ) + } + .frame(width: 350) .padding(1) } } + Spacer() + Text("Long click on a Casting Player to connect using CastingPlayer/Commissioner-Generated passcode commissioning flow (if supported).").font(.system(size: 12)).padding(1) } .navigationTitle("Casting Player Discovery") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + .multilineTextAlignment(.center) } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCEndpointSelector.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCEndpointSelector.swift new file mode 100644 index 00000000000000..fa842c5f81f70b --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCEndpointSelector.swift @@ -0,0 +1,38 @@ +/** + * + * Copyright (c) 2020-2024 Project CHIP Authors + * + * 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. + */ + +import Foundation +import os.log + +class MCEndpointSelector { + static let Log = Logger(subsystem: "com.matter.casting", category: "MCEndpointSelector") + + static func selectEndpoint(from castingPlayer: MCCastingPlayer, sampleEndpointVid: Int) -> MCEndpoint? { + Log.info("MCEndpointSelector.selectEndpoint()") + + if let endpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid }).first { + Log.info("MCEndpointSelector.selectEndpoint() Found endpoint matching the sampleEndpointVid: \(sampleEndpointVid)") + return endpoint + } else if let endpoint = castingPlayer.endpoints().filter({ $0.identifier().intValue == 1 }).first { + Log.info("MCEndpointSelector.selectEndpoint() No endpoint matching the sampleEndpointVid: \(sampleEndpointVid), but found endpoint with identifier: 1") + return endpoint + } + + Log.error("No endpoint matching the example VID or identifier 1 found") + return nil + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCInitializationExample.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCInitializationExample.swift index daae3ab449418f..bbb43a5926eb64 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCInitializationExample.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCInitializationExample.swift @@ -19,11 +19,40 @@ import Foundation import Security import os.log +// This class needs to be imlemented by the client. class MCAppParametersDataSource : NSObject, MCDataSource { let Log = Logger(subsystem: "com.matter.casting", category: "MCAppParametersDataSource") + // Dummy values for demonstration only. + private var commissionableData: MCCommissionableData = MCCommissionableData( + passcode: 20202021, + discriminator: 3874, + // Default to the minimum PBKDF iterations (1,000) for this example implementation. For TV devices and TV casting app production + // implementations, you should use a higher number of PBKDF iterations to enhance security. The default minimum iterations are + // not sufficient against brute-force and rainbow table attacks. Increasing the number of iterations will increase the + // computational time required to derive the key. This can slow down the authentication process, especially on devices with + // limited processing power like a Raspberry Pi 4. For a production implementation, you should measure the actual performance on + // the target device. + // + // 1,000 - Hypothetical key derivation time: ~20 milliseconds (ms). + // 100,000 - Hypothetical key derivation time: ~2 seconds. + spake2pIterationCount: 1000, + spake2pVerifier: nil, + spake2pSalt: nil + ) + + /** + * This function needs to be implemented by the client in use cases where the MCCommissionableData needs to be updated + * post-initialization. For example, when the Commissioner-Generated Passcode feature is used. + */ + func update(_ newCommissionableData: MCCommissionableData) { + Log.info("MCAppParametersDataSource.update() - Before update, passcode: \(self.commissionableData.passcode)") + self.commissionableData = newCommissionableData + Log.info("MCAppParametersDataSource.update() - After update, passcode: \(self.commissionableData.passcode)") + } + func clientQueue() -> DispatchQueue { return DispatchQueue.main; } @@ -34,13 +63,8 @@ class MCAppParametersDataSource : NSObject, MCDataSource } func castingAppDidReceiveRequestForCommissionableData(_ sender: Any) -> MCCommissionableData { - // dummy values for demonstration only - return MCCommissionableData( - passcode: 20202021, - discriminator: 3874, - spake2pIterationCount: 1000, - spake2pVerifier: nil, - spake2pSalt: nil) + Log.info("MCAppParametersDataSource castingAppDidReceiveRequestForCommissionableData()") + return commissionableData } // dummy DAC values for demonstration only @@ -96,18 +120,41 @@ class MCAppParametersDataSource : NSObject, MCDataSource } } +// This class is a singleton class MCInitializationExample { + static let shared = MCInitializationExample() + let Log = Logger(subsystem: "com.matter.casting", category: "MCInitializationExample") - + + // We store the client defined instance of the MCAppParametersDataSource passed to CastingApp.initialize(). + // MCAppParametersDataSource may need to be updated by the client in case of the Casting + // Player/Commissioner-Generated passcode commissioning flow. + private var appParametersDataSource: MCAppParametersDataSource? + + private init() { + // Private initialization to ensure just one instance is created. + } + func initialize() -> Error? { if let castingApp = MCCastingApp.getSharedInstance() { - return castingApp.initialize(with: MCAppParametersDataSource()) + Log.info("MCInitializationExample.initialize() calling MCCastingApp.initializeWithDataSource()") + + let dataSource = MCAppParametersDataSource() + appParametersDataSource = dataSource + + return castingApp.initialize(with: dataSource) } else { return NSError(domain: "com.matter.casting", code: Int(MATTER_ERROR_INCORRECT_STATE.code)) } } + + // Getter method for the stored instance of MCAppParametersDataSource + func getAppParametersDataSource() -> MCAppParametersDataSource? { + Log.info("MCInitializationExample.getAppParametersDataSource()") + return appParametersDataSource + } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift index e7d3581dd07350..7a4752431e1846 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift @@ -30,69 +30,71 @@ class MCMediaPlaybackSubscribeToCurrentStateExampleViewModel: ObservableObject { func subscribe(castingPlayer: MCCastingPlayer) { - // select the MCEndpoint on the MCCastingPlayer to invoke the command on - if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first - { - // validate that the selected endpoint supports the MediaPlayback cluster - if(!endpoint.hasCluster(MCEndpointClusterTypeMediaPlayback)) - { - self.Log.error("No MediaPlayback cluster supporting endpoint found") - DispatchQueue.main.async - { - self.status = "No MediaPlayback cluster supporting endpoint found" - } - return + self.Log.info("MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.subscribe()") + castingPlayer.logAllEndpoints() + + // Use MCEndpointSelector to select the endpoint + guard let endpoint = MCEndpointSelector.selectEndpoint(from: castingPlayer, sampleEndpointVid: sampleEndpointVid) else { + self.Log.error("MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.subscribe() No endpoint matching the example VID or identifier 1 found") + DispatchQueue.main.async { + self.status = "No endpoint matching the example VID or identifier 1 found" } - - // get MediaPlayback cluster from the endpoint - let mediaPlaybackCluster: MCMediaPlaybackCluster = endpoint.cluster(for: MCEndpointClusterTypeMediaPlayback) as! MCMediaPlaybackCluster + return + } - // get the currentStateAttribute from the mediaPlaybackCluster - let currentStateAttribute: MCMediaPlaybackClusterCurrentStateAttribute? = mediaPlaybackCluster.currentStateAttribute() - if(currentStateAttribute == nil) + self.Log.info("MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.subscribe() selected endpoint: \(endpoint.description)") + + // validate that the selected endpoint supports the MediaPlayback cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeMediaPlayback)) + { + self.Log.error("No MediaPlayback cluster supporting endpoint found") + DispatchQueue.main.async { - self.Log.error("CurrentState attribute not supported on cluster") - DispatchQueue.main.async - { - self.status = "CurrentState attribute not supported on cluster" - } - return + self.status = "No MediaPlayback cluster supporting endpoint found" } - - - // call read on currentStateAttribute and pass in a completion block - currentStateAttribute!.subscribe(nil, completion: { context, before, after, err in - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH:mm:ss" - let currentTime = dateFormatter.string(from: Date()) - DispatchQueue.main.async - { - if(err != nil) - { - self.Log.error("Error when reading CurrentState value \(String(describing: err)) at \(currentTime)") - self.status = "Error when reading CurrentState value \(String(describing: err)) at \(currentTime)" - return - } - if(before != nil) - { - self.Log.info("Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)") - self.status = "Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)" - } - else - { - self.Log.info("Read CurrentState value: \(String(describing: after)) at \(currentTime)") - self.status = "Read CurrentState value: \(String(describing: after)) at \(currentTime)" - } - } - }, minInterval: 0, maxInterval: 1) + return } - else + + // get MediaPlayback cluster from the endpoint + let mediaPlaybackCluster: MCMediaPlaybackCluster = endpoint.cluster(for: MCEndpointClusterTypeMediaPlayback) as! MCMediaPlaybackCluster + + // get the currentStateAttribute from the mediaPlaybackCluster + let currentStateAttribute: MCMediaPlaybackClusterCurrentStateAttribute? = mediaPlaybackCluster.currentStateAttribute() + if(currentStateAttribute == nil) { - self.Log.error("No endpoint matching the example VID found") + self.Log.error("CurrentState attribute not supported on cluster") DispatchQueue.main.async { - self.status = "No endpoint matching the example VID found" + self.status = "CurrentState attribute not supported on cluster" } + return } + + + // call read on currentStateAttribute and pass in a completion block + currentStateAttribute!.subscribe(nil, completion: { context, before, after, err in + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm:ss" + let currentTime = dateFormatter.string(from: Date()) + DispatchQueue.main.async + { + if(err != nil) + { + self.Log.error("Error when reading CurrentState value \(String(describing: err)) at \(currentTime)") + self.status = "Error when reading CurrentState value \(String(describing: err)) at \(currentTime)" + return + } + if(before != nil) + { + self.Log.info("Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)") + self.status = "Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)" + } + else + { + self.Log.info("Read CurrentState value: \(String(describing: after)) at \(currentTime)") + self.status = "Read CurrentState value: \(String(describing: after)) at \(currentTime)" + } + } + }, minInterval: 0, maxInterval: 1) } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift index ce31f4f79446ce..c164c62a1ff518 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift @@ -32,8 +32,7 @@ struct TvCastingApp: App { if ProcessInfo.processInfo.environment["CHIP_CASTING_SIMPLIFIED"] == "1" { self.Log.info("CHIP_CASTING_SIMPLIFIED = 1") - - let err: Error? = MCInitializationExample().initialize() + let err: Error? = MCInitializationExample.shared.initialize() if err != nil { self.Log.error("MCCastingApp initialization failed \(err)")