diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 5888972ad97e42..f0d1cfda70d2fa 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -137,7 +137,7 @@ client's lifecycle: `func castingAppDidReceiveRequestForRotatingDeviceIdUniqueId` in a class, `MTRAppParametersDataSource`, that implements the `MTRDataSource`: - ```objectivec + ```swift class MTRAppParametersDataSource : NSObject, MTRDataSource { func castingAppDidReceiveRequestForRotatingDeviceIdUniqueId(_ sender: Any) -> Data { @@ -199,7 +199,7 @@ client's lifecycle: `MTRAppParametersDataSource` class defined above, that can provide the required values to the `MTRCastingApp`. - ```objectivec + ```swift func castingAppDidReceiveRequestForCommissionableData(_ sender: Any) -> MTRCommissionableData { // dummy values for demonstration only return MTRCommissionableData( @@ -276,7 +276,7 @@ client's lifecycle: `MTRDeviceAttestationCredentials` and sign messages for the Casting Client, respectively. - ```objectivec + ```swift // dummy DAC values for demonstration only let kDevelopmentDAC_Cert_FFF1_8001: Data = Data(base64Encoded: "MIIB....CXE1M=")!; let kDevelopmentDAC_PrivateKey_FFF1_8001: Data = Data(base64Encoded: "qrYAtE+/8=")!; @@ -292,21 +292,38 @@ client's lifecycle: productAttestationIntermediateCert: KPAI_FFF1_8000_Cert_Array) } - func castingApp(_ sender: Any, didReceiveRequestToSignCertificateRequest csrData: Data) -> Data { - var privateKey = Data() - privateKey.append(kDevelopmentDAC_PublicKey_FFF1_8001); - privateKey.append(kDevelopmentDAC_PrivateKey_FFF1_8001); + func castingApp(_ sender: Any, didReceiveRequestToSignCertificateRequest csrData: Data, outRawSignature: AutoreleasingUnsafeMutablePointer) -> MatterError { + Log.info("castingApp didReceiveRequestToSignCertificateRequest") - let privateKeyRef: SecKey = SecKeyCreateWithData(privateKey as NSData, + // get the private SecKey + var privateKeyData = Data() + privateKeyData.append(kDevelopmentDAC_PublicKey_FFF1_8001); + privateKeyData.append(kDevelopmentDAC_PrivateKey_FFF1_8001); + let privateSecKey: SecKey = SecKeyCreateWithData(privateKeyData as NSData, [ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits: 256 ] as NSDictionary, nil)! - let _:Unmanaged = Unmanaged.passRetained(privateKeyRef); + // sign csrData to get asn1SignatureData + var error: Unmanaged? + let asn1SignatureData: CFData? = SecKeyCreateSignature(privateSecKey, .ecdsaSignatureMessageX962SHA256, csrData as CFData, &error) + if(error != nil) + { + Log.error("Failed to sign message. Error: \(String(describing: error))") + return MATTER_ERROR_INVALID_ARGUMENT + } + else if (asn1SignatureData == nil) + { + Log.error("Failed to sign message. asn1SignatureData is nil") + return MATTER_ERROR_INVALID_ARGUMENT + } - // use SecKey above to sign csrData and return resulting value + // convert ASN.1 DER signature to SEC1 raw format + return MTRCryptoUtils.ecdsaAsn1SignatureToRaw(withFeLengthBytes: 32, + asn1Signature: asn1SignatureData!, + outRawSignature: &outRawSignature.pointee) } ``` @@ -319,6 +336,7 @@ On Linux, create an `AppParameters` object using the `RotatingDeviceIdUniqueIdProvider`, `LinuxCommissionableDataProvider`, `CommonCaseDeviceServerInitParamsProvider`, `ExampleDACProvider` and `DefaultDACVerifier`, and call `CastingApp::GetInstance()->Initialize` with it. +Then, call `Start` on the `CastingApp`. ```c LinuxCommissionableDataProvider gCommissionableDataProvider; @@ -357,7 +375,8 @@ int main(int argc, char * argv[]) { On Android, create an `AppParameters` object using the `rotatingDeviceIdUniqueIdProvider`, `commissioningDataProvider`, `dacProvider` and `DataProvider`, and call -`CastingApp.getInstance().initialize` with it. +`CastingApp.getInstance().initialize` with it. Then, call `start` on the +`CastingApp` ```java public static MatterError initAndStart(Context applicationContext) { @@ -396,7 +415,7 @@ public static MatterError initAndStart(Context applicationContext) { On iOS, call `MTRCastingApp.initialize` with an object of the `MTRAppParametersDataSource`. -```objectivec +```swift func initialize() -> MatterError { if let castingApp = MTRCastingApp.getSharedInstance() { return castingApp.initialize(with: MTRAppParametersDataSource()) @@ -406,9 +425,73 @@ func initialize() -> MatterError { } ``` +After initialization, on iOS, call `start` and `stop` on the `MTRCastingApp` +shared instance when the App sends the +`UIApplication.didBecomeActiveNotification` and +`UIApplication.willResignActiveNotification` + +```objectivec +struct TvCastingApp: App { + let Log = Logger(subsystem: "com.matter.casting", category: "TvCastingApp") + @State + var firstAppActivation: Bool = true + + var body: some Scene { + WindowGroup { + ContentView() + .onAppear(perform: { + let err: Error? = MTRInitializationExample().initialize() + if err != nil + { + self.Log.error("MTRCastingApp initialization failed \(err)") + return + } + }) + .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in + self.Log.info("TvCastingApp: UIApplication.didBecomeActiveNotification") + if let castingApp = MTRCastingApp.getSharedInstance() + { + castingApp.start(completionBlock: { (err : Error?) -> () in + if err != nil + { + self.Log.error("MTRCastingApp start failed \(err)") + } + }) + } + } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in + self.Log.info("TvCastingApp: UIApplication.willResignActiveNotification") + if let castingApp = MTRCastingApp.getSharedInstance() + { + castingApp.stop(completionBlock: { (err : Error?) -> () in + if err != nil + { + self.Log.error("MTRCastingApp stop failed \(err)") + } + }) + } + } + } // WindowGroup + } // body +} // App +``` + +Note about on-device cache: The Casting App maintains an on-device cache +containing information about the Casting Players it has connected with so far. +This cached information allows the Casting App to connect with Casting Players +(that it had previously connected with) faster and using fewer resources, by +potentially skipping the longer commissioning process and instead, simply +re-establishing the CASE session. This cache can be cleared by calling the +`ClearCache` API on the `CastingApp`, say when the user signs out of the app. +See API and its documentation for [Linux](tv-casting-common/core/CastingApp.h), +Android and +[iOS](darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingApp.h). + ### Discover Casting Players -_{Complete Discovery examples: [Linux](linux/simple-app-helper.cpp)}_ +_{Complete Discovery examples: [Linux](linux/simple-app-helper.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MTRDiscoveryExampleViewModel.swift)}_ The Casting Client discovers `CastingPlayers` using Matter Commissioner discovery over DNS-SD by listening for `CastingPlayer` events as they are @@ -439,6 +522,111 @@ public: }; ``` +On Android, implement the `CastingPlayerDiscovery.CastingPlayerChangeListener`. + +```java +private static final CastingPlayerDiscovery.CastingPlayerChangeListener castingPlayerChangeListener = + new CastingPlayerDiscovery.CastingPlayerChangeListener() { + private final String TAG = CastingPlayerDiscovery.CastingPlayerChangeListener.class.getSimpleName(); + + @Override + public void onAdded(CastingPlayer castingPlayer) { + Log.i(TAG, "onAdded() Discovered CastingPlayer deviceId: " + castingPlayer.getDeviceId()); + // Display CastingPlayer info on the screen + new Handler(Looper.getMainLooper()).post(() -> { + arrayAdapter.add(castingPlayer); + }); + } + + @Override + public void onChanged(CastingPlayer castingPlayer) { + Log.i(TAG, "onChanged() Discovered changes to CastingPlayer with deviceId: " + castingPlayer.getDeviceId()); + // Update the CastingPlayer on the screen + new Handler(Looper.getMainLooper()).post(() -> { + final Optional playerInList = castingPlayerList.stream().filter(node -> castingPlayer.equals(node)).findFirst(); + if (playerInList.isPresent()) { + Log.d(TAG, "onChanged() Updating existing CastingPlayer entry " + playerInList.get().getDeviceId() + " in castingPlayerList list"); + arrayAdapter.remove(playerInList.get()); + } + arrayAdapter.add(castingPlayer); + }); + } + + @Override + public void onRemoved(CastingPlayer castingPlayer) { + Log.i(TAG, "onRemoved() Removed CastingPlayer with deviceId: " + castingPlayer.getDeviceId()); + // Remove CastingPlayer from the screen + new Handler(Looper.getMainLooper()).post(() -> { + final Optional playerInList = castingPlayerList.stream().filter(node -> castingPlayer.equals(node)).findFirst(); + if (playerInList.isPresent()) { + Log.d(TAG, "onRemoved() Removing existing CastingPlayer entry " + playerInList.get().getDeviceId() + " in castingPlayerList list"); + arrayAdapter.remove(playerInList.get()); + } + }); + } +}; +``` + +On iOS, implement a `func addDiscoveredCastingPlayers`, +`func removeDiscoveredCastingPlayers` and `func updateDiscoveredCastingPlayers` +which listen to notifications as Casting Players are added, removed, or updated. + +```swift +@objc +func didAddDiscoveredCastingPlayers(notification: Notification) +{ + Log.info("didAddDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didAddDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didAddDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + DispatchQueue.main.async + { + self.displayedCastingPlayers.append(castingPlayer) + } +} + +@objc +func didRemoveDiscoveredCastingPlayers(notification: Notification) +{ + Log.info("didRemoveDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didRemoveDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didRemoveDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + DispatchQueue.main.async + { + self.displayedCastingPlayers.removeAll(where: {$0 == castingPlayer}) + } +} + +@objc +func didUpdateDiscoveredCastingPlayers(notification: Notification) +{ + Log.info("didUpdateDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didUpdateDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didUpdateDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + if let index = displayedCastingPlayers.firstIndex(where: { castingPlayer.identifier() == $0.identifier() }) + { + DispatchQueue.main.async + { + self.displayedCastingPlayers[index] = castingPlayer + } + } +} +``` + Finally, register these listeners and start discovery. On Linux, register an instance of the `DiscoveryDelegateImpl` with @@ -462,9 +650,50 @@ chip::DeviceLayer::PlatformMgr().RunEventLoop(); ... ``` +On Android, add the implemented `castingPlayerChangeListener` as a listener to +the singleton instance of `MatterCastingPlayerDiscovery` to listen to changes in +the discovered Casting Players and call `startDiscovery`. + +```java +MatterError err = MatterCastingPlayerDiscovery.getInstance().addCastingPlayerChangeListener(castingPlayerChangeListener); +if (err.hasError()) { + Log.e(TAG, "startDiscovery() addCastingPlayerChangeListener() called, err Add: " + err); + return false; +} + +// Start discovery +Log.i(TAG, "startDiscovery() calling CastingPlayerDiscovery.startDiscovery()"); +err = MatterCastingPlayerDiscovery.getInstance().startDiscovery(DISCOVERY_TARGET_DEVICE_TYPE); +if (err.hasError()) { + Log.e(TAG, "Error in startDiscovery(): " + err); + return false; +} +``` + +On iOS, register the listeners by calling `addObserver` on the +`NotificationCenter` with the appropriate selector, and then call start on the +`sharedInstance` of `MTRCastingPlayerDiscovery`. + +```swift +func startDiscovery() { + NotificationCenter.default.addObserver(self, selector: #selector(self.didAddDiscoveredCastingPlayers), name: NSNotification.Name.didAddCastingPlayers, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didRemoveDiscoveredCastingPlayers), name: NSNotification.Name.didRemoveCastingPlayers, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didUpdateDiscoveredCastingPlayers), name: NSNotification.Name.didUpdateCastingPlayers, object: nil) + + MTRCastingPlayerDiscovery.sharedInstance().start() + ... +} +``` + +Note: You will need to connect with a Casting Player as described below to see +the list of Endpoints that they support. Refer to the +[Connection](#connect-to-a-casting-player) section for details on how to +discover available endpoints supported by a Casting Player. + ### Connect to a Casting Player -_{Complete Connection examples: [Linux](linux/simple-app-helper.cpp)}_ +_{Complete Connection examples: [Linux](linux/simple-app-helper.cpp) | +[iOS](darwin/TvCasting/TvCasting/MTRConnectionExampleViewModel.swift)}_ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as @@ -511,6 +740,37 @@ targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, ... ``` +On iOS, the Casting Client may call `verifyOrEstablishConnection` on the +`MTRCastingPlayer` object it wants to connect to and handle any `NSErrors` that +may happen in the process. + +```swift +// VendorId of the MTREndpoint on the MTRCastingPlayer that the MTRCastingApp desires to interact with after connection +let kDesiredEndpointVendorId: UInt16 = 65521; + +@Published var connectionSuccess: Bool?; + +@Published var connectionStatus: String?; + +func connect(selectedCastingPlayer: MTRCastingPlayer?) { + let desiredEndpointFilter: MTREndpointFilter = MTREndpointFilter() + desiredEndpointFilter.vendorId = kDesiredEndpointVendorId + selectedCastingPlayer?.verifyOrEstablishConnection(completionBlock: { err in + self.Log.error("MTRConnectionExampleViewModel connect() completed with \(err)") + if(err == nil) + { + self.connectionSuccess = true + self.connectionStatus = "Connected!" + } + else + { + self.connectionSuccess = false + self.connectionStatus = "Connection failed with \(String(describing: err))" + } + }, desiredEndpointFilter: desiredEndpointFilter) +} +``` + ### Select an Endpoint on the Casting Player _{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp)}_ @@ -705,3 +965,8 @@ void SubscribeToMediaPlaybackCurrentState(matter::casting::memory::Strong _Nonnull)dataSource; +- (NSError * _Nullable)initializeWithDataSource:(id _Nonnull)dataSource; + +/** + * @brief (async) Starts the Matter server that the MTRCastingApp runs on and registers all the necessary delegates + */ +- (void)startWithCompletionBlock:(void (^_Nonnull __strong)(NSError * _Nullable __strong))completion; + +/** + * @brief (async) Stops the Matter server that the MTRCastingApp runs on + */ +- (void)stopWithCompletionBlock:(void (^_Nonnull __strong)(NSError * _Nullable __strong))completion; + +/** + * @brief true, if MTRCastingApp is running. false otherwise + */ +- (bool)isRunning; /** - * @brief Starts the Matter server that the MTRCastingApp runs on and registers all the necessary delegates + * @brief Tears down all active subscriptions. */ -- (MatterError * _Nonnull)start; +- (NSError * _Nullable)ShutdownAllSubscriptions; /** - * @brief Stops the Matter server that the MTRCastingApp runs on + * @brief Clears app cache that contains the information about CastingPlayers previously connected to */ -- (MatterError * _Nonnull)stop; +- (NSError * _Nullable)ClearCache; @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingApp.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingApp.mm index 1ee0bc98bb2805..ba7e72b6730fac 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingApp.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingApp.mm @@ -20,6 +20,7 @@ #import "MTRCommissionableDataProvider.h" #import "MTRCommonCaseDeviceServerInitParamsProvider.h" #import "MTRDeviceAttestationCredentialsProvider.h" +#import "MTRErrorUtils.h" #import "MTRRotatingDeviceIdUniqueIdProvider.h" #import "core/Types.h" @@ -36,14 +37,17 @@ @interface MTRCastingApp () @property matter::casting::support::MTRDeviceAttestationCredentialsProvider * dacProvider; @property MTRCommonCaseDeviceServerInitParamsProvider * serverInitParamsProvider; -// queue used to serialize all work performed by the CastingServerBridge -@property (atomic) dispatch_queue_t chipWorkQueue; +// queue used when calling the client code on completion blocks from any MatterTvCastingBridge API +@property dispatch_queue_t _Nonnull clientQueue; + +// queue used to perform all work performed by the MatterTvCastingBridge +@property (atomic) dispatch_queue_t workQueue; @end @implementation MTRCastingApp -+ (MTRCastingApp * _Nullable)getSharedInstance ++ (MTRCastingApp *)getSharedInstance { static MTRCastingApp * instance = nil; static dispatch_once_t onceToken; @@ -53,18 +57,32 @@ + (MTRCastingApp * _Nullable)getSharedInstance return instance; } -- (MatterError * _Nonnull)initializeWithDataSource:(id _Nonnull)dataSource +- (dispatch_queue_t)getWorkQueue +{ + return _workQueue; +} + +- (dispatch_queue_t)getClientQueue +{ + return _clientQueue; +} + +- (NSError *)initializeWithDataSource:(id)dataSource { ChipLogProgress(AppServer, "MTRCastingApp.initializeWithDataSource called"); + // get the clientQueue + VerifyOrReturnValue([dataSource clientQueue] != nil, [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); + _clientQueue = [dataSource clientQueue]; + // Initialize cpp Providers - VerifyOrReturnValue(_uniqueIdProvider.Initialize(dataSource) == CHIP_NO_ERROR, MATTER_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(_uniqueIdProvider.Initialize(dataSource) == CHIP_NO_ERROR, [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); _commissionableDataProvider = new matter::casting::support::MTRCommissionableDataProvider(); - VerifyOrReturnValue(_commissionableDataProvider->Initialize(dataSource) == CHIP_NO_ERROR, MATTER_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(_commissionableDataProvider->Initialize(dataSource) == CHIP_NO_ERROR, [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); _dacProvider = new matter::casting::support::MTRDeviceAttestationCredentialsProvider(); - VerifyOrReturnValue(_dacProvider->Initialize(dataSource) == CHIP_NO_ERROR, MATTER_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(_dacProvider->Initialize(dataSource) == CHIP_NO_ERROR, [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); _serverInitParamsProvider = new MTRCommonCaseDeviceServerInitParamsProvider(); @@ -72,44 +90,82 @@ - (MatterError * _Nonnull)initializeWithDataSource:(id _Nonnull)dataSource VerifyOrReturnValue(_appParameters.Create(&_uniqueIdProvider, _commissionableDataProvider, _dacProvider, GetDefaultDACVerifier(chip::Credentials::GetTestAttestationTrustStore()), _serverInitParamsProvider) == CHIP_NO_ERROR, - MATTER_ERROR_INVALID_ARGUMENT); + [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT]); // Initialize cpp CastingApp VerifyOrReturnValue(matter::casting::core::CastingApp::GetInstance()->Initialize(_appParameters) == CHIP_NO_ERROR, - MATTER_ERROR_INCORRECT_STATE); + [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); // Get and store the CHIP Work queue - _chipWorkQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(); + _workQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(); - return MATTER_NO_ERROR; + return [MTRErrorUtils NSErrorFromChipError:CHIP_NO_ERROR]; } -- (MatterError * _Nonnull)start +- (void)startWithCompletionBlock:(void (^)(NSError *))completion { - ChipLogProgress(AppServer, "MTRCastingApp.start called"); - VerifyOrReturnValue(_chipWorkQueue != nil, MATTER_ERROR_INCORRECT_STATE); + ChipLogProgress(AppServer, "MTRCastingApp.startWithCompletionBlock called"); + VerifyOrReturn(_workQueue != nil && _clientQueue != nil, dispatch_async(self->_clientQueue, ^{ + completion([MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); + })); + + dispatch_async(_workQueue, ^{ + __block CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->Start(); + dispatch_async(self->_clientQueue, ^{ + completion([MTRErrorUtils NSErrorFromChipError:err]); + }); + }); + __block CHIP_ERROR err = chip::DeviceLayer::PlatformMgrImpl().StartEventLoopTask(); + VerifyOrReturn(err == CHIP_NO_ERROR, dispatch_async(self->_clientQueue, ^{ + completion([MTRErrorUtils NSErrorFromChipError:err]); + })); +} - __block CHIP_ERROR err = CHIP_NO_ERROR; +- (void)stopWithCompletionBlock:(void (^)(NSError *))completion +{ + ChipLogProgress(AppServer, "MTRCastingApp.stopWithCompletionBlock called"); + VerifyOrReturn(_workQueue != nil && _clientQueue != nil, dispatch_async(self->_clientQueue, ^{ + completion([MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); + })); - dispatch_sync(_chipWorkQueue, ^{ - err = matter::casting::core::CastingApp::GetInstance()->Start(); - }); + dispatch_async(_workQueue, ^{ + __block CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->Stop(); - return err == CHIP_NO_ERROR ? MATTER_NO_ERROR : MATTER_ERROR_INCORRECT_STATE; + dispatch_async(self->_clientQueue, ^{ + completion([MTRErrorUtils NSErrorFromChipError:err]); + }); + }); } -- (MatterError * _Nonnull)stop +- (bool)isRunning { - ChipLogProgress(AppServer, "MTRCastingApp.stop called"); - VerifyOrReturnValue(_chipWorkQueue != nil, MATTER_ERROR_INCORRECT_STATE); + VerifyOrReturnValue(_workQueue != nil && _clientQueue != nil, false); - __block CHIP_ERROR err = CHIP_NO_ERROR; + __block bool running = false; + dispatch_sync(_workQueue, ^{ + running = matter::casting::core::CastingApp::GetInstance()->isRunning(); + }); + return running; +} - dispatch_sync(_chipWorkQueue, ^{ - err = matter::casting::core::CastingApp::GetInstance()->Stop(); +- (NSError *)ShutdownAllSubscriptions +{ + ChipLogProgress(AppServer, "MTRCastingApp.ShutdownAllSubscriptions called"); + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_sync(_workQueue, ^{ + err = matter::casting::core::CastingApp::GetInstance()->ShutdownAllSubscriptions(); }); + return [MTRErrorUtils NSErrorFromChipError:err]; +} - return err == CHIP_NO_ERROR ? MATTER_NO_ERROR : MATTER_ERROR_INCORRECT_STATE; +- (NSError *)ClearCache +{ + ChipLogProgress(AppServer, "MTRCastingApp.ClearCache called"); + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_sync(_workQueue, ^{ + err = matter::casting::core::CastingApp::GetInstance()->ClearCache(); + }); + return [MTRErrorUtils NSErrorFromChipError:err]; } @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.h new file mode 100644 index 00000000000000..5827e652d6a60c --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.h @@ -0,0 +1,83 @@ +/** + * + * Copyright (c) 2020-2023 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 "MTREndpointFilter.h" + +#import + +#ifndef MTRCastingPlayer_h +#define MTRCastingPlayer_h + +@class MTREndpoint; + +/** + * @brief MTRCastingPlayer represents a Matter commissioner that is able to play media to a physical + * output or to a display screen which is part of the device. + */ +@interface MTRCastingPlayer : NSObject + ++ (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) + */ +- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion timeout:(long long)timeout desiredEndpointFilter:(MTREndpointFilter * _Nullable)desiredEndpointFilter; + +/** + * @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 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) + */ +- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion desiredEndpointFilter:(MTREndpointFilter * _Nullable)desiredEndpointFilter; + +/** + * @brief Sets the internal connection state of this CastingPlayer to "disconnected" + */ +- (void)disconnect; + +- (NSString * _Nonnull)identifier; +- (NSString * _Nonnull)deviceName; +- (uint16_t)vendorId; +- (uint16_t)productId; +- (uint32_t)deviceType; +- (NSArray * _Nonnull)ipAddresses; + +// TODO +// - (NSArray * _Nonnull)endpoints; + +- (nonnull instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (nonnull instancetype)new UNAVAILABLE_ATTRIBUTE; + +@end + +#endif /* MTRCastingPlayer_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.mm new file mode 100644 index 00000000000000..04286426dde6c1 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayer.mm @@ -0,0 +1,169 @@ +/** + * + * Copyright (c) 2023 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 "MTRCastingPlayer.h" + +#import "MTRCastingApp.h" +#import "MTRErrorUtils.h" + +#import "core/CastingPlayer.h" + +#import + +@interface MTRCastingPlayer () + +@property (nonatomic, readwrite) matter::casting::memory::Strong cppCastingPlayer; + +@end + +@implementation MTRCastingPlayer + +static const NSInteger kMinCommissioningWindowTimeoutSec = matter::casting::core::kCommissioningWindowTimeoutSec; + ++ (NSInteger)kMinCommissioningWindowTimeoutSec +{ + return kMinCommissioningWindowTimeoutSec; +} + +- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion desiredEndpointFilter:(MTREndpointFilter * _Nullable)desiredEndpointFilter +{ + [self verifyOrEstablishConnectionWithCompletionBlock:completion timeout:kMinCommissioningWindowTimeoutSec desiredEndpointFilter:desiredEndpointFilter]; +} + +- (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion timeout:(long long)timeout desiredEndpointFilter:(MTREndpointFilter * _Nullable)desiredEndpointFilter +{ + ChipLogProgress(AppServer, "MTRCastingPlayer.verifyOrEstablishConnectionWithCompletionBlock called"); + VerifyOrReturn([[MTRCastingApp getSharedInstance] isRunning], ChipLogError(AppServer, "MTRCastingApp NOT running")); + + dispatch_queue_t workQueue = [[MTRCastingApp getSharedInstance] getWorkQueue]; + dispatch_sync(workQueue, ^{ + __block matter::casting::core::EndpointFilter cppDesiredEndpointFilter; + if (desiredEndpointFilter != nil) { + cppDesiredEndpointFilter.vendorId = desiredEndpointFilter.vendorId; + cppDesiredEndpointFilter.productId = desiredEndpointFilter.productId; + } + + _cppCastingPlayer->VerifyOrEstablishConnection( + [completion](CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) { + dispatch_queue_t clientQueue = [[MTRCastingApp getSharedInstance] getClientQueue]; + dispatch_async(clientQueue, ^{ + completion(err == CHIP_NO_ERROR ? nil : [MTRErrorUtils NSErrorFromChipError:err]); + }); + }, timeout, cppDesiredEndpointFilter); + }); +} + +- (void)disconnect +{ + ChipLogProgress(AppServer, "MTRCastingPlayer.disconnect called"); + VerifyOrReturn([[MTRCastingApp getSharedInstance] isRunning], ChipLogError(AppServer, "MTRCastingApp NOT running")); + + dispatch_queue_t workQueue = [[MTRCastingApp getSharedInstance] getWorkQueue]; + dispatch_sync(workQueue, ^{ + _cppCastingPlayer->Disconnect(); + }); +} + +- (NSString * _Nonnull)description +{ + return [NSString stringWithFormat:@"%@ with Product ID: %d and Vendor ID: %d. Resolved IPAddr?: %@", + self.deviceName, self.productId, self.vendorId, self.ipAddresses != nil && self.ipAddresses.count > 0 ? @"YES" : @"NO"]; +} + +- (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Strong)cppCastingPlayer +{ + if (self = [super init]) { + _cppCastingPlayer = cppCastingPlayer; + } + return self; +} + +- (NSString * _Nonnull)identifier +{ + return [NSString stringWithCString:_cppCastingPlayer->GetId() encoding:NSUTF8StringEncoding]; +} + +- (NSString * _Nonnull)deviceName +{ + return [NSString stringWithCString:_cppCastingPlayer->GetDeviceName() encoding:NSUTF8StringEncoding]; +} + +- (uint16_t)productId +{ + return _cppCastingPlayer->GetProductId(); +} + +- (uint16_t)vendorId +{ + return _cppCastingPlayer->GetVendorId(); +} + +- (uint32_t)deviceType +{ + return _cppCastingPlayer->GetDeviceType(); +} + +- (NSArray * _Nonnull)ipAddresses +{ + NSMutableArray * ipAddresses = [NSMutableArray new]; + for (size_t i = 0; i < _cppCastingPlayer->GetNumIPs(); i++) { + char addrCString[chip::Inet::IPAddress::kMaxStringLength]; + _cppCastingPlayer->GetIPAddresses()[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); + ipAddresses[i] = [NSString stringWithCString:addrCString encoding:NSASCIIStringEncoding]; + } + return ipAddresses; +} + +// TODO convert to Obj-C endpoints and return +/*- (NSArray * _Nonnull)endpoints +{ + return [NSMutableArray new]; +}*/ + +- (BOOL)isEqualToMTRCastingPlayer:(MTRCastingPlayer * _Nullable)other +{ + return [self.identifier isEqualToString:other.identifier]; +} + +- (BOOL)isEqual:(id _Nullable)other +{ + if (other == nil) { + return NO; + } + + if (self == other) { + return YES; + } + + if (![other isKindOfClass:[MTRCastingPlayer class]]) { + return NO; + } + + return [self isEqualToMTRCastingPlayer:(MTRCastingPlayer *) other]; +} + +- (NSUInteger)hash +{ + const NSUInteger prime = 31; + NSUInteger result = 1; + + result = prime * result + [self.identifier hash]; + + return result; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.h new file mode 100644 index 00000000000000..3fd0ca84700157 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.h @@ -0,0 +1,85 @@ +/** + * + * Copyright (c) 2020-2023 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 "MTRCastingPlayer.h" +#import "MatterError.h" + +#ifndef MTRCastingPlayerDiscovery_h +#define MTRCastingPlayerDiscovery_h + +/** + * MTRCastingPlayerDiscovery sends notification with ADD_CASTING_PLAYER_NOTIFICATION_NAME + * through the NSNotificationCenter if a new MTRCastingPlayer is added to the network + */ +extern NSString * _Nonnull const ADD_CASTING_PLAYER_NOTIFICATION_NAME; + +/** + * MTRCastingPlayerDiscovery sends notification with REMOVE_CASTING_PLAYER_NOTIFICATION_NAME + * through the NSNotificationCenter if a MTRCastingPlayer is removed from the network + */ +extern NSString * _Nonnull const REMOVE_CASTING_PLAYER_NOTIFICATION_NAME; + +/** + * MTRCastingPlayerDiscovery sends notification with UPDATE_CASTING_PLAYER_NOTIFICATION_NAME + * through the NSNotificationCenter if a previously added MTRCastingPlayer is updated + */ +extern NSString * _Nonnull const UPDATE_CASTING_PLAYER_NOTIFICATION_NAME; + +/** + * MTRCastingPlayerDiscovery sends ADD / REMOVE / UPDATE notifications through the + * NSNotificationCenter with userInfo set to an NSDictionary that has CASTING_PLAYER_KEY as the + * key to a MTRCastingPlayer object as value. + */ +extern NSString * _Nonnull const CASTING_PLAYER_KEY; + +/** + * @brief MTRCastingPlayerDiscovery is a singleton utility class for discovering MTRCastingPlayers. + */ +@interface MTRCastingPlayerDiscovery : NSObject ++ (MTRCastingPlayerDiscovery * _Nonnull)sharedInstance; + +- (nonnull instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (nonnull instancetype)new UNAVAILABLE_ATTRIBUTE; + +@property (nonatomic, strong) NSArray * _Nonnull castingPlayers; + +/** + * @brief Starts the discovery for MTRCastingPlayers + * + * @return Returns nil if discovery for CastingPlayers started successfully, NSError * describing the error otherwise. + */ +- (NSError * _Nullable)start; + +/** + * @brief Starts the discovery for MTRCastingPlayers + * + * @param filterBydeviceType if passed as a non-zero value, MTRCastingPlayerDiscovery will only discover + * MTRCastingPlayers whose deviceType matches filterBydeviceType + * @return Returns nil if discovery for MTRCastingPlayers started successfully, NSError * describing the error otherwise. + */ +- (NSError * _Nullable)start:(const uint32_t)filterBydeviceType; + +/** + * @brief Stop the discovery for MTRCastingPlayers + * + * @return Returns nil if discovery for MTRCastingPlayers stopped successfully, NSError * describing the error otherwise. + */ +- (NSError * _Nullable)stop; + +@end + +#endif /* MTRCastingPlayerDiscovery_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.mm new file mode 100644 index 00000000000000..d840f46495717f --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCastingPlayerDiscovery.mm @@ -0,0 +1,144 @@ +/** + * + * Copyright (c) 2020-2023 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 "MTRCastingPlayerDiscovery.h" + +#import "MTRCastingApp.h" + +#import "MTRCastingPlayer.mm" +#import "MTRErrorUtils.h" + +#include "core/CastingPlayer.h" +#include "core/CastingPlayerDiscovery.h" +#include "core/Types.h" + +#include + +#import + +using namespace matter::casting; + +/** + * @brief Singleton that reacts to CastingPlayer discovery results + */ +class MTRDiscoveryDelegateImpl : public matter::casting::core::DiscoveryDelegate { +private: + MTRDiscoveryDelegateImpl() {}; + static MTRDiscoveryDelegateImpl * _discoveryDelegateImpl; + +public: + static MTRDiscoveryDelegateImpl * GetInstance(); + void HandleOnAdded(matter::casting::memory::Strong player) override; + void HandleOnUpdated(matter::casting::memory::Strong player) override; +}; + +@implementation MTRCastingPlayerDiscovery + +NSString * const ADD_CASTING_PLAYER_NOTIFICATION_NAME = @"didAddCastingPlayersNotification"; +NSString * const REMOVE_CASTING_PLAYER_NOTIFICATION_NAME = @"didRemoveCastingPlayersNotification"; +NSString * const UPDATE_CASTING_PLAYER_NOTIFICATION_NAME = @"didUpdateCastingPlayersNotification"; +NSString * const CASTING_PLAYER_KEY = @"castingPlayer"; + +- init +{ + self = [super init]; + if (self) { + dispatch_queue_t workQueue = [[MTRCastingApp getSharedInstance] getWorkQueue]; + dispatch_sync(workQueue, ^{ + core::CastingPlayerDiscovery::GetInstance()->SetDelegate(MTRDiscoveryDelegateImpl::GetInstance()); + }); + } + return self; +} + ++ (MTRCastingPlayerDiscovery *)sharedInstance +{ + static MTRCastingPlayerDiscovery * instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (NSError *)start +{ + return [self start:0]; // default to filterBydeviceType: 0 +} + +- (NSError *)start:(const uint32_t)filterBydeviceType +{ + ChipLogProgress(AppServer, "MTRCastingPlayerDiscovery.start called"); + VerifyOrReturnValue([[MTRCastingApp getSharedInstance] isRunning], [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); + + dispatch_queue_t workQueue = [[MTRCastingApp getSharedInstance] getWorkQueue]; + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_sync(workQueue, ^{ + err = core::CastingPlayerDiscovery::GetInstance()->StartDiscovery(filterBydeviceType); + }); + + return [MTRErrorUtils NSErrorFromChipError:err]; +} + +- (NSError *)stop +{ + ChipLogProgress(AppServer, "MTRCastingPlayerDiscovery.stop called"); + VerifyOrReturnValue([[MTRCastingApp getSharedInstance] isRunning], [MTRErrorUtils NSErrorFromChipError:CHIP_ERROR_INCORRECT_STATE]); + + dispatch_queue_t workQueue = [[MTRCastingApp getSharedInstance] getWorkQueue]; + __block CHIP_ERROR err = CHIP_NO_ERROR; + dispatch_sync(workQueue, ^{ + err = core::CastingPlayerDiscovery::GetInstance()->StopDiscovery(); + }); + + return [MTRErrorUtils NSErrorFromChipError:err]; +} + +@end + +MTRDiscoveryDelegateImpl * MTRDiscoveryDelegateImpl::_discoveryDelegateImpl = nullptr; + +MTRDiscoveryDelegateImpl * MTRDiscoveryDelegateImpl::GetInstance() +{ + if (_discoveryDelegateImpl == nullptr) { + _discoveryDelegateImpl = new MTRDiscoveryDelegateImpl(); + } + return _discoveryDelegateImpl; +} + +void MTRDiscoveryDelegateImpl::HandleOnAdded(matter::casting::memory::Strong castingPlayer) +{ + ChipLogProgress(AppServer, "MTRDiscoveryDelegateImpl::HandleOnAdded called with CastingPlayer ID: %s", castingPlayer->GetId()); + dispatch_queue_t clientQueue = [[MTRCastingApp getSharedInstance] getClientQueue]; + VerifyOrReturn(clientQueue != nil, ChipLogError(AppServer, "MTRDiscoveryDelegateImpl::HandleOnAdded ClientQueue was nil")); + VerifyOrReturn(castingPlayer != nil, ChipLogError(AppServer, "MTRDiscoveryDelegateImpl::HandleOnAdded Cpp CastingPlayer was nil")); + dispatch_async(clientQueue, ^{ + NSDictionary * dictionary = @ { CASTING_PLAYER_KEY : [[MTRCastingPlayer alloc] initWithCppCastingPlayer:castingPlayer] }; + [[NSNotificationCenter defaultCenter] postNotificationName:ADD_CASTING_PLAYER_NOTIFICATION_NAME object:nil userInfo:dictionary]; + }); +} + +void MTRDiscoveryDelegateImpl::HandleOnUpdated(matter::casting::memory::Strong castingPlayer) +{ + ChipLogProgress(AppServer, "MTRDiscoveryDelegateImpl::HandleOnUpdated called with CastingPlayer ID: %s", castingPlayer->GetId()); + dispatch_queue_t clientQueue = [[MTRCastingApp getSharedInstance] getClientQueue]; + VerifyOrReturn(clientQueue != nil); + dispatch_async(clientQueue, ^{ + NSDictionary * dictionary = @ { CASTING_PLAYER_KEY : [[MTRCastingPlayer alloc] initWithCppCastingPlayer:castingPlayer] }; + [[NSNotificationCenter defaultCenter] postNotificationName:UPDATE_CASTING_PLAYER_NOTIFICATION_NAME object:nil userInfo:dictionary]; + }); +} diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.h new file mode 100644 index 00000000000000..e624d04ffaa848 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.h @@ -0,0 +1,47 @@ +/** + * + * Copyright (c) 2023 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 "MatterError.h" + +#import + +#ifndef MTRCryptoUtils_h +#define MTRCryptoUtils_h + +@interface MTRCryptoUtils : NSObject + +/** + * @brief Convert an ASN.1 DER signature (per X9.62) as used by TLS libraries to SEC1 raw format + * + * Errors are: + * - CHIP_ERROR_INVALID_ARGUMENT on any argument being invalid (e.g. nullptr), wrong sizes, + * wrong or unsupported format, + * - CHIP_ERROR_BUFFER_TOO_SMALL on running out of space at runtime. + * - CHIP_ERROR_INTERNAL on any unexpected processing error. + * + * @param[in] feLengthBytes Field Element length in bytes (e.g. 32 for P256 curve) + * @param[in] asn1Signature ASN.1 DER signature input + * @param[out] outRawSignature Raw signature of concatenated format output buffer. Size must be at + * least >= `2 * fe_length_bytes`. On success, the outRawSignature buffer will be re-assigned + * to have the correct size (2 * feLengthBytes). + * @return Returns an MatterError on error, MATTER_NO_ERROR otherwise + */ ++ (MatterError * _Nonnull)ecdsaAsn1SignatureToRawWithFeLengthBytes:(NSUInteger)feLengthBytes asn1Signature:(CFDataRef _Nonnull)asn1Signature outRawSignature:(NSData * _Nonnull * _Nonnull)outRawSignature; + +@end + +#endif /* MTRCryptoUtils_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.mm new file mode 100644 index 00000000000000..562388d7d9dc73 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRCryptoUtils.mm @@ -0,0 +1,55 @@ +/** + * + * Copyright (c) 2020-2022 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 "MTRCryptoUtils.h" +#import "MTRErrorUtils.h" + +#include +#include +#include +#include + +#include + +@implementation MTRCryptoUtils + ++ (MatterError *)ecdsaAsn1SignatureToRawWithFeLengthBytes:(NSUInteger)feLengthBytes asn1Signature:(CFDataRef)asn1Signature outRawSignature:(NSData **)outRawSignature +{ + // convert asn1Signature from CFDataRef to MutableByteSpan (asn1SignatureByteSpan) + uint8_t asn1SignatureBytes[256]; + chip::MutableByteSpan asn1SignatureByteSpan = chip::MutableByteSpan(asn1SignatureBytes, sizeof(asn1SignatureBytes)); + size_t signatureLen = CFDataGetLength(asn1Signature); + CFDataGetBytes(asn1Signature, CFRangeMake(0, signatureLen), asn1SignatureByteSpan.data()); + asn1SignatureByteSpan.reduce_size(signatureLen); + + // get a rawSignatureMutableByteSpan to pass to chip::Crypto::EcdsaAsn1SignatureToRaw + uint8_t * rawSignatureBytes = new uint8_t[(*outRawSignature).length]; + chip::MutableByteSpan rawSignatureMutableByteSpan = chip::MutableByteSpan(rawSignatureBytes, (*outRawSignature).length); + + // convert ASN.1 DER signature to SEC1 raw format + CHIP_ERROR err = chip::Crypto::EcdsaAsn1SignatureToRaw(feLengthBytes, chip::ByteSpan(asn1SignatureByteSpan.data(), asn1SignatureByteSpan.size()), rawSignatureMutableByteSpan); + if (err != CHIP_NO_ERROR) { + ChipLogError(AppServer, "chip::Crypto::EcdsaAsn1SignatureToRaw() failed"); + return [MTRErrorUtils MatterErrorFromChipError:err]; + } + + // copy from rawSignatureMutableByteSpan into *outRawSignature + *outRawSignature = [NSData dataWithBytes:rawSignatureMutableByteSpan.data() length:rawSignatureMutableByteSpan.size()]; + return MATTER_NO_ERROR; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDataSource.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDataSource.h index a350673b70f6c4..86156efb7462e0 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDataSource.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDataSource.h @@ -17,19 +17,42 @@ #import "MTRCommissionableData.h" #import "MTRDeviceAttestationCredentials.h" +#import "MatterError.h" #ifndef MTRDataSource_h #define MTRDataSource_h @protocol MTRDataSource +/** + * @brief Queue used when calling the client code on completion blocks from any MatterTvCastingBridge API + */ - (dispatch_queue_t _Nonnull)clientQueue; +/** + * @brief Provide UniqueId used to generate the RotatingDeviceId advertised during commissioning by the MTRCastingApp + * Must be at least 16 bytes (i.e. ConfigurationManager::kMinRotatingDeviceIDUniqueIDLength) + */ - (NSData * _Nonnull)castingAppDidReceiveRequestForRotatingDeviceIdUniqueId:(id _Nonnull)sender; + +/** + * @brief Provides MTRCommissionableData (such as setupPasscode, discriminator, etc) used to get the MTRCastingApp commissioned + */ - (MTRCommissionableData * _Nonnull)castingAppDidReceiveRequestForCommissionableData:(id _Nonnull)sender; + +/** + * @brief Provides MTRDeviceAttestationCredentials of the MTRCastingApp used during commissioning + */ - (MTRDeviceAttestationCredentials * _Nonnull)castingAppDidReceiveRequestForDeviceAttestationCredentials:(id _Nonnull)sender; -- (NSData * _Nonnull)castingApp:(id _Nonnull)sender didReceiveRequestToSignCertificateRequest:(NSData * _Nonnull)csrData; +/** + * @brief Request to signs a message using the device attestation private key + * + * @param csrData - The message to sign using the attestation private key. + * @param outRawSignature [in, out] - Buffer to receive the signature in raw format. + * @returns MATTER_NO_ERROR on success. Otherwise, a MATTER_ERROR with a code corresponding to the underlying failure + */ +- (MatterError * _Nonnull)castingApp:(id _Nonnull)sender didReceiveRequestToSignCertificateRequest:(NSData * _Nonnull)csrData outRawSignature:(NSData * _Nonnull * _Nonnull)outRawSignature; @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDeviceAttestationCredentialsProvider.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDeviceAttestationCredentialsProvider.mm index 8a2bcb0439c217..00c4d4102baeac 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDeviceAttestationCredentialsProvider.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRDeviceAttestationCredentialsProvider.mm @@ -34,6 +34,7 @@ VerifyOrReturnError(dataSource != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(mDataSource == nullptr, CHIP_ERROR_INCORRECT_STATE); + mDataSource = dataSource; mDac = [mDataSource castingAppDidReceiveRequestForDeviceAttestationCredentials:@"MTRDeviceAttestationCredentialsProvider.Initialize()"]; @@ -110,13 +111,17 @@ { VerifyOrReturnError(mDataSource != nullptr, CHIP_ERROR_INCORRECT_STATE); - __block NSData * signedData = nil; NSData * csrData = [NSData dataWithBytes:messageToSign.data() length:messageToSign.size()]; + __block NSData * signedData = [NSData dataWithBytes:outSignatureBuffer.data() length:outSignatureBuffer.size()]; + __block MatterError * err = nil; dispatch_sync(mDataSource.clientQueue, ^{ - signedData = [mDataSource castingApp:@"MTRDeviceAttestationCredentialsProvider.SignWithDeviceAttestationKey()" - didReceiveRequestToSignCertificateRequest:csrData]; + err = [mDataSource castingApp:@"MTRDeviceAttestationCredentialsProvider.SignWithDeviceAttestationKey()" + didReceiveRequestToSignCertificateRequest:csrData + outRawSignature:&signedData]; }); + VerifyOrReturnValue(MATTER_NO_ERROR == err, CHIP_ERROR(chip::ChipError::SdkPart::kCore, err.code), ChipLogError(AppServer, "castingApp::SignCertificateRequest failed")); + if (signedData != nil && outSignatureBuffer.size() >= signedData.length) { memcpy(outSignatureBuffer.data(), signedData.bytes, signedData.length); outSignatureBuffer.reduce_size(signedData.length); diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.h new file mode 100644 index 00000000000000..821a7ec834f66f --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.h @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2020-2023 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 MTREndpointFilter_h +#define MTREndpointFilter_h + +/** + * @brief Describes a MTREndpoint that the client wants to connect to + */ +@interface MTREndpointFilter : NSObject +// value of 0 means unspecified +@property (nonatomic) uint16_t vendorId; +@property (nonatomic) uint16_t productId; +// std::vector requiredDeviceTypes; + +@end +#endif /* MTREndpointFilter_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.m b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.m new file mode 100644 index 00000000000000..b060a5deeba3cf --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTREndpointFilter.m @@ -0,0 +1,22 @@ +/** + * + * Copyright (c) 2023 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 "MTREndpointFilter.h" + +@implementation MTREndpointFilter + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.h new file mode 100644 index 00000000000000..18a06478425004 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.h @@ -0,0 +1,39 @@ +/** + * + * Copyright (c) 2023 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 "MatterError.h" +#include + +#import + +#ifndef MTRErrorUtils_h +#define MTRErrorUtils_h + +/** + * @brief - Conversion utilities to/from CHIP_ERROR (C++) / MatterError (Objective C) / NSError + */ +@interface MTRErrorUtils : NSObject + ++ (MatterError * _Nonnull)MatterErrorFromChipError:(CHIP_ERROR)chipError; + ++ (NSError * _Nonnull)NSErrorFromChipError:(CHIP_ERROR)chipError; + ++ (NSError * _Nonnull)NSErrorFromMatterError:(MatterError * _Nonnull)matterError; + +@end + +#endif /* MTRErrorUtils_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.mm new file mode 100644 index 00000000000000..1f6b896e681ca0 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MTRErrorUtils.mm @@ -0,0 +1,41 @@ +/** + * + * Copyright (c) 2020-2022 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 + +#import "MTRErrorUtils.h" + +#include + +@implementation MTRErrorUtils + ++ (MatterError * _Nonnull)MatterErrorFromChipError:(CHIP_ERROR)chipError +{ + return [[MatterError alloc] initWithCode:chipError.AsInteger() message:[NSString stringWithUTF8String:chipError.AsString()]]; +} + ++ (NSError * _Nonnull)NSErrorFromChipError:(CHIP_ERROR)chipError +{ + return chipError == CHIP_NO_ERROR ? nil : [NSError errorWithDomain:@"com.matter.casting" code:chipError.AsInteger() userInfo:@{ NSUnderlyingErrorKey : [NSString stringWithUTF8String:chipError.AsString()] }]; +} + ++ (NSError * _Nonnull)NSErrorFromMatterError:(MatterError * _Nonnull)matterError +{ + return matterError == MATTER_NO_ERROR ? nil : [NSError errorWithDomain:@"com.matter.casting" code:matterError.code userInfo:@{ NSUnderlyingErrorKey : matterError.message }]; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h index 9d12b964ce1c97..b83ce9cccf99be 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h @@ -27,6 +27,10 @@ FOUNDATION_EXPORT const unsigned char MatterTvCastingBridgeVersionString[]; // Add simplified casting API headers here #import "MTRCastingApp.h" +#import "MTRCastingPlayer.h" +#import "MTRCastingPlayerDiscovery.h" #import "MTRCommissionableData.h" +#import "MTRCryptoUtils.h" #import "MTRDataSource.h" #import "MTRDeviceAttestationCredentials.h" +#import "MTREndpointFilter.h" 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 567a0e205d1fc6..c208d6b24901a2 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj @@ -12,6 +12,10 @@ 3C81C75528F8C7B6001CB9D1 /* StartFromCacheViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C81C75428F8C7B6001CB9D1 /* StartFromCacheViewModel.swift */; }; 3C81C75728F8E418001CB9D1 /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C81C75628F8E418001CB9D1 /* ConnectionView.swift */; }; 3C81C75928F8E42D001CB9D1 /* ConnectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C81C75828F8E42D001CB9D1 /* ConnectionViewModel.swift */; }; + 3C94377D2B364D380096E5F4 /* MTRDiscoveryExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C94377C2B364D380096E5F4 /* MTRDiscoveryExampleViewModel.swift */; }; + 3C94377F2B364D510096E5F4 /* MTRConnectionExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C94377E2B364D510096E5F4 /* MTRConnectionExampleViewModel.swift */; }; + 3C94378E2B3B3CB00096E5F4 /* MTRDiscoveryExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C94378D2B3B3CB00096E5F4 /* MTRDiscoveryExampleView.swift */; }; + 3C9437902B3B3FF90096E5F4 /* MTRConnectionExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C94378F2B3B3FF90096E5F4 /* MTRConnectionExampleView.swift */; }; 3CA1CA7A28E281080023ED44 /* ClusterSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA1CA7928E281080023ED44 /* ClusterSelectorView.swift */; }; 3CA1CA7C28E282150023ED44 /* MediaPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA1CA7B28E282150023ED44 /* MediaPlaybackView.swift */; }; 3CA1CA7E28E284950023ED44 /* MediaPlaybackViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA1CA7D28E284950023ED44 /* MediaPlaybackViewModel.swift */; }; @@ -57,6 +61,10 @@ 3C81C75428F8C7B6001CB9D1 /* StartFromCacheViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartFromCacheViewModel.swift; sourceTree = ""; }; 3C81C75628F8E418001CB9D1 /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = ""; }; 3C81C75828F8E42D001CB9D1 /* ConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewModel.swift; sourceTree = ""; }; + 3C94377C2B364D380096E5F4 /* MTRDiscoveryExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTRDiscoveryExampleViewModel.swift; sourceTree = ""; }; + 3C94377E2B364D510096E5F4 /* MTRConnectionExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTRConnectionExampleViewModel.swift; sourceTree = ""; }; + 3C94378D2B3B3CB00096E5F4 /* MTRDiscoveryExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTRDiscoveryExampleView.swift; sourceTree = ""; }; + 3C94378F2B3B3FF90096E5F4 /* MTRConnectionExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTRConnectionExampleView.swift; sourceTree = ""; }; 3CA19434285BA780004768D5 /* ContentLauncherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLauncherView.swift; sourceTree = ""; }; 3CA19436285BA877004768D5 /* ContentLauncherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLauncherViewModel.swift; sourceTree = ""; }; 3CA1CA7928E281080023ED44 /* ClusterSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClusterSelectorView.swift; sourceTree = ""; }; @@ -119,6 +127,10 @@ 3C75075E284C1DF800D7DB3A /* TvCasting.entitlements */, 3CC0E8F92841DD3400EC6A18 /* TvCastingApp.swift */, 3C6920492AA1368F00D0F613 /* MTRInitializationExample.swift */, + 3C94378D2B3B3CB00096E5F4 /* MTRDiscoveryExampleView.swift */, + 3C94377C2B364D380096E5F4 /* MTRDiscoveryExampleViewModel.swift */, + 3C94378F2B3B3FF90096E5F4 /* MTRConnectionExampleView.swift */, + 3C94377E2B364D510096E5F4 /* MTRConnectionExampleViewModel.swift */, EAF14298296D561900E17793 /* CertTestView.swift */, EAF1429A296D57DF00E17793 /* CertTestViewModel.swift */, 3CC0E8FB2841DD3400EC6A18 /* ContentView.swift */, @@ -177,6 +189,8 @@ dependencies = ( ); name = TvCasting; + packageProductDependencies = ( + ); productName = TvCasting; productReference = 3CC0E8F62841DD3400EC6A18 /* TvCasting.app */; productType = "com.apple.product-type.application"; @@ -233,13 +247,16 @@ buildActionMask = 2147483647; files = ( 3C81C75328F8C79E001CB9D1 /* StartFromCacheView.swift in Sources */, + 3C94377F2B364D510096E5F4 /* MTRConnectionExampleViewModel.swift in Sources */, 3C81C75528F8C7B6001CB9D1 /* StartFromCacheViewModel.swift in Sources */, 3CCB8745286A5D0F00771BAD /* CommissionerDiscoveryView.swift in Sources */, 3CCB8746286A5D0F00771BAD /* CommissionerDiscoveryViewModel.swift in Sources */, 3C81C75928F8E42D001CB9D1 /* ConnectionViewModel.swift in Sources */, 3CA1CA7A28E281080023ED44 /* ClusterSelectorView.swift in Sources */, + 3C94378E2B3B3CB00096E5F4 /* MTRDiscoveryExampleView.swift in Sources */, EAF14299296D561900E17793 /* CertTestView.swift in Sources */, 3C69204A2AA1368F00D0F613 /* MTRInitializationExample.swift in Sources */, + 3C94377D2B364D380096E5F4 /* MTRDiscoveryExampleViewModel.swift in Sources */, 3CCB8747286A5D0F00771BAD /* CommissioningView.swift in Sources */, 3CCB8748286A5D0F00771BAD /* CommissioningViewModel.swift in Sources */, 3CAC955B29BA948700BEA5C3 /* ExampleDAC.swift in Sources */, @@ -251,6 +268,7 @@ 3CC0E8FC2841DD3400EC6A18 /* ContentView.swift in Sources */, 3CA1CA7C28E282150023ED44 /* MediaPlaybackView.swift in Sources */, 3CC0E8FA2841DD3400EC6A18 /* TvCastingApp.swift in Sources */, + 3C9437902B3B3FF90096E5F4 /* MTRConnectionExampleView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/ContentView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/ContentView.swift index 08f9dd860d07b5..4f66f03db398bd 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/ContentView.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/ContentView.swift @@ -20,7 +20,11 @@ import SwiftUI struct ContentView: View { var body: some View { NavigationView { - if ProcessInfo.processInfo.environment["CHIP_CASTING_SIMPLIFIED"] == "0" + if ProcessInfo.processInfo.environment["CHIP_CASTING_SIMPLIFIED"] == "1" + { + MTRDiscoveryExampleView() + } + else { StartFromCacheView() } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleView.swift new file mode 100644 index 00000000000000..f56e7efef64481 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleView.swift @@ -0,0 +1,69 @@ +/** + * + * Copyright (c) 2023 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 SwiftUI + +struct MTRConnectionExampleView: View { + var selectedCastingPlayer: MTRCastingPlayer? + + @StateObject var viewModel = MTRConnectionExampleViewModel(); + + init(_selectedCastingPlayer: MTRCastingPlayer?) { + self.selectedCastingPlayer = _selectedCastingPlayer + } + + var body: some View { + VStack(alignment: .leading) { + Text("Verifying or Establishing Connection to Casting Player: \(self.selectedCastingPlayer!.deviceName())").padding() + if let connectionSuccess = viewModel.connectionSuccess + { + if let connectionStatus = viewModel.connectionStatus + { + Text(connectionStatus).padding() + } + + if(connectionSuccess) + { + /* TODO add this back in + NavigationLink( + destination: ClusterSelectorView(), + label: { + Text("Next") + .frame(width: 100, height: 30, alignment: .center) + .border(Color.black, width: 1) + } + ).background(Color.blue) + .foregroundColor(Color.white) + .frame(maxHeight: .infinity, alignment: .bottom) + .padding()*/ + } + } + } + .navigationTitle("Connecting...") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + .onAppear(perform: { + viewModel.connect(selectedCastingPlayer: self.selectedCastingPlayer) + }) + } +} + +struct MTRConnectionExampleView_Previews: PreviewProvider { + static var previews: some View { + MTRConnectionExampleView(_selectedCastingPlayer: nil) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleViewModel.swift new file mode 100644 index 00000000000000..da237e47cc4d6e --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRConnectionExampleViewModel.swift @@ -0,0 +1,50 @@ +/** + * + * Copyright (c) 2020-2023 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 MTRConnectionExampleViewModel: ObservableObject { + let Log = Logger(subsystem: "com.matter.casting", + category: "MTRConnectionExampleViewModel") + + // VendorId of the MTREndpoint on the MTRCastingPlayer that the MTRCastingApp desires to interact with after connection + let kDesiredEndpointVendorId: UInt16 = 65521; + + @Published var connectionSuccess: Bool?; + + @Published var connectionStatus: String?; + + func connect(selectedCastingPlayer: MTRCastingPlayer?) { + let desiredEndpointFilter: MTREndpointFilter = MTREndpointFilter() + desiredEndpointFilter.vendorId = kDesiredEndpointVendorId + selectedCastingPlayer?.verifyOrEstablishConnection(completionBlock: { err in + self.Log.error("MTRConnectionExampleViewModel connect() completed with \(err)") + if(err == nil) + { + self.connectionSuccess = true + self.connectionStatus = "Connected!" + } + else + { + self.connectionSuccess = false + self.connectionStatus = "Connection failed with \(String(describing: err))" + } + }, desiredEndpointFilter: desiredEndpointFilter) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleView.swift new file mode 100644 index 00000000000000..79b8c911e1a27c --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleView.swift @@ -0,0 +1,85 @@ +/** + * + * Copyright (c) 2023 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 SwiftUI + +extension MTRCastingPlayer : Identifiable { + public var id: String { + identifier() + } +} + +struct MTRDiscoveryExampleView: View { + @StateObject var viewModel = MTRDiscoveryExampleViewModel() + + var body: some View { + VStack(alignment: .leading) { + Button("Start Discovery", action: viewModel.startDiscovery) + .frame(width: 350, height: 30, alignment: .center) + .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) + .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) + .border(Color.black, width: 1) + .background(Color.blue) + .foregroundColor(Color.white) + .padding(1) + + if(viewModel.discoveryHasError) + { + Text("Discovery request failed. Check logs for details") + } + else if(!viewModel.displayedCastingPlayers.isEmpty) + { + Text("Select a Casting player...") + ForEach(viewModel.displayedCastingPlayers) { castingPlayer in + NavigationLink( + destination: { + MTRConnectionExampleView(_selectedCastingPlayer: castingPlayer) + }, + label: { + Text(castingPlayer.description) + } + ) + .frame(width: 350, height: 50, alignment: .center) + .border(Color.black, width: 1) + .background(Color.blue) + .foregroundColor(Color.white) + .padding(1) + } + } + } + .navigationTitle("Casting Player Discovery") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + } +} + +struct MTRDiscoveryExampleView_Previews: PreviewProvider { + static var previews: some View { + MTRDiscoveryExampleView() + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleViewModel.swift new file mode 100644 index 00000000000000..16f86457d44e7b --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRDiscoveryExampleViewModel.swift @@ -0,0 +1,126 @@ +/** + * + * Copyright (c) 2020-2023 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 MTRDiscoveryExampleViewModel: ObservableObject { + let Log = Logger(subsystem: "com.matter.casting", + category: "MTRDiscoveryExampleViewModel") + let kTargetPlayerDeviceType: UInt64 = 35 + + @Published var displayedCastingPlayers: [MTRCastingPlayer] = [] + + @Published var discoveryHasError: Bool = false; + + func startDiscovery() { + Log.info("startDiscovery() called") + clearResults() + + // add observers + NotificationCenter.default.addObserver(self, selector: #selector(self.didAddDiscoveredCastingPlayers), name: NSNotification.Name(ADD_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didRemoveDiscoveredCastingPlayers), name: NSNotification.Name(REMOVE_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didUpdateDiscoveredCastingPlayers), name: NSNotification.Name(UPDATE_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + + if let err:Error = MTRCastingPlayerDiscovery.sharedInstance().start(UInt32(kTargetPlayerDeviceType)) + { + Log.error("MTRCastingPlayerDiscovery.start failed with \(err)") + self.discoveryHasError = true + } + self.discoveryHasError = false + } + + func stopDiscovery() { + Log.info("stopDiscovery() called") + if let err:Error = MTRCastingPlayerDiscovery.sharedInstance().stop() + { + Log.error("MTRCastingPlayerDiscovery.stop failed with \(err)") + self.discoveryHasError = true + } + else + { + // remove observers + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(ADD_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(REMOVE_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(UPDATE_CASTING_PLAYER_NOTIFICATION_NAME), object: nil) + } + self.discoveryHasError = false + } + + func clearResults() { + Log.info("clearResults() called") + DispatchQueue.main.async + { + self.displayedCastingPlayers.removeAll() + } + } + + @objc + func didAddDiscoveredCastingPlayers(notification: Notification) + { + Log.info("didAddDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didAddDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didAddDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + + DispatchQueue.main.async + { + self.displayedCastingPlayers.append(castingPlayer) + } + } + + @objc + func didRemoveDiscoveredCastingPlayers(notification: Notification) + { + Log.info("didRemoveDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didRemoveDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didRemoveDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + DispatchQueue.main.async + { + self.displayedCastingPlayers.removeAll(where: {$0 == castingPlayer}) + } + } + + @objc + func didUpdateDiscoveredCastingPlayers(notification: Notification) + { + Log.info("didUpdateDiscoveredCastingPlayers() called") + guard let userInfo = notification.userInfo, + let castingPlayer = userInfo["castingPlayer"] as? MTRCastingPlayer else { + self.Log.error("didUpdateDiscoveredCastingPlayers called with no MTRCastingPlayer") + return + } + + self.Log.info("didUpdateDiscoveredCastingPlayers notified of a MTRCastingPlayer with ID: \(castingPlayer.identifier())") + if let index = displayedCastingPlayers.firstIndex(where: { castingPlayer.identifier() == $0.identifier() }) + { + DispatchQueue.main.async + { + self.displayedCastingPlayers[index] = castingPlayer + } + } + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRInitializationExample.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRInitializationExample.swift index abbc5c9457bb43..534094a8cec060 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRInitializationExample.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MTRInitializationExample.swift @@ -16,10 +16,14 @@ */ import Foundation +import Security import os.log class MTRAppParametersDataSource : NSObject, MTRDataSource -{ +{ + let Log = Logger(subsystem: "com.matter.casting", + category: "MTRAppParametersDataSource") + func clientQueue() -> DispatchQueue { return DispatchQueue.main; } @@ -57,21 +61,38 @@ class MTRAppParametersDataSource : NSObject, MTRDataSource productAttestationIntermediateCert: KPAI_FFF1_8000_Cert_Array) } - func castingApp(_ sender: Any, didReceiveRequestToSignCertificateRequest csrData: Data) -> Data { - var privateKey = Data() - privateKey.append(kDevelopmentDAC_PublicKey_FFF1_8001); - privateKey.append(kDevelopmentDAC_PrivateKey_FFF1_8001); - - let privateKeyRef: SecKey = SecKeyCreateWithData(privateKey as NSData, + func castingApp(_ sender: Any, didReceiveRequestToSignCertificateRequest csrData: Data, outRawSignature: AutoreleasingUnsafeMutablePointer) -> MatterError { + Log.info("castingApp didReceiveRequestToSignCertificateRequest") + + // get the private SecKey + var privateKeyData = Data() + privateKeyData.append(kDevelopmentDAC_PublicKey_FFF1_8001); + privateKeyData.append(kDevelopmentDAC_PrivateKey_FFF1_8001); + let privateSecKey: SecKey = SecKeyCreateWithData(privateKeyData as NSData, [ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits: 256 ] as NSDictionary, nil)! - let _:Unmanaged = Unmanaged.passRetained(privateKeyRef); + // sign csrData to get asn1SignatureData + var error: Unmanaged? + let asn1SignatureData: CFData? = SecKeyCreateSignature(privateSecKey, .ecdsaSignatureMessageX962SHA256, csrData as CFData, &error) + if(error != nil) + { + Log.error("Failed to sign message. Error: \(String(describing: error))") + return MATTER_ERROR_INVALID_ARGUMENT + } + else if (asn1SignatureData == nil) + { + Log.error("Failed to sign message. asn1SignatureData is nil") + return MATTER_ERROR_INVALID_ARGUMENT + } - return Data() // TODO: use SecKey above to sign csrData and return resulting value + // convert ASN.1 DER signature to SEC1 raw format + return MTRCryptoUtils.ecdsaAsn1SignatureToRaw(withFeLengthBytes: 32, + asn1Signature: asn1SignatureData!, + outRawSignature: &outRawSignature.pointee) } } @@ -79,14 +100,14 @@ class MTRInitializationExample { let Log = Logger(subsystem: "com.matter.casting", category: "MTRInitializationExample") - func initialize() -> MatterError { + func initialize() -> Error? { if let castingApp = MTRCastingApp.getSharedInstance() { return castingApp.initialize(with: MTRAppParametersDataSource()) } else { - return MATTER_ERROR_INCORRECT_STATE + return NSError(domain: "com.matter.casting", code: Int(MATTER_ERROR_INCORRECT_STATE.code)) } } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift index 2616ec0714f537..1fc28dce6ddf8c 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/TvCastingApp.swift @@ -33,10 +33,11 @@ struct TvCastingApp: App { { self.Log.info("CHIP_CASTING_SIMPLIFIED = 1") - let err: MatterError = MTRInitializationExample().initialize() - if !MATTER_NO_ERROR.isEqual(err) + let err: Error? = MTRInitializationExample().initialize() + if err != nil { - self.Log.error("CastingApp initialization failed \(err)") + self.Log.error("MTRCastingApp initialization failed \(err)") + return } } else @@ -73,11 +74,12 @@ struct TvCastingApp: App { { if let castingApp = MTRCastingApp.getSharedInstance() { - let err: MatterError = castingApp.stop() - if !MATTER_NO_ERROR.isEqual(err) - { - self.Log.error("CastingApp stop failed \(err)") - } + castingApp.stop(completionBlock: { (err : Error?) -> () in + if err != nil + { + self.Log.error("MTRCastingApp stop failed \(err)") + } + }) } } else if let castingServerBridge = CastingServerBridge.getSharedInstance() @@ -91,11 +93,12 @@ struct TvCastingApp: App { { if let castingApp = MTRCastingApp.getSharedInstance() { - let err: MatterError = castingApp.start() - if !MATTER_NO_ERROR.isEqual(err) - { - self.Log.error("CastingApp start failed \(err)") - } + castingApp.start(completionBlock: { (err : Error?) -> () in + if err != nil + { + self.Log.error("MTRCastingApp start failed \(err)") + } + }) } } else diff --git a/examples/tv-casting-app/linux/simple-app.cpp b/examples/tv-casting-app/linux/simple-app.cpp index d0a23769ea600b..26d35b443c0e67 100644 --- a/examples/tv-casting-app/linux/simple-app.cpp +++ b/examples/tv-casting-app/linux/simple-app.cpp @@ -165,12 +165,10 @@ int main(int argc, char * argv[]) #endif CastingPlayerDiscovery::GetInstance()->SetDelegate(DiscoveryDelegateImpl::GetInstance()); - VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, - ChipLogError(AppServer, "CastingPlayerDiscovery::SetDelegate failed %" CHIP_ERROR_FORMAT, err.Format())); // Discover CastingPlayers err = CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); - VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, + VerifyOrReturnValue(err == CHIP_NO_ERROR, -1, ChipLogError(AppServer, "CastingPlayerDiscovery::StartDiscovery failed %" CHIP_ERROR_FORMAT, err.Format())); chip::DeviceLayer::PlatformMgr().RunEventLoop(); diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index ae85e14f1030b2..b9f458bb00b367 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -103,6 +103,7 @@ chip_data_model("tv-casting-common") { "core/CastingPlayerDiscovery.cpp", "core/CastingPlayerDiscovery.h", "core/Command.h", + "core/Endpoint.cpp", "core/Endpoint.h", "core/Types.h", "support/AppParameters.h", diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp index 0493c98794295e..f7865910832ec9 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp @@ -18,6 +18,7 @@ #include "CastingApp.h" +#include "support/CastingStore.h" #include "support/ChipDeviceEventHandler.h" #include @@ -96,6 +97,23 @@ CHIP_ERROR CastingApp::Start() // perform post server startup registrations ReturnErrorOnFailure(PostStartRegistrations()); + // reconnect (or verify connection) to the CastingPlayer that the app was connected to before being stopped, if any + if (CastingPlayer::GetTargetCastingPlayer() != nullptr) + { + CastingPlayer::GetTargetCastingPlayer()->VerifyOrEstablishConnection( + [](CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) { + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CastingApp::Start Could not reconnect to CastingPlayer %" CHIP_ERROR_FORMAT, + err.Format()); + } + else + { + ChipLogProgress(AppServer, "CastingApp::Start Reconnected to CastingPlayer(ID: %s)", castingPlayer->GetId()); + } + }); + } + return CHIP_NO_ERROR; } @@ -112,8 +130,8 @@ CHIP_ERROR CastingApp::PostStartRegistrations() chip::BindingManager::GetInstance().Init( { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); - // TODO: Set FabricDelegate - // chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&mPersistenceManager); + // Set FabricDelegate + chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(support::CastingStore::GetInstance()); // Register DeviceEvent Handler ReturnErrorOnFailure(chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(ChipDeviceEventHandler::Handle, 0)); @@ -127,15 +145,12 @@ CHIP_ERROR CastingApp::Stop() ChipLogProgress(Discovery, "CastingApp::Stop() called"); VerifyOrReturnError(mState == CASTING_APP_RUNNING, CHIP_ERROR_INCORRECT_STATE); - // TODO: add logic to capture CastingPlayers that we are currently connected to, so we can automatically reconnect with them on - // Start() again - // Shutdown the Matter server chip::Server::GetInstance().Shutdown(); mState = CASTING_APP_NOT_RUNNING; // CastingApp stopped successfully, set state to NOT_RUNNING - return CHIP_ERROR_NOT_IMPLEMENTED; + return CHIP_NO_ERROR; } CHIP_ERROR CastingApp::ShutdownAllSubscriptions() @@ -147,6 +162,11 @@ CHIP_ERROR CastingApp::ShutdownAllSubscriptions() return CHIP_NO_ERROR; } +CHIP_ERROR CastingApp::ClearCache() +{ + return support::CastingStore::GetInstance()->DeleteAll(); +} + }; // namespace core }; // namespace casting }; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h index 3ac2c34b104e8b..ed26e24709e6c5 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h @@ -55,13 +55,15 @@ class CastingApp /** * @brief Starts the Matter server that the CastingApp runs on and registers all the necessary delegates * CastingApp. + * If the CastingApp was previously connected to a CastingPlayer and then Stopped by calling the Stop() + * API, it will re-connect to the CastingPlayer. * * @return CHIP_ERROR - CHIP_NO_ERROR if Matter server started successfully, specific error code otherwise. */ CHIP_ERROR Start(); /** - * @brief Stops the Matter server that the CastingApp runs on + * @brief Stops the Matter server that the CastingApp runs on. * * @return CHIP_ERROR - CHIP_NO_ERROR if Matter server stopped successfully, specific error code otherwise. */ @@ -77,6 +79,11 @@ class CastingApp */ CHIP_ERROR ShutdownAllSubscriptions(); + /** + * @brief Clears app cache that contains the information about CastingPlayers previously connected to + */ + CHIP_ERROR ClearCache(); + private: CastingApp(); static CastingApp * _castingApp; diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp index f2fa4057855d91..e469775ce27bc0 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp @@ -63,6 +63,8 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns unsigned index = (unsigned int) std::distance(cachedCastingPlayers.begin(), it); if (ContainsDesiredEndpoint(&cachedCastingPlayers[index], desiredEndpointFilter)) { + ChipLogProgress( + AppServer, "CastingPlayer::VerifyOrEstablishConnection calling FindOrEstablishSession on cached CastingPlayer"); *this = cachedCastingPlayers[index]; FindOrEstablishSession( @@ -80,7 +82,12 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer failed"); CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; - support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + CHIP_ERROR e = support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + if (e != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CastingStore::Delete() failed. Err: %" CHIP_ERROR_FORMAT, e.Format()); + } + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); mTargetCastingPlayer = nullptr; @@ -123,6 +130,12 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns } } +void CastingPlayer::Disconnect() +{ + mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + mTargetCastingPlayer = nullptr; +} + void CastingPlayer::RegisterEndpoint(const memory::Strong endpoint) { auto it = std::find_if(mEndpoints.begin(), mEndpoints.end(), [endpoint](const memory::Strong & _endpoint) { @@ -148,11 +161,11 @@ CHIP_ERROR CastingPlayer::SendUserDirectedCommissioningRequest() VerifyOrReturnValue(ipAddressToUse != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(AppServer, "No IP Address found to send UDC request to")); + ReturnErrorOnFailure(support::ChipDeviceEventHandler::SetUdcStatus(true)); + ReturnErrorOnFailure(chip::Server::GetInstance().SendUserDirectedCommissioningRequest( chip::Transport::PeerAddress::UDP(*ipAddressToUse, mAttributes.port, mAttributes.interfaceId))); - ReturnErrorOnFailure(support::ChipDeviceEventHandler::SetUdcStatus(true)); - return CHIP_NO_ERROR; } diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index 424981eb8df0a0..7d383bb6669225 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -121,7 +121,7 @@ class CastingPlayer : public std::enable_shared_from_this * @param onCompleted for success - called back with CHIP_NO_ERROR and CastingPlayer *. * For failure - called back with an error and nullptr. * @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window open, if commissioning is - * required. Defaults to kCommissioningWindowTimeoutSec. + * required. Needs to be >= kCommissioningWindowTimeoutSec. * @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint that the client wants to * interact with after commissioning. If this value is passed in, the VerifyOrEstablishConnection will force User Directed * Commissioning, in case the desired Endpoint is not found in the on device CastingStore. @@ -130,6 +130,11 @@ class CastingPlayer : public std::enable_shared_from_this unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter = EndpointFilter()); + /** + * @brief Sets the internal connection state of this CastingPlayer to "disconnected" + */ + void Disconnect(); + /** * @brief Find an existing session for this CastingPlayer, or trigger a new session * request. diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp index f4eb42c8ca607f..402febdb9f6b97 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp @@ -77,7 +77,7 @@ void DeviceDiscoveryDelegateImpl::OnDiscoveredDevice(const chip::Dnssd::Discover { ChipLogProgress(Discovery, "DeviceDiscoveryDelegateImpl::OnDiscoveredDevice() called"); VerifyOrReturn(mClientDelegate != nullptr, - ChipLogError(NotSpecified, "CastingPlayerDeviceDiscoveryDelegate, mClientDelegate is a nullptr")); + ChipLogError(Discovery, "DeviceDiscoveryDelegateImpl::OnDiscoveredDevice mClientDelegate is a nullptr")); // convert nodeData to CastingPlayer CastingPlayerAttributes attributes; diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h index 3bbd4eb94ba084..86886ac71be30f 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h @@ -72,8 +72,7 @@ class DeviceDiscoveryDelegateImpl : public chip::Controller::DeviceDiscoveryDele }; /** - * @brief CastingPlayerDiscovery represents the discovery of Casting Players. - * This class is a singleton. + * @brief CastingPlayerDiscovery is a singleton utility class for discovering CastingPlayers. */ class CastingPlayerDiscovery { diff --git a/examples/tv-casting-app/tv-casting-common/core/Endpoint.cpp b/examples/tv-casting-app/tv-casting-common/core/Endpoint.cpp new file mode 100644 index 00000000000000..d2fb12e48daf68 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/Endpoint.cpp @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Endpoint.h" + +#include "clusters/Clusters.h" + +namespace matter { +namespace casting { +namespace core { + +void Endpoint::RegisterClusters(std::vector clusters) +{ + for (chip::ClusterId clusterId : clusters) + { + switch (clusterId) + { + case chip::app::Clusters::ApplicationBasic::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::ApplicationLauncher::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::ContentLauncher::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::KeypadInput::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::LevelControl::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::OnOff::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::MediaPlayback::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::TargetNavigator::Id: + RegisterCluster(clusterId); + break; + + case chip::app::Clusters::WakeOnLan::Id: + RegisterCluster(clusterId); + break; + + default: + ChipLogProgress(AppServer, "Skipping registration of clusterId %d for endpointId %d", clusterId, GetId()); + break; + } + } +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h index c8389ba63fa2e0..006fde019dd182 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h +++ b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h @@ -112,6 +112,8 @@ class Endpoint : public std::enable_shared_from_this return serverList; } + void RegisterClusters(std::vector clusters); + /** * @brief Registers a cluster of type T against the passed in clusterId * for this Endpoint diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp index b6ee4779d41d6e..f4d92f71b202ba 100644 --- a/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp @@ -90,8 +90,8 @@ std::vector CastingStore::ReadAll() VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); - chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; - err = reader.EnterContainer(outerContainerType); + chip::TLV::TLVType outerContainerType; + err = reader.EnterContainer(outerContainerType); VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); @@ -99,8 +99,8 @@ std::vector CastingStore::ReadAll() VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); chip::TLV::Tag outerContainerTag = reader.GetTag(); - uint8_t outerContainerTagTagNum = static_cast(chip::TLV::TagNumFromTag(outerContainerTag)); - VerifyOrReturnValue(outerContainerTagTagNum == kCastingStoreDataVersionTag, castingPlayers, + uint8_t outerContainerTagNum = static_cast(chip::TLV::TagNumFromTag(outerContainerTag)); + VerifyOrReturnValue(outerContainerTagNum == kCastingStoreDataVersionTag, castingPlayers, ChipLogError(AppServer, "CastingStoreDataVersionTag not found")); uint32_t version; err = reader.Get(version); @@ -109,23 +109,24 @@ std::vector CastingStore::ReadAll() ChipLogProgress(AppServer, "CastingStore::ReadAll TLV(CastingStoreData) version: %d", version); // Entering CastingPlayers container - chip::TLV::TLVType castingPlayersContainerType = chip::TLV::kTLVType_Array; - err = reader.Next(); + err = reader.Next(); VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); + chip::TLV::TLVType castingPlayersContainerType; err = reader.EnterContainer(castingPlayersContainerType); VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); while ((err = reader.Next()) == CHIP_NO_ERROR) { // Entering CastingPlayer container - chip::TLV::TLVType castingPlayerContainerType = chip::TLV::kTLVType_Structure; - err = reader.EnterContainer(castingPlayerContainerType); + chip::TLV::TLVType castingPlayerContainerType; + err = reader.EnterContainer(castingPlayerContainerType); VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); core::CastingPlayerAttributes attributes; std::vector endpointAttributesList; + std::map> endpointServerListMap; while ((err = reader.Next()) == CHIP_NO_ERROR) { chip::TLV::Tag castingPlayerContainerTag = reader.GetTag(); @@ -181,6 +182,23 @@ std::vector CastingStore::ReadAll() continue; } + if (castingPlayerContainerTagNum == kCastingPlayerPortTag) + { + err = reader.Get(attributes.port); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerInstanceNameTag) + { + err = reader.GetBytes(reinterpret_cast(attributes.instanceName), + chip::Dnssd::Commission::kInstanceNameMaxLength + 1); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.GetBytes failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + if (castingPlayerContainerTagNum == kCastingPlayerDeviceNameTag) { err = reader.GetBytes(reinterpret_cast(attributes.deviceName), chip::Dnssd::kMaxDeviceNameLen + 1); @@ -200,16 +218,17 @@ std::vector CastingStore::ReadAll() if (castingPlayerContainerTagNum == kCastingPlayerEndpointsContainerTag) { // Entering Endpoints container - chip::TLV::TLVType endpointsContainerType = chip::TLV::kTLVType_Array; - err = reader.EnterContainer(endpointsContainerType); + chip::TLV::TLVType endpointsContainerType; + err = reader.EnterContainer(endpointsContainerType); VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); core::EndpointAttributes endpointAttributes; + std::vector serverList; while ((err = reader.Next()) == CHIP_NO_ERROR) { // Entering Endpoint container - chip::TLV::TLVType endpointContainerType = chip::TLV::kTLVType_Structure; - err = reader.EnterContainer(endpointContainerType); + chip::TLV::TLVType endpointContainerType; + err = reader.EnterContainer(endpointContainerType); VerifyOrReturnValue( err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); @@ -249,8 +268,8 @@ std::vector CastingStore::ReadAll() if (endpointContainerTagNum == kCastingPlayerEndpointDeviceTypeListContainerTag) { // Entering DeviceTypeList container - chip::TLV::TLVType deviceTypeListContainerType = chip::TLV::kTLVType_Array; - err = reader.EnterContainer(deviceTypeListContainerType); + chip::TLV::TLVType deviceTypeListContainerType; + err = reader.EnterContainer(deviceTypeListContainerType); VerifyOrReturnValue( err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); @@ -258,7 +277,7 @@ std::vector CastingStore::ReadAll() while ((err = reader.Next()) == CHIP_NO_ERROR) { // Entering DeviceTypeStruct container - chip::TLV::TLVType deviceTypeStructContainerType = chip::TLV::kTLVType_Structure; + chip::TLV::TLVType deviceTypeStructContainerType; err = reader.EnterContainer(deviceTypeStructContainerType); VerifyOrReturnValue( err == CHIP_NO_ERROR, std::vector(), @@ -303,7 +322,7 @@ std::vector CastingStore::ReadAll() err.Format())); deviceTypeList.push_back(deviceTypeStruct); - break; + continue; } } if (err == CHIP_END_OF_TLV) @@ -315,9 +334,49 @@ std::vector CastingStore::ReadAll() ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); endpointAttributes.mDeviceTypeList = deviceTypeList; - break; + continue; + } + } + + if (endpointContainerTagNum == kCastingPlayerEndpointServerListContainerTag) + { + // Entering ServerList container + chip::TLV::TLVType serverListContainerType; + err = reader.EnterContainer(serverListContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + chip::TLV::Tag serverListContainerTag = reader.GetTag(); + VerifyOrReturnValue(chip::TLV::IsContextTag(serverListContainerTag), + std::vector(), + ChipLogError(AppServer, "Unexpected non-context TLV tag")); + + uint8_t serverListContainerTagNum = + static_cast(chip::TLV::TagNumFromTag(serverListContainerTag)); + if (serverListContainerTagNum == kCastingPlayerEndpointServerClusterIdTag) + { + chip::ClusterId clusterId; + err = reader.Get(clusterId); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + serverList.push_back(clusterId); + continue; + } + } + + if (err == CHIP_END_OF_TLV) + { + // Exiting ServerList container + err = reader.ExitContainer(serverListContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; } - continue; } } @@ -330,7 +389,8 @@ std::vector CastingStore::ReadAll() ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); endpointAttributesList.push_back(endpointAttributes); - break; + endpointServerListMap[endpointAttributes.mId] = serverList; + continue; } } @@ -341,29 +401,27 @@ std::vector CastingStore::ReadAll() VerifyOrReturnValue( err == CHIP_NO_ERROR, std::vector(), ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); - break; + continue; } - - continue; } - - if (err == CHIP_END_OF_TLV) + } + if (err == CHIP_END_OF_TLV) + { + // Exiting CastingPlayer container + err = reader.ExitContainer(castingPlayerContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + // create a castingPlayer with Endpoints and add it to the castingPlayers to be returned + core::CastingPlayer castingPlayer(attributes); + for (auto & endpointAttributes : endpointAttributesList) { - // Exiting CastingPlayer container - err = reader.ExitContainer(castingPlayerContainerType); - VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), - ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); - - // create a castingPlayer with Endpoints and add it to the castingPlayers to be returned - core::CastingPlayer castingPlayer(attributes); - for (auto & endpointAttributes : endpointAttributesList) - { - std::shared_ptr endpoint(new core::Endpoint(&castingPlayer, endpointAttributes)); - castingPlayer.RegisterEndpoint(endpoint); - } - castingPlayers.push_back(castingPlayer); - break; + std::shared_ptr endpoint(new core::Endpoint(&castingPlayer, endpointAttributes)); + endpoint->RegisterClusters(endpointServerListMap[endpointAttributes.mId]); + castingPlayer.RegisterEndpoint(endpoint); } + castingPlayers.push_back(castingPlayer); + continue; } } @@ -382,67 +440,6 @@ std::vector CastingStore::ReadAll() return castingPlayers; } -CHIP_ERROR CastingStore::DeleteAll() -{ - ChipLogProgress(AppServer, "CastingStore::DeleteAll called"); - CHIP_ERROR err = chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr().Delete(kCastingStoreDataKey); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) // no error, if the key-value pair was not stored - { - ChipLogProgress(AppServer, "CastingStore::DeleteAll ignoring error %" CHIP_ERROR_FORMAT, err.Format()); - return CHIP_NO_ERROR; - } - return err; -} - -CHIP_ERROR CastingStore::Delete(core::CastingPlayer castingPlayer) -{ - ChipLogProgress(AppServer, "CastingStore::Delete"); - - // Read cache of CastingPlayers - std::vector castingPlayers = ReadAll(); - - // search for castingPlayer in CastingStore cache and delete it, if found - if (castingPlayers.size() != 0) - { - auto it = std::find_if( - castingPlayers.begin(), castingPlayers.end(), - [castingPlayer](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == castingPlayer; }); - - if (it != castingPlayers.end()) - { - ChipLogProgress(AppServer, "CastingStore::Delete deleting CastingPlayer %s from CastingStore cache", it->GetId()); - castingPlayers.erase(it); - return WriteAll(castingPlayers); - } - } - return CHIP_NO_ERROR; -} - -void CastingStore::OnFabricRemoved(const chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) -{ - ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved"); - - // Read cache of CastingPlayers - std::vector castingPlayers = ReadAll(); - - // search for castingPlayer in CastingStore cache and delete it, if found - if (castingPlayers.size() != 0) - { - auto it = std::find_if(castingPlayers.begin(), castingPlayers.end(), - [fabricIndex](const core::CastingPlayer & castingPlayerParam) { - return castingPlayerParam.GetFabricIndex() == fabricIndex; - }); - - if (it != castingPlayers.end()) - { - ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved deleting CastingPlayer %s from CastingStore cache", - it->GetId()); - castingPlayers.erase(it); - WriteAll(castingPlayers); - } - } -} - CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayers) { ChipLogProgress(AppServer, "CastingStore::WriteAll called"); @@ -451,28 +448,33 @@ CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayer uint8_t castingStoreData[kCastingStoreDataMaxBytes]; tlvWriter.Init(castingStoreData, kCastingStoreDataMaxBytes); - chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; + chip::TLV::TLVType outerContainerType; ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, outerContainerType)); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingStoreDataVersionTag), kCurrentCastingStoreDataVersion)); - chip::TLV::TLVType castingPlayersContainerType = chip::TLV::kTLVType_Array; + chip::TLV::TLVType castingPlayersContainerType; // CastingPlayers container starts ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayersContainerTag), chip::TLV::kTLVType_Array, castingPlayersContainerType)); for (auto & castingPlayer : castingPlayers) { - chip::TLV::TLVType castingPlayerContainerType = chip::TLV::kTLVType_Structure; + chip::TLV::TLVType castingPlayerContainerType; // CastingPlayer container starts - ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerContainerTag), - chip::TLV::kTLVType_Structure, castingPlayerContainerType)); + ReturnErrorOnFailure( + tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, castingPlayerContainerType)); - ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerIdTag), castingPlayer.GetId())); + ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerIdTag), (const uint8_t *) castingPlayer.GetId(), + static_cast(strlen(castingPlayer.GetId()) + 1))); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerNodeIdTag), castingPlayer.GetNodeId())); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerFabricIndexTag), castingPlayer.GetFabricIndex())); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerVendorIdTag), castingPlayer.GetVendorId())); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerProductIdTag), castingPlayer.GetProductId())); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerDeviceTypeIdTag), castingPlayer.GetDeviceType())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerPortTag), castingPlayer.GetPort())); + ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerInstanceNameTag), + (const uint8_t *) castingPlayer.GetInstanceName(), + static_cast(strlen(castingPlayer.GetInstanceName()) + 1))); ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerDeviceNameTag), (const uint8_t *) castingPlayer.GetDeviceName(), static_cast(strlen(castingPlayer.GetDeviceName()) + 1))); @@ -481,34 +483,33 @@ CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayer static_cast(strlen(castingPlayer.GetHostName()) + 1))); // Endpoints container starts - chip::TLV::TLVType endpointsContainerType = chip::TLV::kTLVType_Array; + chip::TLV::TLVType endpointsContainerType; ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointsContainerTag), chip::TLV::kTLVType_Array, endpointsContainerType)); std::vector> endpoints = core::CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); for (auto & endpoint : endpoints) { - chip::TLV::TLVType endpointContainerType = chip::TLV::kTLVType_Structure; + chip::TLV::TLVType endpointContainerType; // Endpoint container starts - ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointContainerTag), - chip::TLV::kTLVType_Structure, endpointContainerType)); + ReturnErrorOnFailure( + tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, endpointContainerType)); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointIdTag), endpoint->GetId())); ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointVendorIdTag), endpoint->GetVendorId())); ReturnErrorOnFailure( tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointProductIdTag), endpoint->GetProductId())); // DeviceTypeList container starts - chip::TLV::TLVType deviceTypeListContainerType = chip::TLV::kTLVType_Array; + chip::TLV::TLVType deviceTypeListContainerType; ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeListContainerTag), chip::TLV::kTLVType_Array, deviceTypeListContainerType)); std::vector deviceTypeList = endpoint->GetDeviceTypeList(); for (chip::app::Clusters::Descriptor::Structs::DeviceTypeStruct::DecodableType deviceTypeStruct : deviceTypeList) { - chip::TLV::TLVType deviceTypeStructContainerType = chip::TLV::kTLVType_Structure; + chip::TLV::TLVType deviceTypeStructContainerType; // DeviceTypeStruct container starts - ReturnErrorOnFailure( - tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeStructContainerTag), - chip::TLV::kTLVType_Structure, deviceTypeStructContainerType)); + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, + deviceTypeStructContainerType)); ReturnErrorOnFailure( tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeTag), deviceTypeStruct.deviceType)); ReturnErrorOnFailure( @@ -521,9 +522,9 @@ CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayer ReturnErrorOnFailure(tlvWriter.EndContainer(deviceTypeListContainerType)); // ServerList container starts - chip::TLV::TLVType serverListContainerType = chip::TLV::kTLVType_Array; + chip::TLV::TLVType serverListContainerType; ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointServerListContainerTag), - chip::TLV::kTLVType_Array, serverListContainerType)); + chip::TLV::kTLVType_Structure, serverListContainerType)); std::vector serverList = endpoint->GetServerList(); for (chip::ClusterId clusterId : serverList) { @@ -556,6 +557,67 @@ CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayer tlvWriter.GetLengthWritten()); } +CHIP_ERROR CastingStore::DeleteAll() +{ + ChipLogProgress(AppServer, "CastingStore::DeleteAll called"); + CHIP_ERROR err = chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr().Delete(kCastingStoreDataKey); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) // no error, if the key-value pair was not stored + { + ChipLogProgress(AppServer, "CastingStore::DeleteAll ignoring error %" CHIP_ERROR_FORMAT, err.Format()); + return CHIP_NO_ERROR; + } + return err; +} + +CHIP_ERROR CastingStore::Delete(core::CastingPlayer castingPlayer) +{ + ChipLogProgress(AppServer, "CastingStore::Delete"); + + // Read cache of CastingPlayers + std::vector castingPlayers = ReadAll(); + + // search for castingPlayer in CastingStore cache and delete it, if found + if (castingPlayers.size() != 0) + { + auto it = std::find_if( + castingPlayers.begin(), castingPlayers.end(), + [castingPlayer](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == castingPlayer; }); + + if (it != castingPlayers.end()) + { + ChipLogProgress(AppServer, "CastingStore::Delete deleting CastingPlayer %s from CastingStore cache", it->GetId()); + castingPlayers.erase(it); + return WriteAll(castingPlayers); + } + } + return CHIP_NO_ERROR; +} + +void CastingStore::OnFabricRemoved(const chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) +{ + ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved"); + + // Read cache of CastingPlayers + std::vector castingPlayers = ReadAll(); + + // search for castingPlayer in CastingStore cache and delete it, if found + if (castingPlayers.size() != 0) + { + auto it = std::find_if(castingPlayers.begin(), castingPlayers.end(), + [fabricIndex](const core::CastingPlayer & castingPlayerParam) { + return castingPlayerParam.GetFabricIndex() == fabricIndex; + }); + + if (it != castingPlayers.end()) + { + ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved deleting CastingPlayer %s from CastingStore cache", + it->GetId()); + castingPlayers.erase(it); + WriteAll(castingPlayers); + } + } +} + }; // namespace support }; // namespace casting }; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.h b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h index 563410fb380e3a..9602af892c9909 100644 --- a/examples/tv-casting-app/tv-casting-common/support/CastingStore.h +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h @@ -74,24 +74,23 @@ class CastingStore : public chip::FabricTable::Delegate kCastingStoreDataVersionTag = 1, kCastingPlayersContainerTag, - kCastingPlayerContainerTag, kCastingPlayerIdTag, kCastingPlayerNodeIdTag, kCastingPlayerFabricIndexTag, kCastingPlayerVendorIdTag, kCastingPlayerProductIdTag, kCastingPlayerDeviceTypeIdTag, + kCastingPlayerPortTag, + kCastingPlayerInstanceNameTag, kCastingPlayerDeviceNameTag, kCastingPlayerHostNameTag, kCastingPlayerEndpointsContainerTag, - kCastingPlayerEndpointContainerTag, kCastingPlayerEndpointIdTag, kCastingPlayerEndpointVendorIdTag, kCastingPlayerEndpointProductIdTag, kCastingPlayerEndpointDeviceTypeListContainerTag, - kCastingPlayerEndpointDeviceTypeStructContainerTag, kCastingPlayerEndpointDeviceTypeTag, kCastingPlayerEndpointDeviceTypeRevisionTag, diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp index eaaf8dc44cc834..bced79be4bc102 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp @@ -76,7 +76,12 @@ void ChipDeviceEventHandler::Handle(const chip::DeviceLayer::ChipDeviceEvent * e [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { ChipLogError(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer failed"); CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; - support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + CHIP_ERROR err = support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CastingStore::Delete() failed. Err: %" CHIP_ERROR_FORMAT, err.Format()); + } + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); CastingPlayer::mTargetCastingPlayer = nullptr; @@ -187,6 +192,17 @@ void ChipDeviceEventHandler::HandleCommissioningComplete(const chip::DeviceLayer runPostCommissioning = true; } +CHIP_ERROR ChipDeviceEventHandler::SetUdcStatus(bool udcInProgress) +{ + if (sUdcInProgress == udcInProgress) + { + ChipLogError(AppServer, "UDC in progress state is already %d", sUdcInProgress); + return CHIP_ERROR_INCORRECT_STATE; + } + sUdcInProgress = udcInProgress; + return CHIP_NO_ERROR; +} + }; // namespace support }; // namespace casting }; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h index e6759856be8caf..d8660a007cc585 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h @@ -43,16 +43,7 @@ class ChipDeviceEventHandler * If UDC was already in progress when this method was called, it will return a CHIP_ERROR_INCORRECT_STATE without changing the * internal state. */ - static CHIP_ERROR SetUdcStatus(bool udcInProgress) - { - if (sUdcInProgress == udcInProgress) - { - ChipLogError(AppServer, "UDC in progress state is already %d", sUdcInProgress); - return CHIP_ERROR_INCORRECT_STATE; - } - sUdcInProgress = udcInProgress; - return CHIP_NO_ERROR; - } + static CHIP_ERROR SetUdcStatus(bool udcInProgress); private: /** diff --git a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp index d78e5790a0ceb0..478847d15a2eca 100644 --- a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp @@ -121,52 +121,7 @@ void EndpointListLoader::Complete() EndpointAttributes endpointAttributes = mEndpointAttributesList[i]; std::shared_ptr endpoint = std::make_shared(CastingPlayer::GetTargetCastingPlayer(), endpointAttributes); - for (chip::ClusterId clusterId : mEndpointServerLists[i]) - { - switch (clusterId) - { - case chip::app::Clusters::ApplicationBasic::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::ApplicationLauncher::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::ContentLauncher::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::KeypadInput::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::LevelControl::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::OnOff::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::MediaPlayback::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::TargetNavigator::Id: - endpoint->RegisterCluster(clusterId); - break; - - case chip::app::Clusters::WakeOnLan::Id: - endpoint->RegisterCluster(clusterId); - break; - - default: - ChipLogProgress(AppServer, "Skipping registration of clusterId %d for endpointId %d", clusterId, - endpointAttributes.mId); - break; - } - } + endpoint->RegisterClusters(mEndpointServerLists[i]); CastingPlayer::GetTargetCastingPlayer()->RegisterEndpoint(endpoint); } @@ -181,8 +136,14 @@ void EndpointListLoader::Complete() mSessionHandle = nullptr; mNewEndpointsToLoad = 0; - // done loading endpoints, callback client OnCompleted - support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + // done loading endpoints, store TargetCastingPlayer + CHIP_ERROR err = support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "CastingStore::AddOrUpdate() failed. Err: %" CHIP_ERROR_FORMAT, err.Format()); + } + + // callback client OnCompleted VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); }