diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm index 490eec1e7421b9..40d265b3e171e1 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm @@ -38,9 +38,6 @@ ChipLogProgress(chipTool, "Running Command"); ReturnErrorOnFailure(MaybeSetUpStack()); SetIdentity(mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha); - ChipLogDetail(chipTool, "Setting OTA Provider Delegate:"); - mOTADelegate.nodeID = [CurrentCommissioner() controllerNodeId]; - [CurrentCommissioner() setOTAProviderDelegate:mOTADelegate queue:mOTAProviderCallbackQueue]; ReturnLogErrorOnFailure(RunCommand()); ReturnLogErrorOnFailure(StartWaiting(GetWaitDuration())); @@ -67,7 +64,6 @@ CHIPToolKeypair * nocSigner = [[CHIPToolKeypair alloc] init]; storage = [[CHIPToolPersistentStorageDelegate alloc] init]; - mOTAProviderCallbackQueue = dispatch_queue_create("com.darwin-framework-tool.command", DISPATCH_QUEUE_SERIAL); mOTADelegate = [[OTAProviderDelegate alloc] init]; auto factory = [MTRControllerFactory sharedInstance]; @@ -79,6 +75,7 @@ auto params = [[MTRControllerFactoryParams alloc] initWithStorage:storage]; params.port = @(kListenPort); params.startServer = YES; + params.otaProviderDelegate = mOTADelegate; if ([factory startup:params] == NO) { ChipLogError(chipTool, "Controller factory startup failed"); diff --git a/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm index afedecb14aa97a..8e3cbd8b5bd7bd 100644 --- a/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm +++ b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm @@ -33,7 +33,6 @@ @implementation OTAProviderDelegate - (instancetype)init { if (self = [super init]) { - _nodeID = @(0); _selectedCandidate = [[DeviceSoftwareVersionModel alloc] init]; _userConsentState = OTAProviderUserUnknown; } @@ -122,7 +121,7 @@ - (void)handleBDXTransferSessionBegin:(NSString * _Nonnull)fileDesignator offset:(NSNumber * _Nonnull)offset completionHandler:(void (^)(NSError * error))completionHandler { - NSLog(@"BDX TransferSession begin with %@ (offset: %@ )", fileDesignator, offset); + NSLog(@"BDX TransferSession begin with %@ (offset: %@)", fileDesignator, offset); auto * handle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator]; if (handle == nil) { @@ -198,23 +197,30 @@ - (bool)SelectOTACandidate:(NSNumber *)requestorVendorID rPID:(NSNumber *)requestorProductID rSV:(NSNumber *)requestorSoftwareVersion { + auto vendorId = [requestorVendorID unsignedIntValue]; + auto productId = [requestorProductID unsignedIntValue]; + auto softwareVersion = [requestorSoftwareVersion unsignedLongValue]; + bool candidateFound = false; NSArray * sortedArray = [_candidates sortedArrayUsingSelector:@selector(CompareSoftwareVersions:)]; for (DeviceSoftwareVersionModel * candidate : sortedArray) { - if (candidate.deviceModelData.softwareVersionValid - && ([requestorSoftwareVersion unsignedLongValue] < [candidate.softwareVersion unsignedLongValue]) - && ([requestorSoftwareVersion unsignedLongValue] >= - [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue]) - && ([requestorSoftwareVersion unsignedLongValue] <= - [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue]) - && ([requestorVendorID unsignedIntValue] == [candidate.deviceModelData.vendorId unsignedIntValue]) - && ([requestorProductID unsignedIntValue] == [candidate.deviceModelData.productId unsignedIntValue])) { - candidateFound = true; + auto candidateSoftwareVersionValid = candidate.deviceModelData.softwareVersionValid; + auto candidateSoftwareVersion = [candidate.softwareVersion unsignedLongValue]; + auto candidateMinApplicableSoftwareVersion = [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue]; + auto candidateMaxApplicableSoftwareVersion = [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue]; + auto candidateVendorId = [candidate.deviceModelData.vendorId unsignedIntValue]; + auto candidateProductId = [candidate.deviceModelData.productId unsignedIntValue]; + + if (candidateSoftwareVersionValid && (softwareVersion < candidateSoftwareVersion) + && (softwareVersion >= candidateMinApplicableSoftwareVersion) + && (softwareVersion <= candidateMaxApplicableSoftwareVersion) && (vendorId == candidateVendorId) + && (productId == candidateProductId)) { _selectedCandidate = candidate; - _selectedCandidate.imageURI = [NSString - stringWithFormat:@"bdx://%016llX/%@", [_nodeID unsignedLongLongValue], _selectedCandidate.deviceModelData.otaURL]; + _selectedCandidate.imageURI = candidate.deviceModelData.otaURL; + candidateFound = true; } } + return candidateFound; } diff --git a/src/darwin/Framework/CHIP/MTRControllerFactory.h b/src/darwin/Framework/CHIP/MTRControllerFactory.h index 5c457b957dd39c..766adf9c10eb41 100644 --- a/src/darwin/Framework/CHIP/MTRControllerFactory.h +++ b/src/darwin/Framework/CHIP/MTRControllerFactory.h @@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol MTRPersistentStorageDelegate; +@protocol MTROTAProviderDelegate; @protocol MTRKeypair; @class MTRDeviceController; @@ -37,6 +38,13 @@ NS_ASSUME_NONNULL_BEGIN * controllers ends up interacting with. */ @property (strong, nonatomic, readonly) id storageDelegate; + +/* + * OTA Provider delegate to be called when an OTA Requestor is requesting a software update. + * Defaults to nil. + */ +@property (strong, nonatomic, nullable) id otaProviderDelegate; + /* * The Product Attestation Authority certificates that are trusted to sign * device attestation information. Defaults to nil. diff --git a/src/darwin/Framework/CHIP/MTRControllerFactory.mm b/src/darwin/Framework/CHIP/MTRControllerFactory.mm index 676455d5916a74..38d0cfe927388c 100644 --- a/src/darwin/Framework/CHIP/MTRControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRControllerFactory.mm @@ -26,6 +26,7 @@ #import "MTRDeviceController_Internal.h" #import "MTRLogging.h" #import "MTRMemory.h" +#import "MTROTAProviderDelegateBridge.h" #import "MTRP256KeypairBridge.h" #import "MTRPersistentStorageDelegateBridge.h" #import "NSDataSpanConversion.h" @@ -53,6 +54,7 @@ static NSString * const kErrorControllerFactoryInit = @"Init failure while initializing controller factory"; static NSString * const kErrorKeystoreInit = @"Init failure while initializing persistent storage keystore"; static NSString * const kErrorCertStoreInit = @"Init failure while initializing persistent storage operational certificate store"; +static NSString * const kErrorOtaProviderInit = @"Init failure while creating an OTA provider delegate"; @interface MTRControllerFactory () @@ -60,6 +62,7 @@ @interface MTRControllerFactory () @property (readonly) DeviceControllerFactory * controllerFactory; @property (readonly) MTRPersistentStorageDelegateBridge * persistentStorageDelegateBridge; @property (readonly) MTRAttestationTrustStoreBridge * attestationTrustStoreBridge; +@property (readonly) MTROTAProviderDelegateBridge * otaProviderDelegateBridge; // We use TestPersistentStorageDelegate just to get an in-memory store to back // our group data provider impl. We initialize this store correctly on every // controller startup, so don't need to actually persist it. @@ -171,6 +174,11 @@ - (void)cleanupStartupObjects _attestationTrustStoreBridge = nullptr; } + if (_otaProviderDelegateBridge) { + delete _otaProviderDelegateBridge; + _otaProviderDelegateBridge = nullptr; + } + if (_keystore) { _keystore->Finish(); delete _keystore; @@ -211,6 +219,14 @@ - (BOOL)startup:(MTRControllerFactoryParams *)startupParams return; } + if (startupParams.otaProviderDelegate) { + _otaProviderDelegateBridge = new MTROTAProviderDelegateBridge(startupParams.otaProviderDelegate); + if (_otaProviderDelegateBridge == nil) { + MTR_LOG_ERROR("Error: %@", kErrorOtaProviderInit); + return; + } + } + // TODO: Allow passing a different keystore implementation via startupParams. _keystore = new PersistentStorageOperationalKeystore(); if (_keystore == nullptr) { @@ -446,6 +462,11 @@ - (MTRDeviceController * _Nullable)createController // Bringing up the first controller. Start the event loop now. If we // fail to bring it up, its cleanup will stop the event loop again. chip::DeviceLayer::PlatformMgrImpl().StartEventLoopTask(); + + if (_otaProviderDelegateBridge) { + auto systemState = _controllerFactory->GetSystemState(); + _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr()); + } } // Add the controller to _controllers now, so if we fail partway through its @@ -521,6 +542,10 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller [_controllers removeObject:controller]; if ([_controllers count] == 0) { + if (_otaProviderDelegateBridge) { + _otaProviderDelegateBridge->Shutdown(); + } + // That was our last controller. Stop the event loop before it // shuts down, because shutdown of the last controller will tear // down most of the world. @@ -556,6 +581,7 @@ - (instancetype)initWithStorage:(id)storageDelegat } _storageDelegate = storageDelegate; + _otaProviderDelegate = nil; _paaCerts = nil; _port = nil; _startServer = NO; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.h b/src/darwin/Framework/CHIP/MTRDeviceController.h index fe1f0eb8b61b22..61db4061896d10 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController.h @@ -27,7 +27,6 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS @class MTRCommissioningParameters; @protocol MTRDevicePairingDelegate; -@protocol MTROTAProviderDelegate; @interface MTRDeviceController : NSObject @@ -120,15 +119,6 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS */ - (void)setPairingDelegate:(id)delegate queue:(dispatch_queue_t)queue; -/** - * Set the Delegate for the OTA Provider as well as the Queue on which the Delegate callbacks will be triggered - * - * @param[in] delegate The delegate the OTA Provider should use - * - * @param[in] queue The queue on which the callbacks will be delivered - */ -- (void)setOTAProviderDelegate:(id)delegate queue:(dispatch_queue_t)queue; - /** * Shutdown the controller. Calls to shutdown after the first one are NO-OPs. */ diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 12884565fcb1ed..586219008e55e6 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -25,7 +25,6 @@ #import "MTRError_Internal.h" #import "MTRKeypair.h" #import "MTRLogging.h" -#import "MTROTAProviderDelegateBridge.h" #import "MTROperationalCredentialsDelegate.h" #import "MTRP256KeypairBridge.h" #import "MTRPersistentStorageDelegateBridge.h" @@ -57,7 +56,6 @@ static NSString * const kErrorOperationalCredentialsInit = @"Init failure while creating operational credentials delegate"; static NSString * const kErrorOperationalKeypairInit = @"Init failure while creating operational keypair bridge"; static NSString * const kErrorPairingInit = @"Init failure while creating a pairing delegate"; -static NSString * const kErrorOtaProviderInit = @"Init failure while creating an OTA provider delegate"; static NSString * const kErrorPairDevice = @"Failure while pairing the device"; static NSString * const kErrorUnpairDevice = @"Failure while unpairing the device"; static NSString * const kErrorStopPairing = @"Failure while trying to stop the pairing process"; @@ -77,7 +75,6 @@ @interface MTRDeviceController () @property (readonly) chip::Controller::DeviceCommissioner * cppCommissioner; @property (readonly) MTRDevicePairingDelegateBridge * pairingDelegateBridge; -@property (readonly) MTROTAProviderDelegateBridge * otaProviderDelegateBridge; @property (readonly) MTROperationalCredentialsDelegate * operationalCredentialsDelegate; @property (readonly) MTRP256KeypairBridge signingKeypairBridge; @property (readonly) MTRP256KeypairBridge operationalKeypairBridge; @@ -98,11 +95,6 @@ - (instancetype)initWithFactory:(MTRControllerFactory *)factory queue:(dispatch_ return nil; } - _otaProviderDelegateBridge = new MTROTAProviderDelegateBridge(); - if ([self checkForInitError:(_otaProviderDelegateBridge != nullptr) logMsg:kErrorOtaProviderInit]) { - return nil; - } - _operationalCredentialsDelegate = new MTROperationalCredentialsDelegate(); if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) { return nil; @@ -156,11 +148,6 @@ - (void)cleanup _operationalCredentialsDelegate = nullptr; } - if (_otaProviderDelegateBridge) { - delete _otaProviderDelegateBridge; - _otaProviderDelegateBridge = nullptr; - } - if (_pairingDelegateBridge) { delete _pairingDelegateBridge; _pairingDelegateBridge = nullptr; @@ -629,13 +616,6 @@ - (void)setPairingDelegate:(id)delegate queue:(dispatc }); } -- (void)setOTAProviderDelegate:(id)delegate queue:(dispatch_queue_t)queue; -{ - dispatch_async(_chipWorkQueue, ^{ - self->_otaProviderDelegateBridge->setDelegate(delegate, queue); - }); -} - - (BOOL)checkForInitError:(BOOL)condition logMsg:(NSString *)logMsg { if (condition) { diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h index 2bd56bf4c186c6..8240ae9e56cb63 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h @@ -24,10 +24,11 @@ NS_ASSUME_NONNULL_BEGIN class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDelegate { public: - MTROTAProviderDelegateBridge(); + MTROTAProviderDelegateBridge(id delegate); ~MTROTAProviderDelegateBridge(); - void setDelegate(id delegate, dispatch_queue_t queue); + void Init(chip::System::Layer * systemLayer, chip::Messaging::ExchangeManager * exchangeManager); + void Shutdown(); void HandleQueryImage( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, @@ -59,7 +60,7 @@ class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDele MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams); _Nullable id mDelegate; - _Nullable dispatch_queue_t mQueue; + dispatch_queue_t mWorkQueue; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index af52d42a31e058..22457781f2bf52 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm @@ -23,73 +23,85 @@ #include #include -// BDX #include +#include +#include #include -#include // For InteractionModelEngine::GetInstance()->GetExchangeManager(); -#include // For &DeviceLayer::SystemLayer() -// BDX - using namespace chip; using namespace chip::app; using namespace chip::app::Clusters::OtaSoftwareUpdateProvider; +using namespace chip::bdx; // TODO Expose a method onto the delegate to make that configurable. constexpr uint32_t kMaxBdxBlockSize = 1024; +constexpr uint32_t kMaxBDXURILen = 256; constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); // OTA Spec mandates >= 5 minutes constexpr System::Clock::Timeout kBdxPollIntervalMs = System::Clock::Milliseconds32(50); constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kSender; class BdxOTASender : public bdx::Responder { public: - BdxOTASender() {} + BdxOTASender() {}; - CHIP_ERROR Start(FabricIndex fabricIndex, NodeId nodeId) + CHIP_ERROR PrepareForTransfer(FabricIndex fabricIndex, NodeId nodeId) { - if (mInitialized) { - VerifyOrReturnError(mFabricIndex.HasValue() && mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); - // Prevent a new node connection since another is active - VerifyOrReturnError(mFabricIndex.Value() == fabricIndex && mNodeId.Value() == nodeId, CHIP_ERROR_BUSY); + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); - // Reset stale connection from the Same Node if exists - Reset(); - } - mInitialized = true; + BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); + return Responder::PrepareForTransfer(mSystemLayer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout, kBdxPollIntervalMs); + } - mFabricIndex.SetValue(fabricIndex); - mNodeId.SetValue(nodeId); + CHIP_ERROR Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeMgr) + { + VerifyOrReturnError(mSystemLayer == nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(systemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(exchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); - BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); - // TODO Have a better mechanism to remove the need from getting an instance of the system layer here. - return PrepareForTransfer(&DeviceLayer::SystemLayer(), kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout, kBdxPollIntervalMs); + exchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, this); + + mSystemLayer = systemLayer; + mExchangeMgr = exchangeMgr; + mWorkQueue = DeviceLayer::PlatformMgrImpl().GetWorkQueue(); + + return CHIP_NO_ERROR; + } + + CHIP_ERROR Shutdown() + { + VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); + + mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); + + mExchangeMgr = nullptr; + mSystemLayer = nullptr; + mWorkQueue = nil; + + ResetState(); + + return CHIP_NO_ERROR; } - void SetDelegate(id delegate, dispatch_queue_t queue) + void SetDelegate(id delegate) { - // TODO Have a better mechanism to retrieve the exchange manager instance - // In order to register ourself as a protocol handler for BDX, it needs to be a reference - // to the exchange manager instance. That's not ideal but the reference is retrieved - // from the interaction model engine instance. - auto exchangeMgr = InteractionModelEngine::GetInstance()->GetExchangeManager(); - if (delegate && queue) { + if (delegate) { mDelegate = delegate; - mDelegateQueue = queue; - mWorkQueue = DeviceLayer::PlatformMgrImpl().GetWorkQueue(); - exchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, this); } else { - Reset(); - exchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); + ResetState(); } } private: - CHIP_ERROR OnMessageToSend(bdx::TransferSession::OutputEvent & event) + CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event) { VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mDelegateQueue != nil, CHIP_ERROR_INCORRECT_STATE); Messaging::SendFlags sendFlags; @@ -103,7 +115,7 @@ CHIP_ERROR OnMessageToSend(bdx::TransferSession::OutputEvent & event) return mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); } - CHIP_ERROR OnTransferSessionBegin(bdx::TransferSession::OutputEvent & event) + CHIP_ERROR OnTransferSessionBegin(TransferSession::OutputEvent & event) { uint16_t fdl = 0; auto fd = mTransfer.GetFileDesignator(fdl); @@ -122,7 +134,7 @@ CHIP_ERROR OnTransferSessionBegin(bdx::TransferSession::OutputEvent & event) // bdx::TransferSession will automatically reject a transfer if there are no // common supported control modes. It will also default to the smaller // block size. - bdx::TransferSession::TransferAcceptData acceptData; + TransferSession::TransferAcceptData acceptData; acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive; acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); acceptData.StartOffset = mTransfer.GetStartOffset(); @@ -132,38 +144,39 @@ CHIP_ERROR OnTransferSessionBegin(bdx::TransferSession::OutputEvent & event) }); }; - dispatch_async(mDelegateQueue, ^{ - [mDelegate handleBDXTransferSessionBegin:fileDesignator offset:offset completionHandler:completionHandler]; + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleBDXTransferSessionBegin:fileDesignator offset:offset completionHandler:completionHandler]; }); return CHIP_NO_ERROR; } - CHIP_ERROR OnTransferSessionEnd(bdx::TransferSession::OutputEvent & event) + CHIP_ERROR OnTransferSessionEnd(TransferSession::OutputEvent & event) { - CHIP_ERROR error = CHIP_ERROR_INTERNAL; - if (event.EventType == bdx::TransferSession::OutputEventType::kAckEOFReceived) { - error = CHIP_NO_ERROR; - } else if (event.EventType == bdx::TransferSession::OutputEventType::kTransferTimeout) { + CHIP_ERROR error = CHIP_NO_ERROR; + if (event.EventType == TransferSession::OutputEventType::kTransferTimeout) { error = CHIP_ERROR_TIMEOUT; + } else if (event.EventType != TransferSession::OutputEventType::kAckEOFReceived) { + error = CHIP_ERROR_INTERNAL; } - auto delegate = mDelegate; // mDelegate will be set to nil by Reset, so get a strong ref to it. - dispatch_async(mDelegateQueue, ^{ - [delegate handleBDXTransferSessionEnd:[MTRError errorForCHIPErrorCode:error]]; + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleBDXTransferSessionEnd:[MTRError errorForCHIPErrorCode:error]]; }); - Reset(); + ResetState(); return CHIP_NO_ERROR; } - CHIP_ERROR OnBlockQuery(bdx::TransferSession::OutputEvent & event) + CHIP_ERROR OnBlockQuery(TransferSession::OutputEvent & event) { auto blockSize = @(mTransfer.GetTransferBlockSize()); auto blockIndex = @(mTransfer.GetNextBlockNum()); auto bytesToSkip = @(0); - if (event.EventType == bdx::TransferSession::OutputEventType::kQueryWithSkipReceived) { + if (event.EventType == TransferSession::OutputEventType::kQueryWithSkipReceived) { bytesToSkip = @(event.bytesToSkip.BytesToSkip); } @@ -174,7 +187,7 @@ CHIP_ERROR OnBlockQuery(bdx::TransferSession::OutputEvent & event) return; } - bdx::TransferSession::BlockData blockData; + TransferSession::BlockData blockData; blockData.Data = static_cast([data bytes]); blockData.Length = static_cast([data length]); blockData.IsEof = isEOF; @@ -189,44 +202,47 @@ CHIP_ERROR OnBlockQuery(bdx::TransferSession::OutputEvent & event) // TODO Handle MaxLength - dispatch_async(mDelegateQueue, ^{ - [mDelegate handleBDXQuery:blockSize blockIndex:blockIndex bytesToSkip:bytesToSkip completionHandler:completionHandler]; + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleBDXQuery:blockSize + blockIndex:blockIndex + bytesToSkip:bytesToSkip + completionHandler:completionHandler]; }); return CHIP_NO_ERROR; } - void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) override + void HandleTransferSessionOutput(TransferSession::OutputEvent & event) override { VerifyOrReturn(mDelegate != nil); - VerifyOrReturn(mDelegateQueue != nil); CHIP_ERROR err = CHIP_NO_ERROR; switch (event.EventType) { - case bdx::TransferSession::OutputEventType::kInitReceived: + case TransferSession::OutputEventType::kInitReceived: err = OnTransferSessionBegin(event); break; - case bdx::TransferSession::OutputEventType::kStatusReceived: + case TransferSession::OutputEventType::kStatusReceived: ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); [[fallthrough]]; - case bdx::TransferSession::OutputEventType::kAckEOFReceived: - case bdx::TransferSession::OutputEventType::kInternalError: - case bdx::TransferSession::OutputEventType::kTransferTimeout: + case TransferSession::OutputEventType::kAckEOFReceived: + case TransferSession::OutputEventType::kInternalError: + case TransferSession::OutputEventType::kTransferTimeout: err = OnTransferSessionEnd(event); break; - case bdx::TransferSession::OutputEventType::kQueryWithSkipReceived: - case bdx::TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: err = OnBlockQuery(event); break; - case bdx::TransferSession::OutputEventType::kMsgToSend: + case TransferSession::OutputEventType::kMsgToSend: err = OnMessageToSend(event); break; - case bdx::TransferSession::OutputEventType::kNone: - case bdx::TransferSession::OutputEventType::kAckReceived: + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kAckReceived: // Nothing to do. break; - case bdx::TransferSession::OutputEventType::kAcceptReceived: - case bdx::TransferSession::OutputEventType::kBlockReceived: + case TransferSession::OutputEventType::kAcceptReceived: + case TransferSession::OutputEventType::kBlockReceived: default: // Should never happens. chipDie(); @@ -235,20 +251,39 @@ void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) over LogErrorOnFailure(err); } - void Reset() + CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) + { + if (mInitialized) { + // Prevent a new node connection since another is active. + VerifyOrReturnError(mFabricIndex.Value() == fabricIndex && mNodeId.Value() == nodeId, CHIP_ERROR_BUSY); + + // Reset stale connection from the same Node if exists. + ResetState(); + } + + mFabricIndex.SetValue(fabricIndex); + mNodeId.SetValue(nodeId); + + mInitialized = true; + + return CHIP_NO_ERROR; + } + + void ResetState() { + if (!mInitialized) { + return; + } + mFabricIndex.ClearValue(); mNodeId.ClearValue(); mTransfer.Reset(); + if (mExchangeCtx != nullptr) { mExchangeCtx->Close(); mExchangeCtx = nullptr; } - mDelegate = nil; - mDelegateQueue = nil; - mWorkQueue = nil; - mInitialized = false; } @@ -256,80 +291,100 @@ void Reset() Optional mFabricIndex; Optional mNodeId; id mDelegate = nil; - dispatch_queue_t mDelegateQueue = nil; dispatch_queue_t mWorkQueue = nil; + Messaging::ExchangeManager * mExchangeMgr = nullptr; }; BdxOTASender gOtaSender; static NSInteger const kOtaProviderEndpoint = 0; -MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge(void) - : mDelegate(nil) +MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge(id delegate) + : mDelegate(delegate) + , mWorkQueue(DeviceLayer::PlatformMgrImpl().GetWorkQueue()) { + gOtaSender.SetDelegate(delegate); + Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); } -MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge(void) {} - -void MTROTAProviderDelegateBridge::setDelegate(id delegate, dispatch_queue_t queue) +MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge() { - mDelegate = delegate ?: nil; - mQueue = queue ?: nil; + gOtaSender.SetDelegate(nil); + Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr); +} - gOtaSender.SetDelegate(delegate, queue); - Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); +void MTROTAProviderDelegateBridge::Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeManager) +{ + gOtaSender.Init(systemLayer, exchangeManager); } +void MTROTAProviderDelegateBridge::Shutdown() { gOtaSender.Shutdown(); } + void MTROTAProviderDelegateBridge::HandleQueryImage( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::QueryImage::DecodableType & commandData) { - id strongDelegate = mDelegate; - if (strongDelegate && mQueue) { - auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterQueryImageParams alloc] init]; - CHIP_ERROR err = ConvertToQueryImageParams(commandData, commandParams); - if (err != CHIP_NO_ERROR) { - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); - return; - } + auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterQueryImageParams alloc] init]; + CHIP_ERROR err = ConvertToQueryImageParams(commandData, commandParams); + if (err != CHIP_NO_ERROR) { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); + return; + } + + // Make sure to hold on to the command handler and command path to be used in the completion block + __block CommandHandler::Handle handle(commandObj); + __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId); + + auto completionHandler = ^( + MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) { + dispatch_async(mWorkQueue, ^{ + CommandHandler * handler = handle.Get(); + VerifyOrReturn(handler != nullptr); + + Commands::QueryImageResponse::Type response; + ConvertFromQueryImageResponseParms(data, response); + + auto hasUpdate = [data.status isEqual:@(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)]; + auto isBDXProtocolSupported = + [commandParams.protocolsSupported containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)]; - // Make sure to hold on to the command handler and command path to be used in the completion block - __block CommandHandler::Handle handle(commandObj); - __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId); - - dispatch_async(mQueue, ^{ - [strongDelegate handleQueryImage:commandParams - completionHandler:^(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, - NSError * _Nullable error) { - dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ - Commands::QueryImageResponse::Type response; - ConvertFromQueryImageResponseParms(data, response); - - CommandHandler * handler = handle.Get(); - if (handler) { - auto hasUpdate = - [data.status isEqual:@(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)]; - auto isBDXProtocolSupported = [commandParams.protocolsSupported - containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)]; - - if (hasUpdate && isBDXProtocolSupported) { - auto fabricIndex = handler->GetSubjectDescriptor().fabricIndex; - auto nodeId = handler->GetSubjectDescriptor().subject; - CHIP_ERROR err = gOtaSender.Start(fabricIndex, nodeId); - if (CHIP_NO_ERROR != err) { - LogErrorOnFailure(err); - handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure); - handle.Release(); - return; - } - } - - handler->AddResponse(cachedCommandPath, response); - handle.Release(); - } - }); - }]; + if (hasUpdate && isBDXProtocolSupported) { + auto fabricIndex = handler->GetSubjectDescriptor().fabricIndex; + auto nodeId = handler->GetSubjectDescriptor().subject; + CHIP_ERROR err = gOtaSender.PrepareForTransfer(fabricIndex, nodeId); + if (CHIP_NO_ERROR != err) { + LogErrorOnFailure(err); + handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure); + handle.Release(); + return; + } + + auto targetNodeId = handler->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId(); + + char uriBuffer[kMaxBDXURILen]; + MutableCharSpan uri(uriBuffer); + err = bdx::MakeURI(targetNodeId.GetNodeId(), CharSpan::fromCharString([data.imageURI UTF8String]), uri); + if (CHIP_NO_ERROR != err) { + LogErrorOnFailure(err); + handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure); + handle.Release(); + return; + } + + response.imageURI.SetValue(uri); + handler->AddResponse(cachedCommandPath, response); + handle.Release(); + return; + } + + handler->AddResponse(cachedCommandPath, response); + handle.Release(); }); - } + }; + + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleQueryImage:commandParams completionHandler:completionHandler]; + }); } void MTROTAProviderDelegateBridge::HandleApplyUpdateRequest(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, @@ -339,29 +394,26 @@ void Reset() __block CommandHandler::Handle handle(commandObj); __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId); - id strongDelegate = mDelegate; - if (strongDelegate && mQueue) { - auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams alloc] init]; - ConvertToApplyUpdateRequestParams(commandData, commandParams); - - dispatch_async(mQueue, ^{ - [strongDelegate - handleApplyUpdateRequest:commandParams - completionHandler:^(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, - NSError * _Nullable error) { - dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ - Commands::ApplyUpdateResponse::Type response; - ConvertFromApplyUpdateRequestResponseParms(data, response); - - CommandHandler * handler = handle.Get(); - if (handler) { - handler->AddResponse(cachedCommandPath, response); - handle.Release(); - } - }); - }]; - }); - } + auto completionHandler + = ^(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, NSError * _Nullable error) { + dispatch_async(mWorkQueue, ^{ + CommandHandler * handler = handle.Get(); + VerifyOrReturn(handler != nullptr); + + Commands::ApplyUpdateResponse::Type response; + ConvertFromApplyUpdateRequestResponseParms(data, response); + handler->AddResponse(cachedCommandPath, response); + handle.Release(); + }); + }; + + auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams alloc] init]; + ConvertToApplyUpdateRequestParams(commandData, commandParams); + + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleApplyUpdateRequest:commandParams completionHandler:completionHandler]; + }); } void MTROTAProviderDelegateBridge::HandleNotifyUpdateApplied(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, @@ -371,24 +423,23 @@ void Reset() __block CommandHandler::Handle handle(commandObj); __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId); - id strongDelegate = mDelegate; - if (strongDelegate && mQueue) { - auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams alloc] init]; - ConvertToNotifyUpdateAppliedParams(commandData, commandParams); - - dispatch_async(mQueue, ^{ - [strongDelegate handleNotifyUpdateApplied:commandParams - completionHandler:^(NSError * _Nullable error) { - dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ - CommandHandler * handler = handle.Get(); - if (handler) { - handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Success); - handle.Release(); - } - }); - }]; + auto completionHandler = ^(NSError * _Nullable error) { + dispatch_async(mWorkQueue, ^{ + CommandHandler * handler = handle.Get(); + VerifyOrReturn(handler != nullptr); + + handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Success); + handle.Release(); }); - } + }; + + auto * commandParams = [[MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams alloc] init]; + ConvertToNotifyUpdateAppliedParams(commandData, commandParams); + + auto strongDelegate = mDelegate; + dispatch_async(mWorkQueue, ^{ + [strongDelegate handleNotifyUpdateApplied:commandParams completionHandler:completionHandler]; + }); } CHIP_ERROR MTROTAProviderDelegateBridge::ConvertToQueryImageParams(