From 64909a7fc122e5168cbd4935cfedf01408915b43 Mon Sep 17 00:00:00 2001 From: Philip Gregor Date: Wed, 26 Jun 2024 17:25:07 -0700 Subject: [PATCH 1/4] tv-casting-app updated APIs.md docs with new v1.3 Matter APIs --- examples/tv-casting-app/APIs.md | 516 +++++++++++++++--- .../casting/ConnectionExampleFragment.java | 30 +- .../IdentificationDeclarationOptions.java | 59 +- .../matter/casting/support/TargetAppInfo.java | 62 ++- .../MCConnectionExampleViewModel.swift | 10 +- .../linux/simple-app-helper.cpp | 8 +- 6 files changed, 555 insertions(+), 130 deletions(-) diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 76af45a2d326d4..92441511238abe 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -158,6 +158,16 @@ client's lifecycle: the Matter specification's "Onboarding Payload" section for more details on commissioning data. + For the optional `CastingPlayer` / Commissioner-Generated Passcode User + Directed Commissioning (UDC) feature, Commissioning Data `DataProvider` + needs to be updated post Casting Client initialization. In this scenario, + the `CastingPlayer` generates a Passcode and displays it for the user. The + user enters the Passcode into the Casting Client UX. The Casting Client + generates a PAKE verifier based upon this Passcode and enters commissioning + mode using this PAKE verifier. The APIs needed for this update are described + below. See the Matter specification’s UDC section for more information on + the Commissioner-Generated Passcode feature. + On Linux, define a function `InitCommissionableDataProvider` to initialize a `LinuxCommissionableDataProvider` that can provide the required values to the `CastingApp`. @@ -185,33 +195,76 @@ client's lifecycle: } ``` + On Linux, to utilize the optional `CastingPlayer` / Commissioner-Generated + Passcode UDC feature, the Casting Client may initialize a new + `LinuxCommissionableDataProvider` with the user-entered `CastingPlayer` + generated Passcode. The new `LinuxCommissionableDataProvider` may be passed + to the CastingApp using `UpdateCommissionableDataProvider`. + + ```c + LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; + LinuxCommissionableDataProvider gCommissionableDataProvider; + CHIP_ERROR err = CHIP_NO_ERROR; + err = InitCommissionableDataProvider(gCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); + err = matter::casting::core::CastingApp::GetInstance()->UpdateCommissionableDataProvider(&gCommissionableDataProvider); + ``` + On Android, define a `commissioningDataProvider` that can provide the - required values to the `CastingApp`. + required values to the `CastingApp`. To utilize the optional `CastingPlayer` + / Commissioner-Generated Passcode UDC feature, the Casting Client may also + define a function to update the CommissionableDataProvider + post-initialization. `updateCommissionableDataSetupPasscode` is an example + function to update the CommissionableData with the `CastingPlayer` generated + passcode. ```java - private static final DataProvider commissionableDataProvider = - new DataProvider() { + // Dummy values for commissioning demonstration only. These are hard coded in the example tv-app: + // connectedhomeip/examples/tv-app/tv-common/src/AppTv.cpp + private static final long DUMMY_SETUP_PASSCODE = 20202021; + private static final int DUMMY_DISCRIMINATOR = 3874; + + public static class CommissionableDataProvider implements DataProvider { + CommissionableData commissionableData = + new CommissionableData(DUMMY_SETUP_PASSCODE, DUMMY_DISCRIMINATOR); + @Override public CommissionableData get() { - // dummy values for demonstration only - return new CommissionableData(20202021, 3874); + return commissionableData; + } + + // For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. + public void updateCommissionableDataSetupPasscode(long setupPasscode, int discriminator) { + commissionableData.setSetupPasscode(setupPasscode); + commissionableData.setDiscriminator(discriminator); } }; ``` On iOS, add a `func commissioningDataProvider` to the `MCAppParametersDataSource` class defined above, that can provide the - required values to the `MCCastingApp`. + required values to the `MCCastingApp`. To utilize the optional + `CastingPlayer` / Commissioner-Generated Passcode UDC feature, the Casting + Client may also add a function to update the CommissionableDataProvider + post-initialization. `update` is an example function to update the + CommissionableData with the `CastingPlayer` generated passcode. ```swift + // Dummy values for demonstration only. + private var commissionableData: MCCommissionableData = MCCommissionableData( + passcode: 20202021, + discriminator: 3874, + spake2pIterationCount: 1000, + spake2pVerifier: nil, + spake2pSalt: nil + ) + func castingAppDidReceiveRequestForCommissionableData(_ sender: Any) -> MCCommissionableData { - // dummy values for demonstration only - return MCCommissionableData( - passcode: 20202021, - discriminator: 3874, - spake2pIterationCount: 1000, - spake2pVerifier: nil, - spake2pSalt: nil) + return commissionableData + } + + // For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. + func update(_ newCommissionableData: MCCommissionableData) { + self.commissionableData = newCommissionableData } ``` @@ -703,9 +756,13 @@ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as `deviceName`, `vendorId`, `productId`, etc. which can help the user pick the right `CastingPlayer`. A Casting Client can attempt to connect to the -`selectedCastingPlayer` using Matter User Directed Commissioning (UDC). The -Matter TV Casting library locally caches information required to reconnect to a -`CastingPlayer`, once the Casting client has been commissioned by it. After +`selectedCastingPlayer` using Matter User Directed Commissioning (UDC). To +utilize the optional `CastingPlayer` / Commissioner-Generated Passcode UDC +feature to connect to a `CastingPlayer`, the `CastingPlayer` object must +indicate its support for it via the`supportsCommissionerGeneratedPasscode` flag. + +The Matter TV Casting library locally caches information required to reconnect +to a `CastingPlayer`, once the Casting client has been commissioned by it. After that, the Casting client is able to skip the full UDC process by establishing CASE with the `CastingPlayer` directly. Once connected, the `CastingPlayer` object will contain the list of available Endpoints on that `CastingPlayer`. @@ -734,12 +791,18 @@ void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * ca } ... +matter::casting::core::IdentificationDeclarationOptions idOptions; +chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; +targetAppInfo.vendorId = kDesiredEndpointVendorId; +CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); + +matter::casting::core::ConnectionCallbacks connectionCallbacks; +connectionCallbacks.mOnConnectionComplete = ConnectionHandler; + // targetCastingPlayer is a discovered CastingPlayer -matter::casting::core::EndpointFilter desiredEndpointFilter; -desiredEndpointFilter.vendorId = kDesiredEndpointVendorId; -targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, +targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, matter::casting::core::kCommissioningWindowTimeoutSec, - desiredEndpointFilter); + idOptions); ... ``` @@ -747,44 +810,51 @@ On Android, the Casting Client may call `verifyOrEstablishConnection` on the `CastingPlayer` object it wants to connect to. ```java -private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; +private static final short MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; +private static final Integer DESIRED_TARGET_APP_VENDOR_ID = 65521; -EndpointFilter desiredEndpointFilter = new EndpointFilter(); -desiredEndpointFilter.vendorId = DESIRED_ENDPOINT_VENDOR_ID; +IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(); +TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); +idOptions.addTargetAppInfo(targetAppInfo); -MatterError err = targetCastingPlayer.verifyOrEstablishConnection( - MIN_CONNECTION_TIMEOUT_SEC, - desiredEndpointFilter, - new MatterCallback() { - @Override - public void handle(Void v) { - Log.i( - TAG, - "Connected to CastingPlayer with deviceId: " - + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName() - + "\n\n"); - connectionFragmentNextButton.setEnabled(true); - }); - } - }, - new MatterCallback() { - @Override - public void handle(MatterError err) { - Log.e(TAG, "CastingPLayer connection failed: " + err); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " + err + "\n\n"); - }); - } - }); +ConnectionCallbacks connectionCallbacks = + new ConnectionCallbacks( + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Successfully connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Successfully connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPlayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } + }, + onCommissionerDeclaration:null + ); + +MatterError err = + targetCastingPlayer.verifyOrEstablishConnection( + connectionCallbacks, MIN_CONNECTION_TIMEOUT_SEC, idOptions); if (err.hasError()) { @@ -810,21 +880,280 @@ let kDesiredEndpointVendorId: UInt16 = 65521; @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)") - if(err == nil) - { - self.connectionSuccess = true - self.connectionStatus = "Connected!" + + 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 = "Successfully connected to Casting Player!" + } else { + self.connectionSuccess = false + self.connectionStatus = "Connection to Casting Player failed with: \(String(describing: err))" + } } - else + } + + let identificationDeclarationOptions: MCIdentificationDeclarationOptions + let targetAppInfo: MCTargetAppInfo + let connectionCallbacks: MCConnectionCallbacks + identificationDeclarationOptions = MCIdentificationDeclarationOptions() + targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) + connectionCallbacks = MCConnectionCallbacks( + callbacks: connectionCompleteCallback, + commissionerDeclarationCallback: nil + ) + identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) + + let err = selectedCastingPlayer?.verifyOrEstablishConnection(with: connectionCallbacks, identificationDeclarationOptions: identificationDeclarationOptions) + if err != nil { + self.Log.error("MCConnectionExampleViewModel connect(), MCCastingPlayer.verifyOrEstablishConnection() failed due to: \(err)") + } +} +``` + +For a Casting Client to connect to a `CastingPlayer` using the optional +`CastingPlayer` / Commissioner-Generated Passcode UDC feature, the Casting +Client may specify optional parameters in the `VerifyOrEstablishConnection` +function call, handle and then respond to the CastingPlayer's +CommissionerDeclaration message as follows: + +1. In `VerifyOrEstablishConnection` the Casting Client should set the UDC + IdentificationDeclaration `CommissionerPasscode` field to true and provide a + `ConnectionCallbacks` callback to handle the CastingPlayer's + CommissionerDeclaration message. +1. Upon receiving the CastingPlayer’s CommissionerDeclaration message with + PasscodeDialogDisplayed set to true, the Casting Client should prompt the + user to input the Passcode displayed on the `CastingPlayer` UX. If the user + declines to enter the Passcode, the Casting Client should call + `StopConnecting` to alert the `CastingPlayer` that the commissioning attempt + was canceled. +1. The Casting Client should then update the commissioning session's PAKE + verifier with the user-entered Passcode using the API's described in the + Initialize the Casting Client section above. +1. Finally, the Casting Client should call `ContinueConnecting` to send a second + IdentificationDeclaration message to the `CastingPlayer` with + `CommissionerPasscodeReady` set to true. + +On Linux, the Casting Client can connect to a `CastingPlayer`, using the +optional `CastingPlayer` / Commissioner-Generated Passcode UDC feature, by +successfully calling `VerifyOrEstablishConnection`, updating the PAKE verifier +and then calling `ContinueConnecting` on the `CastingPlayer`. + +```c +// VendorId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection +const uint16_t kDesiredEndpointVendorId = 65521; + +void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) +{ + if(err == CHIP_NO_ERROR) + { + ChipLogProgress(AppServer, "ConnectionHandler: Successfully connected to CastingPlayer(ID: %s)", castingPlayer->GetId()); + ... + } +} + +// For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. +void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source, + chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) +{ + ChipLogProgress(AppServer, + "simple-app-helper.cpp::CommissionerDeclarationCallback() called with CommissionerDeclaration message:"); + if (cd.GetCommissionerPasscode()) + { + // A Passcode is now displayed for the user by the CastingPlayer. Prompt the user to enter the Passcode. + ... + + // Update the commissioning session's PAKE verifier with the user-entered Passcode + LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; + LinuxCommissionableDataProvider gCommissionableDataProvider; + CHIP_ERROR err = CHIP_NO_ERROR; + err = InitCommissionableDataProvider(gCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); + err = matter::casting::core::CastingApp::GetInstance()->UpdateCommissionableDataProvider(&gCommissionableDataProvider); + + // Call continueConnecting to complete commissioning. + err = targetCastingPlayer->ContinueConnecting(); + if (err != CHIP_NO_ERROR) { - self.connectionSuccess = false - self.connectionStatus = "Connection failed with \(String(describing: err))" + ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode ContinueConnecting() err %" CHIP_ERROR_FORMAT, + err.Format()); + } + } +} + +... +matter::casting::core::IdentificationDeclarationOptions idOptions; +chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; +targetAppInfo.vendorId = kDesiredEndpointVendorId; +CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); + +matter::casting::core::ConnectionCallbacks connectionCallbacks; +connectionCallbacks.mOnConnectionComplete = ConnectionHandler; + +// For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. +idOptions.mCommissionerPasscode = true; +connectionCallbacks.mCommissionerDeclarationCallback = CommissionerDeclarationCallback; + +// targetCastingPlayer is a discovered CastingPlayer +targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, + matter::casting::core::kCommissioningWindowTimeoutSec, + idOptions); +... +``` + +On Andorid, the Casting Client can connect to a `CastingPlayer`, using the +optional `CastingPlayer` / Commissioner-Generated Passcode UDC feature, by +successfully calling `verifyOrEstablishConnection`, updating the PAKE verifier +and then calling `continueConnecting` on the `CastingPlayer`. + +```java +private static final short MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; +private static final Integer DESIRED_TARGET_APP_VENDOR_ID = 65521; + +// Constructor to set only the commissionerPasscode. +IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(commissionerPasscode:true); +TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); +idOptions.addTargetAppInfo(targetAppInfo); + +ConnectionCallbacks connectionCallbacks = + new ConnectionCallbacks( + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Successfully connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Successfully connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPlayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } + }, + new MatterCallback() { + @Override + public void handle(CommissionerDeclaration cd) { + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "CommissionerDeclaration message received from Casting Player: \n\n"); + if (cd.getCommissionerPasscode()) { + + displayPasscodeInputDialog(getActivity()); + ... + + // Update the commissioning session's PAKE verifier with the user-entered Passcode + InitializationExample.commissionableDataProvider.updateCommissionableDataSetupPasscode( + passcodeLongValue, DEFAULT_DISCRIMINATOR_FOR_CGP_FLOW); + + // Call continueConnecting to complete commissioning. + MatterError err = targetCastingPlayer.continueConnecting(); + ... + } + }); + } } - }, desiredEndpointFilter: desiredEndpointFilter) + ); + +MatterError err = + targetCastingPlayer.verifyOrEstablishConnection( + connectionCallbacks, MIN_CONNECTION_TIMEOUT_SEC, idOptions); +... +``` + +On iOS, the Casting Client can connect to a `CastingPlayer`, using the optional +`CastingPlayer` / Commissioner-Generated Passcode UDC feature, by successfully +calling `verifyOrEstablishConnection`, updating the PAKE verifier and then +calling `continueConnecting` on the `CastingPlayer`. + +```swift +// VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection +let kDesiredEndpointVendorId: UInt16 = 65521; + +@Published var connectionSuccess: Bool?; + +@Published var connectionStatus: String?; + +func connect(selectedCastingPlayer: MCCastingPlayer?) { + + 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 = "Successfully connected to Casting Player!" + } else { + self.connectionSuccess = false + 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 { + if let topViewController = self.getTopMostViewController() { + self.displayPasscodeInputDialog(on: topViewController, continueConnecting: { userEnteredPasscode in + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Continuing to connect with user entered MCCastingPlayer/Commissioner-Generated passcode: \(passcode)") + + // Update the commissioning session's PAKE verifier with the user-entered Passcode + if let dataSource = initializationExample.getAppParametersDataSource() { + let newCommissionableData = MCCommissionableData( + passcode: UInt32(userEnteredPasscode) ?? 0, + discriminator: 0, + ... + ) + dataSource.update(newCommissionableData) + } + + // Call continueConnecting to complete commissioning. + let errContinue = selectedCastingPlayer?.continueConnecting() + if errContinue == nil { + self.connectionStatus = "Continuing to connect with user entered passcode: \(userEnteredPasscode)" + } + ... + }, cancelConnecting: { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user, calling MCCastingPlayer.stopConnecting()") + let err = selectedCastingPlayer?.stopConnecting() + ... + }) + } + } + } + } + + let identificationDeclarationOptions: MCIdentificationDeclarationOptions + let targetAppInfo: MCTargetAppInfo + let connectionCallbacks: MCConnectionCallbacks + identificationDeclarationOptions = MCIdentificationDeclarationOptions(commissionerPasscodeOnly: true) + targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) + connectionCallbacks = MCConnectionCallbacks( + callbacks: connectionCompleteCallback, + commissionerDeclarationCallback: commissionerDeclarationCallback + ) + identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) + + let err = selectedCastingPlayer?.verifyOrEstablishConnection(with: connectionCallbacks, identificationDeclarationOptions: identificationDeclarationOptions) + ... } ``` @@ -864,26 +1193,22 @@ On Android, it can select an `Endpoint` as shown below. ```java private static final Integer SAMPLE_ENDPOINT_VID = 65521; -private Endpoint selectFirstEndpointByVID() -{ - Endpoint endpoint = null; - if(selectedCastingPlayer != null) - { - List endpoints = selectedCastingPlayer.getEndpoints(); - if (endpoints == null) - { - Log.e(TAG, "No Endpoints found on CastingPlayer"); - } - else - { - endpoint = endpoints - .stream() - .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) - .findFirst() - .get(); - } +public static Endpoint selectFirstEndpointByVID(CastingPlayer selectedCastingPlayer) { +Endpoint endpoint = null; +if (selectedCastingPlayer != null) { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) { + Log.e(TAG, "selectFirstEndpointByVID() No Endpoints found on CastingPlayer"); + } else { + endpoint = + endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .orElse(null); } - return endpoint; +} +return endpoint; } ``` @@ -893,10 +1218,19 @@ On iOS, it can select an `MCEndpoint` similarly and as shown below. // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection let sampleEndpointVid: Int = 65521 ... -// select the MCEndpoint on the MCCastingPlayer to invoke the command on -if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first -{ -... + +// Select the MCEndpoint on the MCCastingPlayer to invoke the command on +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 + } + ... + + Log.error("No endpoint matching the example VID or identifier 1 found") + return nil } ``` diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index 1bf95ef4a2d7eb..bb43165be6fcfb 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -129,23 +129,23 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { Log.d(TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection()"); IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(); - TargetAppInfo targetAppInfo = new TargetAppInfo(); - targetAppInfo.vendorId = DESIRED_TARGET_APP_VENDOR_ID; + TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); if (useCommissionerGeneratedPasscode) { - idOptions.commissionerPasscode = true; - targetAppInfo.vendorId = DESIRED_TARGET_APP_VENDOR_ID_FOR_CGP_FLOW; + // Constructor to set only the commissionerPasscode. + idOptions = new IdentificationDeclarationOptions(true); + targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID_FOR_CGP_FLOW); Log.d( TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection() Target Content Application Vendor ID: " - + targetAppInfo.vendorId + + targetAppInfo.getVendorId() + ", useCommissionerGeneratedPasscode: " + useCommissionerGeneratedPasscode); } else { Log.d( TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection() Target Content Application Vendor ID: " - + targetAppInfo.vendorId); + + targetAppInfo.getVendorId()); } idOptions.addTargetAppInfo(targetAppInfo); @@ -242,21 +242,21 @@ private void displayPasscodeInputDialog(Context context) { new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - String passcode = input.getText().toString(); + String userEnteredPasscode = input.getText().toString(); Log.i( TAG, - "displayPasscodeInputDialog() User entered CastingPlayer/Commissioner-Generated passcode: " - + passcode); + "displayPasscodeInputDialog() user-entered CastingPlayer/Commissioner-Generated passcode: " + + userEnteredPasscode); // Display the user entered passcode on the screen connectionFragmentStatusTextView.setText( - "Continue Connecting with user entered CastingPlayer/Commissioner-Generated passcode: " - + passcode + "Continue Connecting with user-entered CastingPlayer/Commissioner-Generated passcode: " + + userEnteredPasscode + "\n\n"); long passcodeLongValue = DEFAULT_COMMISSIONER_GENERATED_PASSCODE; try { - passcodeLongValue = Long.parseLong(passcode); + passcodeLongValue = Long.parseLong(userEnteredPasscode); Log.i( TAG, "displayPasscodeInputDialog() User entered CastingPlayer/Commissioner-Generated passcode: " @@ -268,12 +268,12 @@ public void onClick(DialogInterface dialog, int which) { + nfe); connectionFragmentStatusTextView.setText( "User entered CastingPlayer/Commissioner-Generated passcode is not a valid integer: " - + passcode + + userEnteredPasscode + "\n\n"); } - // Update the CommissionableData DataProvider with the user entered - // CastingPlayer/Commissioner-Generated setup passcode. This is mandatory for + // Update the CommissionableData DataProvider with the user-entered + // CastingPlayer / Commissioner-Generated setup passcode. This is mandatory for // Commissioner-Generated passcode commissioning since the commissioning session's PAKE // verifier needs to be updated with the entered passcode. InitializationExample.commissionableDataProvider.updateCommissionableDataSetupPasscode( 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 9b64b898459dbd..928ab7dc5d7dc3 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 @@ -27,21 +27,38 @@ public class IdentificationDeclarationOptions { private final short CHIP_DEVICE_CONFIG_UDC_MAX_TARGET_APPS = getChipDeviceConfigUdcMaxTargetApps(); + /** Default constructor. */ public IdentificationDeclarationOptions() {} + /** + * Constructor to set only the commissionerPasscode. + * + * @param commissionerPasscode the commissioner passcode flag. + */ + public IdentificationDeclarationOptions(boolean commissionerPasscode) { + this.commissionerPasscode = commissionerPasscode; + } + + /** + * Constructor to set all fields. + * + * @param noPasscode the no passcode flag. + * @param cdUponPasscodeDialog the cd upon passcode dialog flag. + * @param commissionerPasscode the commissioner passcode flag. + * @param commissionerPasscodeReady the commissioner passcode ready flag. + * @param cancelPasscode the cancel passcode flag. + */ public IdentificationDeclarationOptions( boolean noPasscode, boolean cdUponPasscodeDialog, boolean commissionerPasscode, boolean commissionerPasscodeReady, - boolean cancelPasscode, - List targetAppInfos) { + boolean cancelPasscode) { this.noPasscode = noPasscode; this.cdUponPasscodeDialog = cdUponPasscodeDialog; this.commissionerPasscode = commissionerPasscode; this.commissionerPasscodeReady = commissionerPasscodeReady; this.cancelPasscode = cancelPasscode; - this.targetAppInfos = targetAppInfos != null ? targetAppInfos : new ArrayList<>(); } /** @@ -57,28 +74,28 @@ public IdentificationDeclarationOptions( * Passcode input dialog, and instead send a CommissionerDeclaration message if a commissioning * Passcode is needed. */ - public boolean noPasscode = false; + private boolean noPasscode = false; /** * Feature: Coordinate Passcode Dialogs - Flag to instruct the Commissioner to send a * CommissionerDeclaration message when the Passcode input dialog on the Commissioner has been * shown to the user. */ - public boolean cdUponPasscodeDialog = false; + private boolean cdUponPasscodeDialog = false; /** * Feature: Commissioner-Generated Passcode - Flag to instruct the Commissioner to use the * Commissioner-generated Passcode for commissioning. */ - public boolean commissionerPasscode = false; + private boolean commissionerPasscode = false; /** * Feature: Commissioner-Generated Passcode - Flag to indicate whether or not the Commissionee has * obtained the Commissioner Passcode from the user and is therefore ready for commissioning. */ - public boolean commissionerPasscodeReady = false; + private boolean commissionerPasscodeReady = false; /** * Feature: Coordinate Passcode Dialogs Flag - to indicate when the Commissionee user has decided * to exit the commissioning process. */ - public boolean cancelPasscode = false; + private boolean cancelPasscode = false; /** * 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, @@ -104,6 +121,26 @@ public boolean addTargetAppInfo(TargetAppInfo targetAppInfo) { return true; } + public boolean isNoPasscode() { + return noPasscode; + } + + public boolean isCdUponPasscodeDialog() { + return cdUponPasscodeDialog; + } + + public boolean isCommissionerPasscode() { + return commissionerPasscode; + } + + public boolean isCommissionerPasscodeReady() { + return commissionerPasscodeReady; + } + + public boolean isCancelPasscode() { + return cancelPasscode; + } + public List getTargetAppInfoList() { return targetAppInfos; } @@ -129,11 +166,7 @@ public String toString() { sb.append("IdentificationDeclarationOptions::targetAppInfos list: \n"); for (TargetAppInfo targetAppInfo : targetAppInfos) { - sb.append("\t\tTargetAppInfo - Vendor ID: ") - .append(targetAppInfo.vendorId) - .append(", Product ID: ") - .append(targetAppInfo.productId) - .append("\n"); + sb.append("\t\t").append(targetAppInfo.toString()).append("\n"); } return sb.toString(); 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 e8b0553b322c47..1eb38331bc9abe 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 @@ -19,7 +19,65 @@ */ public class TargetAppInfo { /** Target Target Content Application Vendor ID, null means unspecified */ - public Integer vendorId; + private Integer vendorId; /** Target Target Content Application Product ID, null means unspecified */ - public Integer productId; + private Integer productId; + + /** + * Constructor to set both vendorId and productId. + * + * @param vendorId the vendor ID, null means unspecified. + * @param productId the product ID, null means unspecified. + */ + public TargetAppInfo(Integer vendorId, Integer productId) { + this.vendorId = vendorId; + this.productId = productId; + } + + /** + * Constructor to set only the vendorId. + * + * @param vendorId the vendor ID, null means unspecified. + */ + public TargetAppInfo(Integer vendorId) { + this.vendorId = vendorId; + this.productId = null; // product ID unspecified + } + + /** + * Getter for vendorId. + * + * @return the vendor ID, null means unspecified. + */ + public Integer getVendorId() { + return vendorId; + } + + /** + * Getter for productId. + * + * @return the product ID, null means unspecified. + */ + public Integer getProductId() { + return productId; + } + + /** + * Returns both vendorId and productId as an array. + * + * @return an array with vendorId and productId. + */ + public Integer[] getVendorAndProductId() { + return new Integer[] {vendorId, productId}; + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return "TargetAppInfo Vendor ID:" + vendorId + ", Product ID:" + productId; + } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 13293d9b7ca1ee..95d183d2942387 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -67,8 +67,8 @@ class MCConnectionExampleViewModel: ObservableObject { 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)") + self.displayPasscodeInputDialog(on: topViewController, continueConnecting: { userEnteredPasscode in + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Continuing to connect with user entered MCCastingPlayer/Commissioner-Generated passcode: \(userEnteredPasscode)") // Update the CommissionableData in the client defined MCAppParametersDataSource with the user // entered CastingPlayer/Commissioner-Generated setup passcode. This is mandatory for the @@ -79,14 +79,14 @@ class MCConnectionExampleViewModel: ObservableObject { self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback calling MCInitializationExample.getAppParametersDataSource()") if let dataSource = initializationExample.getAppParametersDataSource() { let newCommissionableData = MCCommissionableData( - passcode: UInt32(passcode) ?? 0, + passcode: UInt32(userEnteredPasscode) ?? 0, discriminator: 0, spake2pIterationCount: 1000, spake2pVerifier: nil, spake2pSalt: nil ) dataSource.update(newCommissionableData) - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Updated MCAppParametersDataSource instance with new MCCommissionableData.") + self.Log.info("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." @@ -95,7 +95,7 @@ class MCConnectionExampleViewModel: ObservableObject { 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)" + self.connectionStatus = "Continuing to connect with user entered passcode: \(userEnteredPasscode)" } 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)") diff --git a/examples/tv-casting-app/linux/simple-app-helper.cpp b/examples/tv-casting-app/linux/simple-app-helper.cpp index 8fc0e7fafe0a07..0ade2b462a9040 100644 --- a/examples/tv-casting-app/linux/simple-app-helper.cpp +++ b/examples/tv-casting-app/linux/simple-app-helper.cpp @@ -455,17 +455,17 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) return PrintAllCommands(); } char * eptr; - uint32_t passcode = (uint32_t) strtol(argv[1], &eptr, 10); + uint32_t userEnteredPasscode = (uint32_t) strtol(argv[1], &eptr, 10); if (gAwaitingCommissionerPasscodeInput) { - ChipLogProgress(AppServer, "CommandHandler() setcommissionerpasscode user entered passcode: %d", passcode); + ChipLogProgress(AppServer, "CommandHandler() setcommissionerpasscode user-entered passcode: %d", userEnteredPasscode); gAwaitingCommissionerPasscodeInput = false; // Per connectedhomeip/examples/platform/linux/LinuxCommissionableDataProvider.h: We don't support overriding the - // passcode post-init (it is deprecated!). Therefore we need to initiate a new provider with the user entered + // passcode post-init (it is deprecated!). Therefore we need to initiate a new provider with the user-entered // Commissioner-generated passcode, and then update the CastigApp's AppParameters to update the commissioning session's // passcode. - LinuxDeviceOptions::GetInstance().payload.setUpPINCode = passcode; + LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; LinuxCommissionableDataProvider gCommissionableDataProvider; CHIP_ERROR err = CHIP_NO_ERROR; err = InitCommissionableDataProvider(gCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); From 176b733ab6362d78c6660dffbccf2ea6f32d9ea8 Mon Sep 17 00:00:00 2001 From: Philip Gregor Date: Wed, 26 Jun 2024 17:30:47 -0700 Subject: [PATCH 2/4] Fixing spelling error --- examples/tv-casting-app/APIs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 92441511238abe..e799074dd7afb6 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -929,7 +929,7 @@ CommissionerDeclaration message as follows: `StopConnecting` to alert the `CastingPlayer` that the commissioning attempt was canceled. 1. The Casting Client should then update the commissioning session's PAKE - verifier with the user-entered Passcode using the API's described in the + verifier with the user-entered Passcode using the APIs described in the Initialize the Casting Client section above. 1. Finally, the Casting Client should call `ContinueConnecting` to send a second IdentificationDeclaration message to the `CastingPlayer` with @@ -1001,7 +1001,7 @@ targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, ... ``` -On Andorid, the Casting Client can connect to a `CastingPlayer`, using the +On Android, the Casting Client can connect to a `CastingPlayer`, using the optional `CastingPlayer` / Commissioner-Generated Passcode UDC feature, by successfully calling `verifyOrEstablishConnection`, updating the PAKE verifier and then calling `continueConnecting` on the `CastingPlayer`. From 76c1f5facc69a8675592555a83132c4f15439673 Mon Sep 17 00:00:00 2001 From: Philip Gregor Date: Thu, 27 Jun 2024 17:24:26 -0700 Subject: [PATCH 3/4] Addressed comments by sharadb-amazon --- examples/tv-casting-app/APIs.md | 479 +++++++++--------- .../matter/casting/InitializationExample.java | 10 +- .../IdentificationDeclarationOptions.java | 2 +- .../matter/casting/support/TargetAppInfo.java | 10 - .../MCConnectionExampleViewModel.swift | 20 +- .../linux/simple-app-helper.cpp | 22 +- 6 files changed, 277 insertions(+), 266 deletions(-) diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index e799074dd7afb6..15a7ef38026647 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -159,14 +159,14 @@ client's lifecycle: commissioning data. For the optional `CastingPlayer` / Commissioner-Generated Passcode User - Directed Commissioning (UDC) feature, Commissioning Data `DataProvider` - needs to be updated post Casting Client initialization. In this scenario, - the `CastingPlayer` generates a Passcode and displays it for the user. The - user enters the Passcode into the Casting Client UX. The Casting Client - generates a PAKE verifier based upon this Passcode and enters commissioning - mode using this PAKE verifier. The APIs needed for this update are described - below. See the Matter specification’s UDC section for more information on - the Commissioner-Generated Passcode feature. + Directed Commissioning (UDC) feature, the Commissioning `DataProvider` needs + to be updated during the commissioning process. In this scenario, the + `CastingPlayer` generates a Passcode and displays it for the user. The user + enters the Passcode on the UX of the Casting Client which should update its + Commissioning `DataProvider`. This allows the Matter Casting Library to run + commissioning with the `CastingPlayer` using a PAKE verifier based on the + user-entered passcode. See the Matter specification’s UDC section for more + information on the Commissioner-Generated Passcode feature. On Linux, define a function `InitCommissionableDataProvider` to initialize a `LinuxCommissionableDataProvider` that can provide the required values to @@ -195,36 +195,53 @@ client's lifecycle: } ``` - On Linux, to utilize the optional `CastingPlayer` / Commissioner-Generated - Passcode UDC feature, the Casting Client may initialize a new - `LinuxCommissionableDataProvider` with the user-entered `CastingPlayer` - generated Passcode. The new `LinuxCommissionableDataProvider` may be passed - to the CastingApp using `UpdateCommissionableDataProvider`. + On Linux, if using the `CastingPlayer` / Commissioner-Generated Passcode UDC + feature, set up a new `LinuxCommissionableDataProvider` when called back on + the `CommissionerDeclarationCallback` during the + [VerifyOrEstablishConnection()](#connect-to-a-casting-player) API call + (described later). The `CastingPlayer` generated passcode (as entered by the + user on the Casting Client UX) should be set in this + `LinuxCommissionableDataProvider` which should then be passed to the + CastingApp using the `UpdateCommissionableDataProvider` API. ```c LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; LinuxCommissionableDataProvider gCommissionableDataProvider; CHIP_ERROR err = CHIP_NO_ERROR; err = InitCommissionableDataProvider(gCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, + "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } err = matter::casting::core::CastingApp::GetInstance()->UpdateCommissionableDataProvider(&gCommissionableDataProvider); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, + "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } ``` On Android, define a `commissioningDataProvider` that can provide the - required values to the `CastingApp`. To utilize the optional `CastingPlayer` - / Commissioner-Generated Passcode UDC feature, the Casting Client may also - define a function to update the CommissionableDataProvider - post-initialization. `updateCommissionableDataSetupPasscode` is an example - function to update the CommissionableData with the `CastingPlayer` generated - passcode. + required values to the `CastingApp`. If using the `CastingPlayer` / + Commissioner-Generated Passcode UDC feature, the Casting Client needs to + update this `commissioningDataProvider` during the + [verifyOrEstablishConnection()](#connect-to-a-casting-player) API call + (described later). In the example below, + `updateCommissionableDataSetupPasscode` updates the CommissionableData with + the `CastingPlayer` generated passcode entered by the user on the Casting + Client UX. ```java - // Dummy values for commissioning demonstration only. These are hard coded in the example tv-app: - // connectedhomeip/examples/tv-app/tv-common/src/AppTv.cpp - private static final long DUMMY_SETUP_PASSCODE = 20202021; - private static final int DUMMY_DISCRIMINATOR = 3874; - public static class CommissionableDataProvider implements DataProvider { CommissionableData commissionableData = + // Dummy values for commissioning demonstration only. These are hard coded in the example tv-app: + // connectedhomeip/examples/tv-app/tv-common/src/AppTv.cpp + private static final long DUMMY_SETUP_PASSCODE = 20202021; + private static final int DUMMY_DISCRIMINATOR = 3874; + new CommissionableData(DUMMY_SETUP_PASSCODE, DUMMY_DISCRIMINATOR); @Override @@ -232,7 +249,7 @@ client's lifecycle: return commissionableData; } - // For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: public void updateCommissionableDataSetupPasscode(long setupPasscode, int discriminator) { commissionableData.setSetupPasscode(setupPasscode); commissionableData.setDiscriminator(discriminator); @@ -242,11 +259,13 @@ client's lifecycle: On iOS, add a `func commissioningDataProvider` to the `MCAppParametersDataSource` class defined above, that can provide the - required values to the `MCCastingApp`. To utilize the optional - `CastingPlayer` / Commissioner-Generated Passcode UDC feature, the Casting - Client may also add a function to update the CommissionableDataProvider - post-initialization. `update` is an example function to update the - CommissionableData with the `CastingPlayer` generated passcode. + required values to the `MCCastingApp`. If using the `CastingPlayer` / + Commissioner-Generated Passcode UDC feature, the Casting Client needs to + update this `commissioningDataProvider` during the + [VerifyOrEstablishConnection()](#connect-to-a-casting-player) API call + (described later). In the example below, the `update` function updates the + CommissionableData with the `CastingPlayer` generated passcode entered by + the user on the Casting Client UX. ```swift // Dummy values for demonstration only. @@ -262,7 +281,7 @@ client's lifecycle: return commissionableData } - // For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: func update(_ newCommissionableData: MCCommissionableData) { self.commissionableData = newCommissionableData } @@ -756,10 +775,39 @@ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as `deviceName`, `vendorId`, `productId`, etc. which can help the user pick the right `CastingPlayer`. A Casting Client can attempt to connect to the -`selectedCastingPlayer` using Matter User Directed Commissioning (UDC). To -utilize the optional `CastingPlayer` / Commissioner-Generated Passcode UDC -feature to connect to a `CastingPlayer`, the `CastingPlayer` object must -indicate its support for it via the`supportsCommissionerGeneratedPasscode` flag. +`selectedCastingPlayer` using Matter User Directed Commissioning (UDC), where +the Casting Client generates the passcode. Alternately, a Casting Client can +attempt to connect to a `CastingPlayer`, using the `CastingPlayer`/ +Commissioner-Generated Passcode UDC feature, if the +`supportsCommissionerGeneratedPasscode` flag on the `selectedCastingPlayer` is +set to `true`. + +For a Casting Client to connect to a `CastingPlayer` using the optional +`CastingPlayer` / Commissioner-Generated Passcode UDC feature, the Casting +Client may specify optional parameters in the `VerifyOrEstablishConnection` +function call and then respond to the CastingPlayer's CommissionerDeclaration +message as follows: + +1. In `VerifyOrEstablishConnection` the Casting Client should set the UDC + IdentificationDeclaration `CommissionerPasscode` field to true and provide a + `CommissionerDeclarationCallback` in the `ConnectionCallbacks` parameter to + handle the CastingPlayer's CommissionerDeclaration message during + commissioning. +2. Upon receiving the CastingPlayer’s CommissionerDeclaration message with + PasscodeDialogDisplayed set to true, the Casting Client should prompt the + user to input the Passcode displayed on the `CastingPlayer` UX. If the user + declines to enter the Passcode, the Casting Client should call + `StopConnecting` to alert the `CastingPlayer` that the commissioning attempt + was canceled. +3. The Casting Client should then update the passcode to be used for + commissioning session to the user-entered Passcode. Refer to how to set up + the `commissioningDataProvider` in + [Initialize the Casting Client](#initialize-the-casting-client) section + above. +4. Finally, the Casting Client should call `ContinueConnecting` to send a second + IdentificationDeclaration message to the `CastingPlayer` with + `CommissionerPasscodeReady` in `IdentificationDeclarationOptions` set to + true. The Matter TV Casting library locally caches information required to reconnect to a `CastingPlayer`, once the Casting client has been commissioned by it. After @@ -775,170 +823,11 @@ Casting library to go through the full UDC process in search of the desired Endpoint, in cases where it is not available in the Casting client's cache. On Linux, the Casting Client can connect to a `CastingPlayer` by successfully -calling `VerifyOrEstablishConnection` on it. - -```c -// VendorId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection -const uint16_t kDesiredEndpointVendorId = 65521; - -void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) -{ - if(err == CHIP_NO_ERROR) - { - ChipLogProgress(AppServer, "ConnectionHandler: Successfully connected to CastingPlayer(ID: %s)", castingPlayer->GetId()); - ... - } -} - -... -matter::casting::core::IdentificationDeclarationOptions idOptions; -chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; -targetAppInfo.vendorId = kDesiredEndpointVendorId; -CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); - -matter::casting::core::ConnectionCallbacks connectionCallbacks; -connectionCallbacks.mOnConnectionComplete = ConnectionHandler; - -// targetCastingPlayer is a discovered CastingPlayer -targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, - matter::casting::core::kCommissioningWindowTimeoutSec, - idOptions); -... -``` - -On Android, the Casting Client may call `verifyOrEstablishConnection` on the -`CastingPlayer` object it wants to connect to. - -```java -private static final short MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; -private static final Integer DESIRED_TARGET_APP_VENDOR_ID = 65521; - -IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(); -TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); -idOptions.addTargetAppInfo(targetAppInfo); - -ConnectionCallbacks connectionCallbacks = - new ConnectionCallbacks( - new MatterCallback() { - @Override - public void handle(Void v) { - Log.i( - TAG, - "Successfully connected to CastingPlayer with deviceId: " - + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Successfully connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName() - + "\n\n"); - connectionFragmentNextButton.setEnabled(true); - }); - } - }, - new MatterCallback() { - @Override - public void handle(MatterError err) { - Log.e(TAG, "CastingPlayer connection failed: " + err); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " + err + "\n\n"); - }); - } - }, - onCommissionerDeclaration:null - ); - -MatterError err = - targetCastingPlayer.verifyOrEstablishConnection( - connectionCallbacks, MIN_CONNECTION_TIMEOUT_SEC, idOptions); - -if (err.hasError()) -{ - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " + err + "\n\n"); - }); -} -``` - -On iOS, the Casting Client may call `verifyOrEstablishConnection` on the -`MCCastingPlayer` object it wants to connect to and handle any `NSErrors` that -may happen in the process. - -```swift -// VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection -let kDesiredEndpointVendorId: UInt16 = 65521; - -@Published var connectionSuccess: Bool?; - -@Published var connectionStatus: String?; - -func connect(selectedCastingPlayer: MCCastingPlayer?) { - - 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 = "Successfully connected to Casting Player!" - } else { - self.connectionSuccess = false - self.connectionStatus = "Connection to Casting Player failed with: \(String(describing: err))" - } - } - } - - let identificationDeclarationOptions: MCIdentificationDeclarationOptions - let targetAppInfo: MCTargetAppInfo - let connectionCallbacks: MCConnectionCallbacks - identificationDeclarationOptions = MCIdentificationDeclarationOptions() - targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) - connectionCallbacks = MCConnectionCallbacks( - callbacks: connectionCompleteCallback, - commissionerDeclarationCallback: nil - ) - identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) - - let err = selectedCastingPlayer?.verifyOrEstablishConnection(with: connectionCallbacks, identificationDeclarationOptions: identificationDeclarationOptions) - if err != nil { - self.Log.error("MCConnectionExampleViewModel connect(), MCCastingPlayer.verifyOrEstablishConnection() failed due to: \(err)") - } -} -``` - -For a Casting Client to connect to a `CastingPlayer` using the optional -`CastingPlayer` / Commissioner-Generated Passcode UDC feature, the Casting -Client may specify optional parameters in the `VerifyOrEstablishConnection` -function call, handle and then respond to the CastingPlayer's -CommissionerDeclaration message as follows: - -1. In `VerifyOrEstablishConnection` the Casting Client should set the UDC - IdentificationDeclaration `CommissionerPasscode` field to true and provide a - `ConnectionCallbacks` callback to handle the CastingPlayer's - CommissionerDeclaration message. -1. Upon receiving the CastingPlayer’s CommissionerDeclaration message with - PasscodeDialogDisplayed set to true, the Casting Client should prompt the - user to input the Passcode displayed on the `CastingPlayer` UX. If the user - declines to enter the Passcode, the Casting Client should call - `StopConnecting` to alert the `CastingPlayer` that the commissioning attempt - was canceled. -1. The Casting Client should then update the commissioning session's PAKE - verifier with the user-entered Passcode using the APIs described in the - Initialize the Casting Client section above. -1. Finally, the Casting Client should call `ContinueConnecting` to send a second - IdentificationDeclaration message to the `CastingPlayer` with - `CommissionerPasscodeReady` set to true. - -On Linux, the Casting Client can connect to a `CastingPlayer`, using the -optional `CastingPlayer` / Commissioner-Generated Passcode UDC feature, by -successfully calling `VerifyOrEstablishConnection`, updating the PAKE verifier -and then calling `ContinueConnecting` on the `CastingPlayer`. +calling `VerifyOrEstablishConnection` on it. Alternately, the Casting Client can +connect to a `CastingPlayer` using the `CastingPlayer` / Commissioner-Generated +Passcode UDC feature, by successfully calling `VerifyOrEstablishConnection`, +updating the commissioning passcode and then calling `ContinueConnecting` on the +`CastingPlayer`. ```c // VendorId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection @@ -953,7 +842,8 @@ void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * ca } } -// For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: +// Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source, chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) { @@ -964,7 +854,7 @@ void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source // A Passcode is now displayed for the user by the CastingPlayer. Prompt the user to enter the Passcode. ... - // Update the commissioning session's PAKE verifier with the user-entered Passcode + // Update the commissioning session's passcode with the user-entered Passcode LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; LinuxCommissionableDataProvider gCommissionableDataProvider; CHIP_ERROR err = CHIP_NO_ERROR; @@ -977,11 +867,20 @@ void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source { ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode ContinueConnecting() err %" CHIP_ERROR_FORMAT, err.Format()); + // Since continueConnecting() failed, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner by calling StopConnecting(). + err = targetCastingPlayer->StopConnecting(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode, ContinueConnecting() failed and then StopConnecting failed due to err %" CHIP_ERROR_FORMAT, err.Format()); + } } } } -... +// Specify the TargetApp 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 +// CastingStore matter::casting::core::IdentificationDeclarationOptions idOptions; chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; targetAppInfo.vendorId = kDesiredEndpointVendorId; @@ -990,7 +889,10 @@ CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); matter::casting::core::ConnectionCallbacks connectionCallbacks; connectionCallbacks.mOnConnectionComplete = ConnectionHandler; -// For the optional CastingPlayer / Commissioner-Generated Passcode UDC feature. +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: +// Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / +// Commissioner to use the Commissioner-generated Passcode for commissioning. Set the +// CommissionerDeclarationCallback in ConnectionCallbacks. idOptions.mCommissionerPasscode = true; connectionCallbacks.mCommissionerDeclarationCallback = CommissionerDeclarationCallback; @@ -1001,55 +903,68 @@ targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, ... ``` -On Android, the Casting Client can connect to a `CastingPlayer`, using the -optional `CastingPlayer` / Commissioner-Generated Passcode UDC feature, by -successfully calling `verifyOrEstablishConnection`, updating the PAKE verifier -and then calling `continueConnecting` on the `CastingPlayer`. +On Android, the Casting Client may call `verifyOrEstablishConnection` on the +`CastingPlayer` object it wants to connect to. Alternately, the Casting Client +can connect to a `CastingPlayer`, using the `CastingPlayer` / +Commissioner-Generated Passcode UDC feature by successfully calling +`verifyOrEstablishConnection`, updating the commissioning passcode and then +calling `continueConnecting` on the `CastingPlayer`. ```java private static final short MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; private static final Integer DESIRED_TARGET_APP_VENDOR_ID = 65521; -// Constructor to set only the commissionerPasscode. -IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(commissionerPasscode:true); +// Specify the TargetApp 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 +// CastingStore +IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(); TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); idOptions.addTargetAppInfo(targetAppInfo); +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. +// Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / +// Commissioner to use the Commissioner-generated Passcode for commissioning. +idOptions = new IdentificationDeclarationOptions(commissionerPasscode:true); +idOptions.addTargetAppInfo(targetAppInfo); + ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks( new MatterCallback() { - @Override - public void handle(Void v) { - Log.i( - TAG, - "Successfully connected to CastingPlayer with deviceId: " - + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Successfully connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName() - + "\n\n"); - connectionFragmentNextButton.setEnabled(true); - }); - } + @Override + public void handle(Void v) { + Log.i( + TAG, + "Successfully connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Successfully connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } }, new MatterCallback() { - @Override - public void handle(MatterError err) { - Log.e(TAG, "CastingPlayer connection failed: " + err); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " + err + "\n\n"); - }); - } + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPlayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } }, + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. + // This can be null if using Casting Client / Commissionee generated passcode commissioning. new MatterCallback() { - @Override - public void handle(CommissionerDeclaration cd) { + @Override + public void handle(CommissionerDeclaration cd) { getActivity() .runOnUiThread( () -> { @@ -1060,29 +975,51 @@ ConnectionCallbacks connectionCallbacks = displayPasscodeInputDialog(getActivity()); ... - // Update the commissioning session's PAKE verifier with the user-entered Passcode + // Update the commissioning session's passcode with the user-entered Passcode InitializationExample.commissionableDataProvider.updateCommissionableDataSetupPasscode( passcodeLongValue, DEFAULT_DISCRIMINATOR_FOR_CGP_FLOW); // Call continueConnecting to complete commissioning. MatterError err = targetCastingPlayer.continueConnecting(); - ... + if (err.hasError()) { + ... + Log.e( + TAG, + "displayPasscodeInputDialog() continueConnecting() failed, calling stopConnecting() due to: " + + err); + // Since continueConnecting() failed, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner by calling stopConnecting(). + err = targetCastingPlayer.stopConnecting(); + if (err.hasError()) { + Log.e(TAG, "displayPasscodeInputDialog() stopConnecting() failed due to: " + err); + } + } } }); } } ); -MatterError err = - targetCastingPlayer.verifyOrEstablishConnection( +MatterError err = targetCastingPlayer.verifyOrEstablishConnection( connectionCallbacks, MIN_CONNECTION_TIMEOUT_SEC, idOptions); -... +if (err.hasError()) +{ + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); +} ``` -On iOS, the Casting Client can connect to a `CastingPlayer`, using the optional -`CastingPlayer` / Commissioner-Generated Passcode UDC feature, by successfully -calling `verifyOrEstablishConnection`, updating the PAKE verifier and then -calling `continueConnecting` on the `CastingPlayer`. +On iOS, the Casting Client may call `verifyOrEstablishConnection` on the +`MCCastingPlayer` object it wants to connect to and handle any `NSErrors` that +may happen in the process. Alternately, the Casting Client can connect to a +`CastingPlayer` using the `CastingPlayer` / Commissioner-Generated Passcode UDC +feature, by successfully calling `verifyOrEstablishConnection`, updating the +commissioning passcode and then calling `continueConnecting` on the +`CastingPlayer`. ```swift // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection @@ -1107,6 +1044,8 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { } } + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. let commissionerDeclarationCallback: (MCCommissionerDeclaration) -> Void = { commissionerDeclarationMessage in DispatchQueue.main.async { self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, recived a message form the MCCastingPlayer:\n\(commissionerDeclarationMessage)") @@ -1115,7 +1054,7 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { self.displayPasscodeInputDialog(on: topViewController, continueConnecting: { userEnteredPasscode in self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Continuing to connect with user entered MCCastingPlayer/Commissioner-Generated passcode: \(passcode)") - // Update the commissioning session's PAKE verifier with the user-entered Passcode + // Update the commissioning session's passcode with the user-entered Passcode if let dataSource = initializationExample.getAppParametersDataSource() { let newCommissionableData = MCCommissionableData( passcode: UInt32(userEnteredPasscode) ?? 0, @@ -1123,14 +1062,38 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ... ) dataSource.update(newCommissionableData) + ... + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, calling stopConnecting()") + self.connectionStatus = "Failed to update the MCAppParametersDataSource with the user entered passcode: \n\nRoute back and try again." + self.connectionSuccess = false + // Since we failed to update the passcode, attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner. + let err = selectedCastingPlayer?.stopConnecting() + if err == nil { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() succeeded") + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() failed due to: \(err)") + } + return } // Call continueConnecting to complete commissioning. let errContinue = selectedCastingPlayer?.continueConnecting() if errContinue == nil { self.connectionStatus = "Continuing to connect with user entered passcode: \(userEnteredPasscode)" + } 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)") + // Since continueConnecting() failed, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner by calling stopConnecting(). + let err = selectedCastingPlayer?.stopConnecting() + if err == nil { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() succeeded") + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() failed due to: \(err)") + } } - ... }, cancelConnecting: { self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user, calling MCCastingPlayer.stopConnecting()") let err = selectedCastingPlayer?.stopConnecting() @@ -1144,6 +1107,22 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { let identificationDeclarationOptions: MCIdentificationDeclarationOptions let targetAppInfo: MCTargetAppInfo let connectionCallbacks: MCConnectionCallbacks + + // Specify the TargetApp 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 + // CastingStore + identificationDeclarationOptions = MCIdentificationDeclarationOptions() + targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) + connectionCallbacks = MCConnectionCallbacks( + callbacks: connectionCompleteCallback, + commissionerDeclarationCallback: nil + ) + identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) + + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / + // Commissioner to use the Commissioner-generated Passcode for commissioning. Set the + // CommissionerDeclarationCallback in MCConnectionCallbacks. identificationDeclarationOptions = MCIdentificationDeclarationOptions(commissionerPasscodeOnly: true) targetAppInfo = MCTargetAppInfo(vendorId: kDesiredEndpointVendorId) connectionCallbacks = MCConnectionCallbacks( @@ -1153,7 +1132,9 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) let err = selectedCastingPlayer?.verifyOrEstablishConnection(with: connectionCallbacks, identificationDeclarationOptions: identificationDeclarationOptions) - ... + if err != nil { + self.Log.error("MCConnectionExampleViewModel connect(), MCCastingPlayer.verifyOrEstablishConnection() failed due to: \(err)") + } } ``` diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java index 6cae9ab8326474..6ccf2a3591c3d8 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java @@ -28,10 +28,6 @@ public class InitializationExample { private static final String TAG = InitializationExample.class.getSimpleName(); - // Dummy values for commissioning demonstration only. These are hard coded in the example tv-app: - // connectedhomeip/examples/tv-app/tv-common/src/AppTv.cpp - private static final long DUMMY_SETUP_PASSCODE = 20202021; - private static final int DUMMY_DISCRIMINATOR = 3874; /** * DataProvider implementation for the Unique ID that is used by the SDK to generate the Rotating @@ -53,6 +49,12 @@ public byte[] get() { * through commissioning */ public static class CommissionableDataProvider implements DataProvider { + // Dummy values for commissioning demonstration only. These are hard coded in the example + // tv-app: + // connectedhomeip/examples/tv-app/tv-common/src/AppTv.cpp + private static final long DUMMY_SETUP_PASSCODE = 20202021; + private static final int DUMMY_DISCRIMINATOR = 3874; + CommissionableData commissionableData = new CommissionableData(DUMMY_SETUP_PASSCODE, DUMMY_DISCRIMINATOR); 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 928ab7dc5d7dc3..2539fe86b84a33 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 @@ -166,7 +166,7 @@ public String toString() { sb.append("IdentificationDeclarationOptions::targetAppInfos list: \n"); for (TargetAppInfo targetAppInfo : targetAppInfos) { - sb.append("\t\t").append(targetAppInfo.toString()).append("\n"); + sb.append("\t\t").append(targetAppInfo).append("\n"); } return sb.toString(); 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 1eb38331bc9abe..ab3c70f495fc8d 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 @@ -41,7 +41,6 @@ public TargetAppInfo(Integer vendorId, Integer productId) { */ public TargetAppInfo(Integer vendorId) { this.vendorId = vendorId; - this.productId = null; // product ID unspecified } /** @@ -62,15 +61,6 @@ public Integer getProductId() { return productId; } - /** - * Returns both vendorId and productId as an array. - * - * @return an array with vendorId and productId. - */ - public Integer[] getVendorAndProductId() { - return new Integer[] {vendorId, productId}; - } - /** * Returns a string representation of the object. * diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 95d183d2942387..895b4a58e31d04 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -88,8 +88,18 @@ class MCConnectionExampleViewModel: ObservableObject { dataSource.update(newCommissionableData) self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Updated MCAppParametersDataSource instance with new MCCommissionableData.") } else { - self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed") + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, calling stopConnecting()") self.connectionStatus = "Failed to update the MCAppParametersDataSource with the user entered passcode: \n\nRoute back and try again." + self.connectionSuccess = false + // Since we failed to update the passcode, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner. + let err = selectedCastingPlayer?.stopConnecting() + if err == nil { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() succeeded") + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, InitializationExample.getAppParametersDataSource() failed, then stopConnecting() failed due to: \(err)") + } + return } self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, calling MCCastingPlayer.continueConnecting()") @@ -99,6 +109,14 @@ class MCConnectionExampleViewModel: ObservableObject { } 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)") + // Since continueConnecting() failed, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner. + let err = selectedCastingPlayer?.stopConnecting() + if err == nil { + self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() succeeded") + } else { + self.Log.error("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, MCCastingPlayer.continueConnecting() failed, then stopConnecting() failed due to: \(err)") + } } }, cancelConnecting: { self.Log.info("MCConnectionExampleViewModel connect() commissionerDeclarationCallback, Connection attempt cancelled by the user, calling MCCastingPlayer.stopConnecting()") diff --git a/examples/tv-casting-app/linux/simple-app-helper.cpp b/examples/tv-casting-app/linux/simple-app-helper.cpp index 0ade2b462a9040..3cf13fac8606f2 100644 --- a/examples/tv-casting-app/linux/simple-app-helper.cpp +++ b/examples/tv-casting-app/linux/simple-app-helper.cpp @@ -394,6 +394,10 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) targetCastingPlayer = castingPlayers.at(index); gCommissionerGeneratedPasscodeFlowRunning = false; + + // Specify the TargetApp 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 + // CastingStore matter::casting::core::IdentificationDeclarationOptions idOptions; chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; targetAppInfo.vendorId = kDesiredEndpointVendorId; @@ -474,6 +478,7 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); + return err; } // Update the CommissionableDataProvider stored in this CastingApp's AppParameters and the CommissionableDataProvider to // be used for the commissioning session. @@ -483,14 +488,27 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); + return err; } // Continue Connecting to the target CastingPlayer with the user entered Commissioner-generated Passcode. err = targetCastingPlayer->ContinueConnecting(); if (err != CHIP_NO_ERROR) { - ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode ContinueConnecting() err %" CHIP_ERROR_FORMAT, + ChipLogError(AppServer, + "CommandHandler() setcommissionerpasscode ContinueConnecting() failed due to err %" CHIP_ERROR_FORMAT, err.Format()); + // Since continueConnecting() failed, Attempt to cancel the connection attempt with + // the CastingPlayer/Commissioner by calling StopConnecting(). + err = targetCastingPlayer->StopConnecting(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, + "CommandHandler() setcommissionerpasscode, ContinueConnecting() failed and then StopConnecting " + "failed due to err %" CHIP_ERROR_FORMAT, + err.Format()); + } + return err; } } else @@ -498,6 +516,7 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) ChipLogError( AppServer, "CommandHandler() setcommissionerpasscode, no Commissioner-Generated passcode input expected at this time."); + return CHIP_ERROR_INVALID_ARGUMENT; } } if (strcmp(argv[0], "stop-connecting") == 0) @@ -507,6 +526,7 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() stop-connecting, err %" CHIP_ERROR_FORMAT, err.Format()); + return err; } } if (strcmp(argv[0], "print-bindings") == 0) From c0bc80e930d3f6b0f0d20c84b8daa1e424bb4995 Mon Sep 17 00:00:00 2001 From: Philip Gregor Date: Fri, 28 Jun 2024 12:12:39 -0700 Subject: [PATCH 4/4] Addressed comments by sharadb-amazon2 --- examples/tv-casting-app/APIs.md | 16 ++++++++-------- .../casting/ConnectionExampleFragment.java | 8 +++++--- .../IdentificationDeclarationOptions.java | 9 --------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 15a7ef38026647..75c63eb2379eac 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -249,7 +249,7 @@ client's lifecycle: return commissionableData; } - // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature: public void updateCommissionableDataSetupPasscode(long setupPasscode, int discriminator) { commissionableData.setSetupPasscode(setupPasscode); commissionableData.setDiscriminator(discriminator); @@ -281,7 +281,7 @@ client's lifecycle: return commissionableData } - // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature: func update(_ newCommissionableData: MCCommissionableData) { self.commissionableData = newCommissionableData } @@ -842,7 +842,7 @@ void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * ca } } -// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature: // Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source, chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) @@ -889,7 +889,7 @@ CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); matter::casting::core::ConnectionCallbacks connectionCallbacks; connectionCallbacks.mOnConnectionComplete = ConnectionHandler; -// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow: +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature: // Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / // Commissioner to use the Commissioner-generated Passcode for commissioning. Set the // CommissionerDeclarationCallback in ConnectionCallbacks. @@ -921,7 +921,7 @@ IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOption TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); idOptions.addTargetAppInfo(targetAppInfo); -// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. +// If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature. // Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / // Commissioner to use the Commissioner-generated Passcode for commissioning. idOptions = new IdentificationDeclarationOptions(commissionerPasscode:true); @@ -959,7 +959,7 @@ ConnectionCallbacks connectionCallbacks = }); } }, - // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature. // Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. // This can be null if using Casting Client / Commissionee generated passcode commissioning. new MatterCallback() { @@ -1044,7 +1044,7 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { } } - // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature. // Define a callback to handle CastingPlayer’s CommissionerDeclaration messages. let commissionerDeclarationCallback: (MCCommissionerDeclaration) -> Void = { commissionerDeclarationMessage in DispatchQueue.main.async { @@ -1119,7 +1119,7 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ) identificationDeclarationOptions.addTargetAppInfo(targetAppInfo) - // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature commissioning flow. + // If using the alternate CastingPlayer / Commissioner-Generated Passcode UDC feature. // Set the IdentificationDeclaration CommissionerPasscode flag to instruct the CastingPlayer / // Commissioner to use the Commissioner-generated Passcode for commissioning. Set the // CommissionerDeclarationCallback in MCConnectionCallbacks. diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index bb43165be6fcfb..b61b63f3e0785e 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -128,12 +128,13 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { () -> { Log.d(TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection()"); - IdentificationDeclarationOptions idOptions = new IdentificationDeclarationOptions(); + IdentificationDeclarationOptions idOptions; TargetAppInfo targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID); if (useCommissionerGeneratedPasscode) { - // Constructor to set only the commissionerPasscode. - idOptions = new IdentificationDeclarationOptions(true); + // Set commissionerPasscode to true for CastingPlayer/Commissioner-Generated + // passcode commissioning. + idOptions = new IdentificationDeclarationOptions(false, false, true, false, false); targetAppInfo = new TargetAppInfo(DESIRED_TARGET_APP_VENDOR_ID_FOR_CGP_FLOW); Log.d( TAG, @@ -142,6 +143,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + ", useCommissionerGeneratedPasscode: " + useCommissionerGeneratedPasscode); } else { + idOptions = new IdentificationDeclarationOptions(); Log.d( TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection() Target Content Application Vendor ID: " 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 2539fe86b84a33..48c7acecae45d2 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 @@ -30,15 +30,6 @@ public class IdentificationDeclarationOptions { /** Default constructor. */ public IdentificationDeclarationOptions() {} - /** - * Constructor to set only the commissionerPasscode. - * - * @param commissionerPasscode the commissioner passcode flag. - */ - public IdentificationDeclarationOptions(boolean commissionerPasscode) { - this.commissionerPasscode = commissionerPasscode; - } - /** * Constructor to set all fields. *