From 73c6d9a6fc8bfd72801aaed1a9738e9f979cc48b Mon Sep 17 00:00:00 2001 From: Nivedita Sarkar Date: Thu, 21 Sep 2023 00:01:34 -0700 Subject: [PATCH] Add AsyncFacilitator to enable an async, event-driven, non-polling mechanism for facilitating BDX transfers - Currently BDX transfers can only use the TransferFacilitator which polls the BDXTransferSession with a poll interval that adds unneccessary delays and is not the ideal approach to handle BDX transfers - Add support for an AsyncTrasferFacilitator that uses a non-polling, event-driven mechanism based on BDX messages received over the exchange context and gets the next output event from the transfer session to drive the BDX transfer - Add support for an AsyncResponder that uses the AsyncTrasferFacilitator and enables a provider delegate to handle BDX transfers - Modify the darwin OTA provider code to use the AsyncResponder - Add support for handling multiple OTA requests to the ota provider by creating an MTROTAImageTransferHandler object that handles a BDX transfer session independently from a singleton unsolicited BDX message handler that is created by the MTROTAProviderDelegateBridge. - Support only one BDX transfer session and return busy to any query images coming from other nodes for now to match the test expectations (to be removed once tests for multiple OTA are in place) --- .../CHIP/MTRDeviceControllerFactory.mm | 16 +- .../CHIP/MTROTAImageTransferHandler.h | 73 +++ .../CHIP/MTROTAImageTransferHandler.mm | 434 +++++++++++++++ .../CHIP/MTROTAProviderDelegateBridge.h | 16 +- .../CHIP/MTROTAProviderDelegateBridge.mm | 522 ++---------------- .../CHIP/MTROTAUnsolicitedBDXMessageHandler.h | 60 ++ .../MTROTAUnsolicitedBDXMessageHandler.mm | 74 +++ .../Matter.xcodeproj/project.pbxproj | 16 + .../bdx/AsyncTransferFacilitator.cpp | 154 ++++++ src/protocols/bdx/AsyncTransferFacilitator.h | 108 ++++ src/protocols/bdx/BUILD.gn | 2 + src/protocols/bdx/BdxTransferSession.cpp | 7 +- src/protocols/bdx/BdxTransferSession.h | 22 + 13 files changed, 996 insertions(+), 508 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h create mode 100644 src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm create mode 100644 src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h create mode 100644 src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm create mode 100644 src/protocols/bdx/AsyncTransferFacilitator.cpp create mode 100644 src/protocols/bdx/AsyncTransferFacilitator.h diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index 21660828e25847..f7d708eb571e26 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -79,6 +79,11 @@ @interface MTRDeviceControllerFactory () @property (atomic, readonly) dispatch_queue_t chipWorkQueue; @property (readonly) DeviceControllerFactory * controllerFactory; @property (readonly) PersistentStorageDelegate * persistentStorageDelegate; + +// This is the OTA provider cluster delegate that would handle the requests to the OTA provider cluster +// itself like QueryImage, HandleUpdate and NotifyUpdateApplied. All BDX related handlers will be +// not handled here since each BDX session will have its own delegate to handle all BDX messages +// for that session. @property (readonly) MTROTAProviderDelegateBridge * otaProviderDelegateBridge; @property (readonly) Crypto::RawKeySessionKeystore * sessionKeystore; // We use TestPersistentStorageDelegate just to get an in-memory store to back @@ -138,6 +143,7 @@ - (BOOL)findMatchingFabric:(FabricTable &)fabricTable fabric:(const FabricInfo * _Nullable * _Nonnull)fabric; - (MTRDeviceController * _Nullable)maybeInitializeOTAProvider:(MTRDeviceController * _Nonnull)controller; + @end @interface MTRDeviceControllerFactoryParams () @@ -901,7 +907,7 @@ - (MTRDeviceController * _Nullable)maybeInitializeOTAProvider:(MTRDeviceControll __block CHIP_ERROR err; dispatch_sync(_chipWorkQueue, ^{ auto systemState = _controllerFactory->GetSystemState(); - err = _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr()); + err = _otaProviderDelegateBridge->Init(systemState->ExchangeMgr()); }); if (CHIP_NO_ERROR != err) { MTR_LOG_ERROR("Failed to init provider delegate bridge: %" CHIP_ERROR_FORMAT, err.Format()); @@ -975,10 +981,6 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller // down most of the world. DeviceLayer::PlatformMgrImpl().StopEventLoopTask(); - if (_otaProviderDelegateBridge) { - _otaProviderDelegateBridge->Shutdown(); - } - sharedCleanupBlock(); // Now that our per-controller storage for the controller being shut @@ -999,10 +1001,6 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller } else { // Do the controller shutdown on the Matter work queue. dispatch_sync(_chipWorkQueue, ^{ - if (_otaProviderDelegateBridge) { - _otaProviderDelegateBridge->ControllerShuttingDown(controller); - } - sharedCleanupBlock(); // Now that our per-controller storage for the controller being shut diff --git a/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h new file mode 100644 index 00000000000000..ee06858553accf --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h @@ -0,0 +1,73 @@ +/** + * + * 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 + +#include + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class inherits from the AsyncResponder class and handles the BDX messages for a BDX transfer session. + * It overrides the HandleAsyncTransferSessionOutput virtual method and provides an implementation for it. + * + * For each BDX transfer, we will have an instance of MTROTAImageTransferHandler. + */ +class MTROTAImageTransferHandler : public chip::bdx::AsyncResponder +{ +public: + MTROTAImageTransferHandler(); + ~MTROTAImageTransferHandler(); + + void HandleAsyncTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; + +private: + CHIP_ERROR PrepareForTransfer(chip::Messaging::ExchangeContext * exchangeCtx, chip::FabricIndex fabricIndex, + chip::NodeId nodeId); + + void ResetState(); + + CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId); + + static void HandleBdxInitReceivedTimeoutExpired(chip::System::Layer * systemLayer, void * state); + + CHIP_ERROR OnMessageToSend(chip::bdx::TransferSession::OutputEvent & event); + + CHIP_ERROR OnTransferSessionBegin(chip::bdx::TransferSession::OutputEvent & event); + + CHIP_ERROR OnTransferSessionEnd(chip::bdx::TransferSession::OutputEvent & event); + + CHIP_ERROR OnBlockQuery(chip::bdx::TransferSession::OutputEvent & event); + + // Inherited from ExchangeContext + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; + + // The fabric index of the peer node. + chip::Optional mFabricIndex; + + // The node id of the peer node. + chip::Optional mNodeId; + + // The OTA provider delegate used by the controller. + id mDelegate = nil; + + // The OTA provider delegate queue used by the controller. + dispatch_queue_t mDelegateNotificationQueue = nil; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm new file mode 100644 index 00000000000000..1b939247b67d5b --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm @@ -0,0 +1,434 @@ +/** + * + * 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 "MTROTAImageTransferHandler.h" +#import "MTRDeviceControllerFactory_Internal.h" +#import "MTRDeviceController_Internal.h" +#import "MTROTAUnsolicitedBDXMessageHandler.h" +#import "NSStringSpanConversion.h" + +#include + +using namespace chip; +using namespace chip::bdx; +using namespace chip::app; + +// TODO Expose a method onto the delegate to make that configurable. +constexpr uint32_t kMaxBdxBlockSize = 1024; + +// Since the BDX timeout is 5 minutes and we are starting this after query image is available and before the BDX init comes, +// we just double the timeout to give enough time for the BDX init to come in a reasonable amount of time. +constexpr System::Clock::Timeout kBdxInitReceivedTimeout = System::Clock::Seconds16(10 * 60); + +constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); // OTA Spec mandates >= 5 minutes +constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kSender; + +MTROTAImageTransferHandler::MTROTAImageTransferHandler() +{ + // Increment the number of delegates by 1. + MTROTAUnsolicitedBDXMessageHandler::IncrementNumberOfDelegates(); +} + +CHIP_ERROR MTROTAImageTransferHandler::PrepareForTransfer( + Messaging::ExchangeContext * exchangeCtx, FabricIndex fabricIndex, NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + + BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); + + return AsyncResponder::PrepareForTransfer(exchangeCtx, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); +} + +MTROTAImageTransferHandler::~MTROTAImageTransferHandler() { ResetState(); } + +void MTROTAImageTransferHandler::ResetState() +{ + assertChipStackLockedByCurrentThread(); + if (mNodeId.HasValue() && mFabricIndex.HasValue()) { + ChipLogProgress(Controller, + "Resetting state for OTA Provider; no longer providing an update for node id 0x" ChipLogFormatX64 ", fabric index %u", + ChipLogValueX64(mNodeId.Value()), mFabricIndex.Value()); + } else { + ChipLogProgress(Controller, "Resetting state for OTA Provider"); + } + chip::DeviceLayer::SystemLayer().CancelTimer(HandleBdxInitReceivedTimeoutExpired, this); + + AsyncResponder::ResetTransfer(); + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + mDelegate = nil; + mDelegateNotificationQueue = nil; + MTROTAUnsolicitedBDXMessageHandler::DecrementNumberOfDelegates(); +} +/** + * Timer callback called when we don't receive a BDX init within a reasonable time after a successful QueryImage response. + */ +void MTROTAImageTransferHandler::HandleBdxInitReceivedTimeoutExpired(chip::System::Layer * systemLayer, void * state) +{ + VerifyOrReturn(state != nullptr); + static_cast(state)->ResetState(); +} + +CHIP_ERROR MTROTAImageTransferHandler::OnMessageToSend(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(AsyncTransferFacilitator::GetExchangeContext() != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); + + Messaging::SendFlags sendFlags; + + // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and + // the end of the transfer. + if (!event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { + sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); + } + + auto & msgTypeData = event.msgTypeData; + ChipLogError(BDX, "OnMessageToSend msgTypeData.MessageType = %hu", msgTypeData.MessageType); + Messaging::ExchangeContext * ec = AsyncTransferFacilitator::GetExchangeContext(); + VerifyOrReturnError(ec != nullptr, CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = CHIP_NO_ERROR; + if (ec != nullptr) { + // If there's an error sending the message, call ResetState. + err = ec->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); + if (err != CHIP_NO_ERROR) { + ResetState(); + } else if (event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { + // If the send was successful for a status report, since we are not expecting a response the exchange context is + // already closed. We need to null out the reference to avoid having a dangling pointer. + ec = nullptr; + ResetState(); + } + } + return err; +} + +CHIP_ERROR MTROTAImageTransferHandler::OnTransferSessionBegin(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + // Once we receive the BDX init, cancel the BDX Init timeout and start the BDX session + chip::DeviceLayer::SystemLayer().CancelTimer(HandleBdxInitReceivedTimeoutExpired, this); + + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + uint16_t fdl = 0; + auto fd = mTransfer.GetFileDesignator(fdl); + VerifyOrReturnError(fdl <= bdx::kMaxFileDesignatorLen, CHIP_ERROR_INVALID_ARGUMENT); + CharSpan fileDesignatorSpan(Uint8::to_const_char(fd), fdl); + + auto fileDesignator = AsString(fileDesignatorSpan); + if (fileDesignator == nil) { + return CHIP_ERROR_INCORRECT_STATE; + } + + auto offset = @(mTransfer.GetStartOffset()); + + auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; + VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); + + auto completionHandler = ^(NSError * _Nullable error) { + [controller + asyncDispatchToMatterQueue:^() { + assertChipStackLockedByCurrentThread(); + + if (error != nil) { + CHIP_ERROR err = [MTRError errorToCHIPErrorCode:error]; + LogErrorOnFailure(err); + ResetState(); + AsyncResponder::NotifyEventHandledWithError(err); + return; + } + + // bdx::TransferSession will automatically reject a transfer if there are no + // common supported control modes. It will also default to the smaller + // block size. + TransferSession::TransferAcceptData acceptData; + acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive; + acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); + acceptData.StartOffset = mTransfer.GetStartOffset(); + acceptData.Length = mTransfer.GetTransferLength(); + + LogErrorOnFailure(mTransfer.AcceptTransfer(acceptData)); + AsyncResponder::NotifyEventHandledWithError([MTRError errorToCHIPErrorCode:error]); + return; + } + errorHandler:^(NSError *) { + // Not much we can do here + }]; + }; + + auto nodeId = @(mNodeId.Value()); + + auto strongDelegate = mDelegate; + auto delagateQueue = mDelegateNotificationQueue; + if (strongDelegate == nil || delagateQueue == nil) { + LogErrorOnFailure(CHIP_ERROR_INCORRECT_STATE); + AsyncResponder::NotifyEventHandledWithError(CHIP_ERROR_INCORRECT_STATE); + return CHIP_ERROR_INCORRECT_STATE; + } + + dispatch_async(delagateQueue, ^{ + if ([strongDelegate respondsToSelector:@selector + (handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)]) { + [strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId + controller:controller + fileDesignator:fileDesignator + offset:offset + completion:completionHandler]; + } else { + [strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId + controller:controller + fileDesignator:fileDesignator + offset:offset + completionHandler:completionHandler]; + } + }); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTROTAImageTransferHandler::OnTransferSessionEnd(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR error = CHIP_NO_ERROR; + if (event.EventType == TransferSession::OutputEventType::kTransferTimeout) { + error = CHIP_ERROR_TIMEOUT; + } else if (event.EventType != TransferSession::OutputEventType::kAckEOFReceived + || event.EventType != TransferSession::OutputEventType::kAckEOFReceived) { + error = CHIP_ERROR_INTERNAL; + } + + auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; + VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); + auto nodeId = @(mNodeId.Value()); + + auto strongDelegate = mDelegate; + auto delagateQueue = mDelegateNotificationQueue; + if (strongDelegate == nil || delagateQueue == nil) { + LogErrorOnFailure(CHIP_ERROR_INCORRECT_STATE); + AsyncResponder::NotifyEventHandledWithError(CHIP_ERROR_INCORRECT_STATE); + return CHIP_ERROR_INCORRECT_STATE; + } + + if ([strongDelegate respondsToSelector:@selector(handleBDXTransferSessionEndForNodeID:controller:error:)]) { + dispatch_async(delagateQueue, ^{ + [strongDelegate handleBDXTransferSessionEndForNodeID:nodeId + controller:controller + error:[MTRError errorForCHIPErrorCode:error]]; + }); + } + + ResetState(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTROTAImageTransferHandler::OnBlockQuery(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + auto blockSize = @(mTransfer.GetTransferBlockSize()); + auto blockIndex = @(mTransfer.GetNextBlockNum()); + + auto bytesToSkip = @(0); + if (event.EventType == TransferSession::OutputEventType::kQueryWithSkipReceived) { + bytesToSkip = @(event.bytesToSkip.BytesToSkip); + } + + auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; + VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); + + auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) { + [controller + asyncDispatchToMatterQueue:^() { + assertChipStackLockedByCurrentThread(); + + if (data == nil) { + AsyncResponder::NotifyEventHandledWithError(CHIP_ERROR_INCORRECT_STATE); + return; + } + + TransferSession::BlockData blockData; + blockData.Data = static_cast([data bytes]); + blockData.Length = static_cast([data length]); + blockData.IsEof = isEOF; + + CHIP_ERROR err = mTransfer.PrepareBlock(blockData); + if (CHIP_NO_ERROR != err) { + LogErrorOnFailure(err); + } + AsyncResponder::NotifyEventHandledWithError(err); + } + errorHandler:^(NSError *) { + // Not much we can do here + }]; + }; + + // TODO Handle MaxLength + + auto nodeId = @(mNodeId.Value()); + + auto strongDelegate = mDelegate; + auto delagateQueue = mDelegateNotificationQueue; + if (strongDelegate == nil || delagateQueue == nil) { + LogErrorOnFailure(CHIP_ERROR_INCORRECT_STATE); + AsyncResponder::NotifyEventHandledWithError(CHIP_ERROR_INCORRECT_STATE); + return CHIP_ERROR_INCORRECT_STATE; + } + + dispatch_async(delagateQueue, ^{ + if ([strongDelegate respondsToSelector:@selector(handleBDXQueryForNodeID: + controller:blockSize:blockIndex:bytesToSkip:completion:)]) { + [strongDelegate handleBDXQueryForNodeID:nodeId + controller:controller + blockSize:blockSize + blockIndex:blockIndex + bytesToSkip:bytesToSkip + completion:completionHandler]; + } else { + [strongDelegate handleBDXQueryForNodeID:nodeId + controller:controller + blockSize:blockSize + blockIndex:blockIndex + bytesToSkip:bytesToSkip + completionHandler:completionHandler]; + } + }); + + return CHIP_NO_ERROR; +} + +void MTROTAImageTransferHandler::HandleAsyncTransferSessionOutput(TransferSession::OutputEvent & event) +{ + VerifyOrReturn(mDelegate != nil); + + CHIP_ERROR err = CHIP_NO_ERROR; + switch (event.EventType) { + case TransferSession::OutputEventType::kInitReceived: + err = OnTransferSessionBegin(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandledWithError(err); + ResetState(); + } + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + [[fallthrough]]; + case TransferSession::OutputEventType::kAckEOFReceived: + case TransferSession::OutputEventType::kInternalError: + case TransferSession::OutputEventType::kTransferTimeout: + err = OnTransferSessionEnd(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandledWithError(err); + ResetState(); + } + break; + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: + err = OnBlockQuery(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandledWithError(err); + ResetState(); + } + break; + case TransferSession::OutputEventType::kMsgToSend: + err = OnMessageToSend(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandledWithError(err); + ResetState(); + } + break; + case TransferSession::OutputEventType::kNone: + case TransferSession::OutputEventType::kAckReceived: + // Nothing to do. + break; + case TransferSession::OutputEventType::kAcceptReceived: + case TransferSession::OutputEventType::kBlockReceived: + default: + // Should never happens. + chipDie(); + break; + } + LogErrorOnFailure(err); +} + +CHIP_ERROR MTROTAImageTransferHandler::ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + + auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:fabricIndex]; + VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); + + mDelegate = controller.otaProviderDelegate; + mDelegateNotificationQueue = controller.otaProviderDelegateQueue; + + // We should have already checked that this controller supports OTA. + VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mDelegateNotificationQueue != nil, CHIP_ERROR_INCORRECT_STATE); + + // Start a timer to track whether we receive a BDX init after a successful query image in a reasonable amount of time + CHIP_ERROR err + = chip::DeviceLayer::SystemLayer().StartTimer(kBdxInitReceivedTimeout, HandleBdxInitReceivedTimeoutExpired, this); + LogErrorOnFailure(err); + + ReturnErrorOnFailure(err); + + mFabricIndex.SetValue(fabricIndex); + mNodeId.SetValue(nodeId); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTROTAImageTransferHandler::OnMessageReceived( + chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, chip::System::PacketBufferHandle && payload) +{ + VerifyOrReturnError(ec != nullptr, CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR err; + ChipLogProgress(BDX, "%s: message " ChipLogFormatMessageType " protocol " ChipLogFormatProtocolId, __FUNCTION__, + payloadHeader.GetMessageType(), ChipLogValueProtocolId(payloadHeader.GetProtocolID())); + + // If we receive a ReceiveInit message, then we prepare for transfer + if (payloadHeader.HasMessageType(MessageType::ReceiveInit)) { + NodeId nodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); + FabricIndex fabricIndex = ec->GetSessionHandle()->GetFabricIndex(); + + if (nodeId != kUndefinedNodeId && fabricIndex != kUndefinedFabricIndex) { + err = PrepareForTransfer(ec, fabricIndex, nodeId); + if (err != CHIP_NO_ERROR) { + ChipLogError(Controller, "Failed to prepare for transfer for BDX"); + } + } + } + + // Send the message to the AsyncFacilitator to drive the BDX session state machine + AsyncTransferFacilitator::OnMessageReceived(ec, payloadHeader, std::move(payload)); + return err; +} diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h index f74fb32a08bd19..63fdc822fedfd0 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022 - 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. @@ -15,6 +15,7 @@ * limitations under the License. */ +#import "MTROTAUnsolicitedBDXMessageHandler.h" #import #include @@ -27,15 +28,7 @@ class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDele MTROTAProviderDelegateBridge(); ~MTROTAProviderDelegateBridge(); - CHIP_ERROR Init(chip::System::Layer * systemLayer, chip::Messaging::ExchangeManager * exchangeManager); - - // Shutdown must be called after the event loop has been stopped, since it - // touches Matter objects. - void Shutdown(); - - // ControllerShuttingDown must be called on the Matter work queue, since it - // touches Matter objects. - void ControllerShuttingDown(MTRDeviceController * controller); + CHIP_ERROR Init(chip::Messaging::ExchangeManager * exchangeMgr); void HandleQueryImage( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, @@ -65,6 +58,9 @@ class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDele static void ConvertToNotifyUpdateAppliedParams( const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::DecodableType & commandData, MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams); + +protected: + MTROTAUnsolicitedBDXMessageHandler * sOtaUnsolicitedBDXMsgHandler = nullptr; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index 1b9f436751b2a5..48d88e92159c3c 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022 - 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. @@ -17,23 +17,15 @@ #import "MTROTAProviderDelegateBridge.h" #import "MTRBaseClusters.h" -#import "MTRCommandPayloadsObjC.h" #import "MTRDeviceControllerFactory_Internal.h" #import "MTRDeviceController_Internal.h" #import "NSDataSpanConversion.h" #import "NSStringSpanConversion.h" #include -#include -#include -#include -#include #include -#include -#include #include -#include using namespace chip; using namespace chip::app; @@ -41,14 +33,6 @@ using namespace chip::bdx; using Protocols::InteractionModel::Status; -// TODO Expose a method onto the delegate to make that configurable. -constexpr uint32_t kMaxBdxBlockSize = 1024; -constexpr uint32_t kMaxBDXURILen = 256; - -// Since the BDX timeout is 5 minutes and we are starting this after query image is available and before the BDX init comes, -// we just double the timeout to give enough time for the BDX init to come in a reasonable amount of time. -constexpr System::Clock::Timeout kBdxInitReceivedTimeout = System::Clock::Seconds16(10 * 60); - // Time in seconds after which the requestor should retry calling query image if // busy status is receieved. The spec minimum is 2 minutes, but in practice OTA // generally takes a lot longer than that and devices only retry a few times @@ -57,464 +41,42 @@ // OTA. constexpr uint32_t kDelayedActionTimeSeconds = 600; -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() {}; - - CHIP_ERROR PrepareForTransfer(FabricIndex fabricIndex, NodeId nodeId) - { - assertChipStackLockedByCurrentThread(); - - VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); - - ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); - - BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); - return Responder::PrepareForTransfer(mSystemLayer, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout, kBdxPollIntervalMs); - } - - CHIP_ERROR Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeMgr) - { - assertChipStackLockedByCurrentThread(); - - 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); - - exchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, this); - - mSystemLayer = systemLayer; - mExchangeMgr = exchangeMgr; - - return CHIP_NO_ERROR; - } - - CHIP_ERROR Shutdown() - { - assertChipStackLockedByCurrentThread(); - - VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); - - mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); - ResetState(); - - mExchangeMgr = nullptr; - mSystemLayer = nullptr; - - return CHIP_NO_ERROR; - } - - void ControllerShuttingDown(MTRDeviceController * controller) - { - assertChipStackLockedByCurrentThread(); - - if (mInitialized && mFabricIndex.Value() == controller.fabricIndex) { - ResetState(); - } - } - - void ResetState() - { - assertChipStackLockedByCurrentThread(); - if (mNodeId.HasValue() && mFabricIndex.HasValue()) { - ChipLogProgress(Controller, - "Resetting state for OTA Provider; no longer providing an update for node id 0x" ChipLogFormatX64 - ", fabric index %u", - ChipLogValueX64(mNodeId.Value()), mFabricIndex.Value()); - } else { - ChipLogProgress(Controller, "Resetting state for OTA Provider"); - } - if (mSystemLayer) { - mSystemLayer->CancelTimer(HandleBdxInitReceivedTimeoutExpired, this); - } - // TODO: Check if this can be removed. It seems like we can close the exchange context and reset transfer regardless. - if (!mInitialized) { - return; - } - Responder::ResetTransfer(); - ++mTransferGeneration; - mFabricIndex.ClearValue(); - mNodeId.ClearValue(); - - if (mExchangeCtx != nullptr) { - mExchangeCtx->Close(); - mExchangeCtx = nullptr; - } - - mDelegate = nil; - mDelegateNotificationQueue = nil; - - mInitialized = false; - } - -private: - /** - * Timer callback called when we don't receive a BDX init within a reasonable time after a successful QueryImage response. - */ - static void HandleBdxInitReceivedTimeoutExpired(chip::System::Layer * systemLayer, void * state) - { - VerifyOrReturn(state != nullptr); - static_cast(state)->ResetState(); - } - - CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event) - { - assertChipStackLockedByCurrentThread(); - - VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); - - Messaging::SendFlags sendFlags; - - // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and - // the end of the transfer. - if (!event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { - sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); - } - - auto & msgTypeData = event.msgTypeData; - // If there's an error sending the message, close the exchange and call ResetState. - // TODO: If we can remove the !mInitialized check in ResetState(), just calling ResetState() will suffice here. - CHIP_ERROR err - = mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags); - if (err != CHIP_NO_ERROR) { - mExchangeCtx->Close(); - mExchangeCtx = nullptr; - ResetState(); - } else if (event.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) { - // If the send was successful for a status report, since we are not expecting a response the exchange context is - // already closed. We need to null out the reference to avoid having a dangling pointer. - mExchangeCtx = nullptr; - ResetState(); - } - return err; - } - - bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err) - { - if (err == CHIP_ERROR_INCORRECT_STATE) { - return bdx::StatusCode::kUnexpectedMessage; - } - if (err == CHIP_ERROR_INVALID_ARGUMENT) { - return bdx::StatusCode::kBadMessageContents; - } - return bdx::StatusCode::kUnknown; - } - - CHIP_ERROR OnTransferSessionBegin(TransferSession::OutputEvent & event) - { - assertChipStackLockedByCurrentThread(); - // Once we receive the BDX init, cancel the BDX Init timeout and start the BDX session - if (mSystemLayer) { - mSystemLayer->CancelTimer(HandleBdxInitReceivedTimeoutExpired, this); - } - - VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); - uint16_t fdl = 0; - auto fd = mTransfer.GetFileDesignator(fdl); - VerifyOrReturnError(fdl <= bdx::kMaxFileDesignatorLen, CHIP_ERROR_INVALID_ARGUMENT); - CharSpan fileDesignatorSpan(Uint8::to_const_char(fd), fdl); - - auto fileDesignator = AsString(fileDesignatorSpan); - if (fileDesignator == nil) { - return CHIP_ERROR_INCORRECT_STATE; - } - - auto offset = @(mTransfer.GetStartOffset()); - - auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; - VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); - - auto transferGeneration = mTransferGeneration; - - auto completionHandler = ^(NSError * _Nullable error) { - [controller - asyncDispatchToMatterQueue:^() { - assertChipStackLockedByCurrentThread(); - - if (!mInitialized || mTransferGeneration != transferGeneration) { - // Callback for a stale transfer. - return; - } - - if (error != nil) { - CHIP_ERROR err = [MTRError errorToCHIPErrorCode:error]; - LogErrorOnFailure(err); - LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err))); - return; - } - - // bdx::TransferSession will automatically reject a transfer if there are no - // common supported control modes. It will also default to the smaller - // block size. - TransferSession::TransferAcceptData acceptData; - acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive; - acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); - acceptData.StartOffset = mTransfer.GetStartOffset(); - acceptData.Length = mTransfer.GetTransferLength(); - - LogErrorOnFailure(mTransfer.AcceptTransfer(acceptData)); - } - errorHandler:^(NSError *) { - // Not much we can do here - }]; - }; - - auto nodeId = @(mNodeId.Value()); - - auto strongDelegate = mDelegate; - dispatch_async(mDelegateNotificationQueue, ^{ - if ([strongDelegate respondsToSelector:@selector - (handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)]) { - [strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId - controller:controller - fileDesignator:fileDesignator - offset:offset - completion:completionHandler]; - } else { - [strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId - controller:controller - fileDesignator:fileDesignator - offset:offset - completionHandler:completionHandler]; - } - }); - - return CHIP_NO_ERROR; - } - - CHIP_ERROR OnTransferSessionEnd(TransferSession::OutputEvent & event) - { - assertChipStackLockedByCurrentThread(); - - VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); - - 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 * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; - VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); - auto nodeId = @(mNodeId.Value()); - - auto strongDelegate = mDelegate; - if ([strongDelegate respondsToSelector:@selector(handleBDXTransferSessionEndForNodeID:controller:error:)]) { - dispatch_async(mDelegateNotificationQueue, ^{ - [strongDelegate handleBDXTransferSessionEndForNodeID:nodeId - controller:controller - error:[MTRError errorForCHIPErrorCode:error]]; - }); - } - - ResetState(); - return CHIP_NO_ERROR; - } - - CHIP_ERROR OnBlockQuery(TransferSession::OutputEvent & event) - { - assertChipStackLockedByCurrentThread(); - - VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE); - - auto blockSize = @(mTransfer.GetTransferBlockSize()); - auto blockIndex = @(mTransfer.GetNextBlockNum()); - - auto bytesToSkip = @(0); - if (event.EventType == TransferSession::OutputEventType::kQueryWithSkipReceived) { - bytesToSkip = @(event.bytesToSkip.BytesToSkip); - } - - auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()]; - VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); - - auto transferGeneration = mTransferGeneration; - - auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) { - [controller - asyncDispatchToMatterQueue:^() { - assertChipStackLockedByCurrentThread(); - - if (!mInitialized || mTransferGeneration != transferGeneration) { - // Callback for a stale transfer. - return; - } - - if (data == nil) { - LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown)); - return; - } - - TransferSession::BlockData blockData; - blockData.Data = static_cast([data bytes]); - blockData.Length = static_cast([data length]); - blockData.IsEof = isEOF; - - CHIP_ERROR err = mTransfer.PrepareBlock(blockData); - if (CHIP_NO_ERROR != err) { - LogErrorOnFailure(err); - LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown)); - } - } - errorHandler:^(NSError *) { - // Not much we can do here - }]; - }; - - // TODO Handle MaxLength - - auto nodeId = @(mNodeId.Value()); - - auto strongDelegate = mDelegate; - dispatch_async(mDelegateNotificationQueue, ^{ - if ([strongDelegate respondsToSelector:@selector - (handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completion:)]) { - [strongDelegate handleBDXQueryForNodeID:nodeId - controller:controller - blockSize:blockSize - blockIndex:blockIndex - bytesToSkip:bytesToSkip - completion:completionHandler]; - } else { - [strongDelegate handleBDXQueryForNodeID:nodeId - controller:controller - blockSize:blockSize - blockIndex:blockIndex - bytesToSkip:bytesToSkip - completionHandler:completionHandler]; - } - }); - - return CHIP_NO_ERROR; - } - - void HandleTransferSessionOutput(TransferSession::OutputEvent & event) override - { - VerifyOrReturn(mDelegate != nil); - - CHIP_ERROR err = CHIP_NO_ERROR; - switch (event.EventType) { - case TransferSession::OutputEventType::kInitReceived: - err = OnTransferSessionBegin(event); - if (err != CHIP_NO_ERROR) { - LogErrorOnFailure(mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err))); - } - break; - case TransferSession::OutputEventType::kStatusReceived: - ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); - [[fallthrough]]; - case TransferSession::OutputEventType::kAckEOFReceived: - case TransferSession::OutputEventType::kInternalError: - case TransferSession::OutputEventType::kTransferTimeout: - err = OnTransferSessionEnd(event); - break; - case TransferSession::OutputEventType::kQueryWithSkipReceived: - case TransferSession::OutputEventType::kQueryReceived: - err = OnBlockQuery(event); - break; - case TransferSession::OutputEventType::kMsgToSend: - err = OnMessageToSend(event); - break; - case TransferSession::OutputEventType::kNone: - case TransferSession::OutputEventType::kAckReceived: - // Nothing to do. - break; - case TransferSession::OutputEventType::kAcceptReceived: - case TransferSession::OutputEventType::kBlockReceived: - default: - // Should never happens. - chipDie(); - break; - } - LogErrorOnFailure(err); - } - - CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) - { - assertChipStackLockedByCurrentThread(); - - 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(); - } - - auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:fabricIndex]; - VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); - - mDelegate = controller.otaProviderDelegate; - mDelegateNotificationQueue = controller.otaProviderDelegateQueue; - - // We should have already checked that this controller supports OTA. - VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mDelegateNotificationQueue != nil, CHIP_ERROR_INCORRECT_STATE); - - // Start a timer to track whether we receive a BDX init after a successful query image in a reasonable amount of time - CHIP_ERROR err = mSystemLayer->StartTimer(kBdxInitReceivedTimeout, HandleBdxInitReceivedTimeoutExpired, this); - LogErrorOnFailure(err); - - ReturnErrorOnFailure(err); - - mFabricIndex.SetValue(fabricIndex); - mNodeId.SetValue(nodeId); - - mInitialized = true; - - return CHIP_NO_ERROR; - } - - bool mInitialized = false; - Optional mFabricIndex; - Optional mNodeId; - id mDelegate = nil; - dispatch_queue_t mDelegateNotificationQueue = nil; - Messaging::ExchangeManager * mExchangeMgr = nullptr; - - // Since we are a singleton, we get reused across transfers, but also have - // async calls that can happen. The transfer generation keeps track of - // which transfer we are currently doing, so we can ignore async calls - // attached to no-longer-running transfers. - uint64_t mTransferGeneration = 0; -}; +constexpr uint32_t kMaxBDXURILen = 256; namespace { -BdxOTASender gOtaSender; - NSInteger const kOtaProviderEndpoint = 0; } // anonymous namespace -MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge() { Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); } +MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge() +{ + Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); + + // The singleton unsolicited BDX message handler which will create + // the MTROTAImageTransferHandler object for handling each BDX session. + sOtaUnsolicitedBDXMsgHandler = new MTROTAUnsolicitedBDXMessageHandler(); +} MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge() { - gOtaSender.ResetState(); Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr); + + sOtaUnsolicitedBDXMsgHandler = nullptr; } -CHIP_ERROR MTROTAProviderDelegateBridge::Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeManager) +CHIP_ERROR MTROTAProviderDelegateBridge::Init(Messaging::ExchangeManager * exchangeMgr) { - return gOtaSender.Init(systemLayer, exchangeManager); -} + assertChipStackLockedByCurrentThread(); -void MTROTAProviderDelegateBridge::Shutdown() { gOtaSender.Shutdown(); } + VerifyOrReturnError(exchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); -void MTROTAProviderDelegateBridge::ControllerShuttingDown(MTRDeviceController * controller) -{ - gOtaSender.ControllerShuttingDown(controller); + // Initialize the singleton MTROTAUnsolicitedBDXMessageHandler + VerifyOrReturnValue(sOtaUnsolicitedBDXMsgHandler != nil, CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = sOtaUnsolicitedBDXMsgHandler->Init(exchangeMgr); + if (err != CHIP_NO_ERROR) { + ChipLogError(Controller, "Failed to initialize the unsolicited BDX Message handler with err %s", err.AsString()); + } + return err; } namespace { @@ -617,7 +179,6 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath return; } - auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto ourNodeId = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId(); auto * commandParams = [[MTROTASoftwareUpdateProviderClusterQueryImageParams alloc] init]; @@ -674,42 +235,29 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath return; } - // If there is an update available, try to prepare for a transfer. - CHIP_ERROR err = gOtaSender.PrepareForTransfer(fabricIndex, nodeId); - if (CHIP_NO_ERROR != err) { + // If we already have a delegate, send busy error + if (MTROTAUnsolicitedBDXMessageHandler::GetNumberOfDelegates() >= 1) { - // Handle busy error separately as we have a query image response status that maps to busy - if (err == CHIP_ERROR_BUSY) { - ChipLogError( - Controller, "Responding with Busy due to being in the middle of handling another BDX transfer"); - Commands::QueryImageResponse::Type response; - response.status = static_cast(MTROTASoftwareUpdateProviderOTAQueryStatusBusy); - response.delayedActionTime.SetValue(delegateResponse.delayedActionTime.ValueOr(kDelayedActionTimeSeconds)); - handler->AddResponse(cachedCommandPath, response); - handle.Release(); - // We do not reset state when we get the busy error because that means we are locked in a BDX transfer - // session with another requestor when we get this query image request. We do not want to interrupt the - // ongoing transfer instead just respond to the second requestor with a busy status and a delayedActionTime - // in which the requestor can retry. - return; - } - LogErrorOnFailure(err); - handler->AddStatus(cachedCommandPath, StatusIB(err).mStatus); + ChipLogError(Controller, "Responding with Busy due to being in the middle of handling another BDX transfer"); + Commands::QueryImageResponse::Type response; + response.status = static_cast(MTROTASoftwareUpdateProviderOTAQueryStatusBusy); + response.delayedActionTime.SetValue(delegateResponse.delayedActionTime.ValueOr(kDelayedActionTimeSeconds)); + handler->AddResponse(cachedCommandPath, response); handle.Release(); - // We need to reset state here to clean up any initialization we might have done including starting the BDX - // timeout timer while preparing for transfer if any failure occurs afterwards. - gOtaSender.ResetState(); + // We do not ResetState state when we get the busy error because that means we are locked in a BDX transfer + // session with another requestor when we get this query image request. We do not want to interrupt the + // ongoing transfer instead just respond to the second requestor with a busy status and a delayedActionTime + // in which the requestor can retry. return; } char uriBuffer[kMaxBDXURILen]; MutableCharSpan uri(uriBuffer); - err = bdx::MakeURI(ourNodeId.GetNodeId(), AsCharSpan(data.imageURI), uri); + CHIP_ERROR err = bdx::MakeURI(ourNodeId.GetNodeId(), AsCharSpan(data.imageURI), uri); if (CHIP_NO_ERROR != err) { LogErrorOnFailure(err); handler->AddStatus(cachedCommandPath, StatusIB(err).mStatus); handle.Release(); - gOtaSender.ResetState(); return; } delegateResponse.imageURI.SetValue(uri); diff --git a/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h new file mode 100644 index 00000000000000..0965c33b0bcbdc --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h @@ -0,0 +1,60 @@ +/** + * + * 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 "MTROTAImageTransferHandler.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class creates a handler for listening to all unsolicited BDX messages and when a BDX ReceiveInit + * message is received from a node, it creates a new MTROTAImageTransferHandler object as a delegate + * that handles the OTA image file transfer for that node. + * + * Each MTROTAImageTransferHandler instance will handle one BDX transfer session. + */ +class MTROTAUnsolicitedBDXMessageHandler : public chip::Messaging::UnsolicitedMessageHandler +{ +public: + MTROTAUnsolicitedBDXMessageHandler() : mExchangeMgr(nullptr) {} + ~MTROTAUnsolicitedBDXMessageHandler(); + + CHIP_ERROR Init(chip::Messaging::ExchangeManager * exchangeManager); + + // Returns the number of delegates that are currently handling BDX transfers + static uint8_t GetNumberOfDelegates(); + + // Increase the number of delegates by 1. + static void IncrementNumberOfDelegates(); + + // Decrease the number of delegates by 1. + static void DecrementNumberOfDelegates(); + +private: + CHIP_ERROR OnUnsolicitedMessageReceived(const chip::PayloadHeader & payloadHeader, + chip::Messaging::ExchangeDelegate * _Nonnull & newDelegate) override; + +protected: + chip::Messaging::ExchangeManager * mExchangeMgr; + + MTROTAImageTransferHandler * otaImageTransferHandler; + + static inline uint8_t mNumberOfDelegates = 0; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm new file mode 100644 index 00000000000000..ec0ad3c6527df6 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm @@ -0,0 +1,74 @@ +/** + * + * 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 "MTROTAUnsolicitedBDXMessageHandler.h" +#import "MTROTAImageTransferHandler.h" + +#include + +using namespace chip; +using namespace chip::Messaging; +using namespace chip::bdx; + +MTROTAUnsolicitedBDXMessageHandler::~MTROTAUnsolicitedBDXMessageHandler() +{ + assertChipStackLockedByCurrentThread(); + if (mExchangeMgr) { + mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); + mExchangeMgr = nullptr; + } + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates = 0; +} + +CHIP_ERROR MTROTAUnsolicitedBDXMessageHandler::Init(ExchangeManager * exchangeManager) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INCORRECT_STATE); + + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates = 0; + + mExchangeMgr = exchangeManager; + return mExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id, this); +} + +CHIP_ERROR MTROTAUnsolicitedBDXMessageHandler::OnUnsolicitedMessageReceived( + const PayloadHeader & payloadHeader, ExchangeDelegate * _Nonnull & newDelegate) +{ + assertChipStackLockedByCurrentThread(); + ChipLogDetail(BDX, "%s: message " ChipLogFormatMessageType " protocol " ChipLogFormatProtocolId, __FUNCTION__, + payloadHeader.GetMessageType(), ChipLogValueProtocolId(payloadHeader.GetProtocolID())); + + VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // If we receive a ReceiveInit BDX message, create a new MTROTAImageTransferHandler and register it + // as the handler for all BDX messages for the OTA file transfer that will come over this exchange. + if (payloadHeader.HasMessageType(MessageType::ReceiveInit)) { + otaImageTransferHandler = new MTROTAImageTransferHandler(); + newDelegate = otaImageTransferHandler; + } + return CHIP_NO_ERROR; +} + +uint8_t MTROTAUnsolicitedBDXMessageHandler::GetNumberOfDelegates() +{ + return MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates; +} + +void MTROTAUnsolicitedBDXMessageHandler::IncrementNumberOfDelegates() { MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates++; } + +void MTROTAUnsolicitedBDXMessageHandler::DecrementNumberOfDelegates() { MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates--; } diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index fc6fcc5c7f2c13..01dbe50e42e186 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -260,6 +260,10 @@ B2E0D7B9245B0B5C003C5B48 /* MTRSetupPayload.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2E0D7B0245B0B5C003C5B48 /* MTRSetupPayload.mm */; }; B2F53AF2245B0DCF0010745E /* MTRSetupPayloadParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2F53AF1245B0DCF0010745E /* MTRSetupPayloadParserTests.m */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; + CF0F30852A97E128001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CF0F30842A97E128001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.h */; }; + CF0F30872A97F5D5001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF0F30862A97F5D5001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.mm */; }; + CF2A42DC2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CF2A42DA2AB11FD1009A1DB2 /* MTROTAImageTransferHandler.h */; }; + CF2A42DD2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF2A42DB2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.mm */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -583,6 +587,10 @@ B2F53AF1245B0DCF0010745E /* MTRSetupPayloadParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRSetupPayloadParserTests.m; sourceTree = ""; }; BA09EB3F2474762900605257 /* libCHIP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCHIP.a; path = lib/libCHIP.a; sourceTree = BUILT_PRODUCTS_DIR; }; BA107AEE2470CFBB004287EB /* chip_xcode_build_connector.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = chip_xcode_build_connector.sh; sourceTree = ""; }; + CF0F30842A97E128001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROTAUnsolicitedBDXMessageHandler.h; sourceTree = ""; }; + CF0F30862A97F5D5001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAUnsolicitedBDXMessageHandler.mm; sourceTree = ""; }; + CF2A42DA2AB11FD1009A1DB2 /* MTROTAImageTransferHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROTAImageTransferHandler.h; sourceTree = ""; }; + CF2A42DB2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAImageTransferHandler.mm; sourceTree = ""; }; D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRErrorTestUtils.h; sourceTree = ""; }; D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestKeys.h; sourceTree = ""; }; D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestStorage.h; sourceTree = ""; }; @@ -1006,6 +1014,10 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + CF2A42DA2AB11FD1009A1DB2 /* MTROTAImageTransferHandler.h */, + CF2A42DB2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.mm */, + CF0F30862A97F5D5001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.mm */, + CF0F30842A97E128001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.h */, 1E4D655129C30A8700BC3478 /* MTRCommissionableBrowser.mm */, 1E4D654C29C208DD00BC3478 /* MTRCommissionableBrowser.h */, 1E4D654D29C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h */, @@ -1245,6 +1257,7 @@ 5A6FEC9927B5C88900F25F42 /* MTRDeviceOverXPC.h in Headers */, 51B22C222740CB1D008D5055 /* MTRCommandPayloadsObjc.h in Headers */, 51B22C1E2740CB0A008D5055 /* MTRStructsObjc.h in Headers */, + CF2A42DC2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.h in Headers */, 2CB7163B252E8A7B0026E2BB /* MTRDeviceControllerDelegateBridge.h in Headers */, 75B765C12A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h in Headers */, 5ACDDD7A27CD129700EFD68A /* MTRClusterStateCacheContainer.h in Headers */, @@ -1263,6 +1276,7 @@ 513DDB862761F69300DAA01A /* MTRAttributeTLVValueDecoder_Internal.h in Headers */, 2CB7163F252F731E0026E2BB /* MTRDeviceControllerDelegate.h in Headers */, 88EBF8CE27FABDD500686BC1 /* MTRDeviceAttestationDelegate.h in Headers */, + CF0F30852A97E128001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.h in Headers */, 2C222AD0255C620600E446B9 /* MTRBaseDevice.h in Headers */, 7596A84F2877E6A9004DAE0E /* MTRCluster_Internal.h in Headers */, 3D843716294979230070D20A /* MTRCallbackBridge.h in Headers */, @@ -1541,6 +1555,7 @@ 51E0310127EA20D20083DC9C /* MTRControllerAccessControl.mm in Sources */, 1ED276E226C5812A00547A89 /* MTRCluster.mm in Sources */, B2E0D7B3245B0B5C003C5B48 /* MTRError.mm in Sources */, + CF0F30872A97F5D5001F96C5 /* MTROTAUnsolicitedBDXMessageHandler.mm in Sources */, 51E51FC1282AD37A00FC978D /* MTRDeviceControllerStartupParams.mm in Sources */, 51565CAE2A79D42100469F18 /* MTRConversion.mm in Sources */, 1ED276E026C57CF000547A89 /* MTRCallbackBridge.mm in Sources */, @@ -1550,6 +1565,7 @@ 510470FB2A2F7DF60053EA7E /* MTRBackwardsCompatShims.mm in Sources */, 5173A47629C0E2ED00F67F48 /* MTRFabricInfo.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, + CF2A42DD2AB11FD2009A1DB2 /* MTROTAImageTransferHandler.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */, 3D843757294AD25A0070D20A /* MTRCertificateInfo.mm in Sources */, diff --git a/src/protocols/bdx/AsyncTransferFacilitator.cpp b/src/protocols/bdx/AsyncTransferFacilitator.cpp new file mode 100644 index 00000000000000..0706dc62c38c0b --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.cpp @@ -0,0 +1,154 @@ +/* + * + * 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. + */ + +#include "AsyncTransferFacilitator.h" + +#include + +namespace chip { +namespace bdx { + +CHIP_ERROR AsyncTransferFacilitator::OnMessageReceived(chip::Messaging::ExchangeContext * ec, + const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) +{ + VerifyOrReturnError(ec == mExchange.Get(), CHIP_ERROR_INCORRECT_STATE); + + ChipLogDetail(BDX, " %s: message " ChipLogFormatMessageType " protocol " ChipLogFormatProtocolId, __FUNCTION__, + payloadHeader.GetMessageType(), ChipLogValueProtocolId(payloadHeader.GetProtocolID())); + CHIP_ERROR err = + mTransfer.HandleMessageReceived(payloadHeader, std::move(payload), System::SystemClock().GetMonotonicTimestamp()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(BDX, "failed to handle message: %" CHIP_ERROR_FORMAT, err.Format()); + } + + // Almost every BDX message will follow up with a response on the exchange. Even messages that might signify the end of a + // transfer could necessitate a response if they are received at the wrong time. + // For this reason, it is left up to the application logic to call ExchangeContext::Close() when it has determined that the + // transfer is finished. + ec->WillSendMessage(); + + // Get the next output event and send it to the delegate. + TransferSession::OutputEvent outEvent; + do + { + mTransfer.GetNextAction(outEvent); + + if (outEvent.EventType == TransferSession::OutputEventType::kTransferTimeout) + { + OnResponseTimeout(ec); + } + else + { + HandleAsyncTransferSessionOutput(outEvent); + } + } while (outEvent.EventType != TransferSession::OutputEventType::kNone); + + return err; +} + +Messaging::ExchangeContext * AsyncTransferFacilitator::GetExchangeContext() +{ + if (mExchange) + { + return mExchange.Get(); + } + return nullptr; +} + +void AsyncTransferFacilitator::OnExchangeClosing(chip::Messaging::ExchangeContext * ec) +{ + ChipLogDetail(BDX, "%s, ec: " ChipLogFormatExchange, __FUNCTION__, ChipLogValueExchange(ec)); + if (mExchange) + { + mExchange.Release(); + } +} + +void AsyncTransferFacilitator::OnResponseTimeout(Messaging::ExchangeContext * ec) +{ + ChipLogDetail(BDX, "%s, ec: " ChipLogFormatExchange, __FUNCTION__, ChipLogValueExchange(ec)); + if (mExchange) + { + mExchange.Release(); + } +} + +CHIP_ERROR AsyncResponder::PrepareForTransfer(Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, + System::Clock::Timeout timeout) +{ + VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + + ReturnErrorOnFailure(mTransfer.WaitForTransfer(role, xferControlOpts, maxBlockSize, timeout)); + + VerifyOrReturnError(!mExchange, CHIP_ERROR_INCORRECT_STATE); + mExchange.Grab(exchangeCtx); + + return CHIP_NO_ERROR; +} + +void AsyncResponder::ResetTransfer() +{ + if (mExchange) + { + mExchange.Release(); + } + mTransfer.Reset(); +} + +bdx::StatusCode AsyncResponder::GetBdxStatusCodeFromChipError(CHIP_ERROR err) +{ + if (err == CHIP_ERROR_INCORRECT_STATE) + { + return bdx::StatusCode::kUnexpectedMessage; + } + if (err == CHIP_ERROR_INVALID_ARGUMENT) + { + return bdx::StatusCode::kBadMessageContents; + } + return bdx::StatusCode::kUnknown; +} + +void AsyncResponder::NotifyEventHandledWithError(CHIP_ERROR error) +{ + ChipLogDetail(BDX, "%s : error %d", __FUNCTION__, error.AsInteger()); + if (error != CHIP_NO_ERROR) + { + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(error)); + } + + // Get the next output event and send it to the delegate. + TransferSession::OutputEvent outEvent; + do + { + mTransfer.GetNextAction(outEvent); + + if (outEvent.EventType == TransferSession::OutputEventType::kTransferTimeout) + { + OnResponseTimeout(mExchange.Get()); + } + else + { + HandleAsyncTransferSessionOutput(outEvent); + } + } while (outEvent.EventType != TransferSession::OutputEventType::kNone); +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/AsyncTransferFacilitator.h b/src/protocols/bdx/AsyncTransferFacilitator.h new file mode 100644 index 00000000000000..878791be45e135 --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.h @@ -0,0 +1,108 @@ +/* + * + * 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. + */ + +#include +#include +#include +#include +#include + +#pragma once + +namespace chip { +namespace bdx { + +/** + * An abstract class with methods for handling BDX messages from an ExchangeContext and using an event driven + * async approach to get the next action from the transfer session state machine. + * + * This class does not define any methods for beginning a transfer or initializing the underlying TransferSession object (see + * Responder below. TODO: # 29334 - Add Initiator to AsyncFacilitator) + * + * For each BDX transfer, we will have an instance of AsyncTransferFacilitator facilitating the transfer. + */ +class AsyncTransferFacilitator : public Messaging::ExchangeDelegate +{ +public: + AsyncTransferFacilitator() : mExchange(*this) {} + ~AsyncTransferFacilitator() override = default; + + /** + * This method should be implemented to contain business-logic handling of BDX messages and other TransferSession events. + * + * @param[in] event An OutputEvent that contains output from the TransferSession object. + */ + virtual void HandleAsyncTransferSessionOutput(TransferSession::OutputEvent & event) = 0; + + /** + * This method returns the exchange context contained in the exchange holder member of this class. + */ + chip::Messaging::ExchangeContext * GetExchangeContext(); + + // Inherited from ExchangeContext + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; + void OnResponseTimeout(Messaging::ExchangeContext * ec) override; + void OnExchangeClosing(chip::Messaging::ExchangeContext * ec) override; + +protected: + // The transfer session coresponding to this AsynTransferFacilitator object. + TransferSession mTransfer; + + // The Exchange holder that holds the exchange context used for the BDX messages. + Messaging::ExchangeHolder mExchange; +}; + +/** + * An AsyncTransferFacilitator that is initialized to respond to an incoming BDX transfer request. + * + * Provides a method for initializing the TransferSession member but still needs to be extended to implement + * HandleAsyncTransferSessionOutput. It is intended that this class will be used as a delegate for handling an unsolicited BDX + * message. + */ +class AsyncResponder : public AsyncTransferFacilitator +{ +public: + /** + * Initialize the TransferSession state machine to be ready for an incoming transfer request. + * + * @param[in] exchangeCtx The exchange context of the delegate + * @param[in] role The role of the Responder: Sender or Receiver of BDX data + * @param[in] xferControlOpts Supported transfer modes (see TransferControlFlags) + * @param[in] maxBlockSize The supported maximum size of BDX Block data + * @param[in] timeout The chosen timeout delay for the BDX transfer + */ + CHIP_ERROR PrepareForTransfer(Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, + System::Clock::Timeout timeout); + + void ResetTransfer(); + + /** + * Notifies the transfer facilitator that an output event has been handled by the delegate and passes any error(s) that occured + * while handling the event. This is needed as the delegate calls async callbacks for handling the BDX messages and we need to + * get the next action from the state machine based on how the delegate handled the message. + * + * @param[in] error The CHIP_ERROR if there were any errors in handling the event, otherwise CHIP_NO_ERROR is passed + */ + void NotifyEventHandledWithError(CHIP_ERROR error); + +private: + bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err); +}; +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BUILD.gn b/src/protocols/bdx/BUILD.gn index c9cfe91ac45a39..a7e43ecb2c5259 100644 --- a/src/protocols/bdx/BUILD.gn +++ b/src/protocols/bdx/BUILD.gn @@ -18,6 +18,8 @@ static_library("bdx") { output_name = "libBdx" sources = [ + "AsyncTransferFacilitator.cpp", + "AsyncTransferFacilitator.h", "BdxMessages.cpp", "BdxMessages.h", "BdxTransferSession.cpp", diff --git a/src/protocols/bdx/BdxTransferSession.cpp b/src/protocols/bdx/BdxTransferSession.cpp index f6c54985ab5cd1..5d5c32fb3524a5 100644 --- a/src/protocols/bdx/BdxTransferSession.cpp +++ b/src/protocols/bdx/BdxTransferSession.cpp @@ -64,10 +64,13 @@ TransferSession::TransferSession() mSuppportedXferOpts.ClearAll(); } -void TransferSession::PollOutput(OutputEvent & event, System::Clock::Timestamp curTime) +void TransferSession::GetNextAction(OutputEvent & event) { - event = OutputEvent(OutputEventType::kNone); + PollOutput(event, System::SystemClock().GetMonotonicTimestamp()); +} +void TransferSession::PollOutput(OutputEvent & event, System::Clock::Timestamp curTime) +{ if (mShouldInitTimeoutStart) { mTimeoutStartTime = curTime; diff --git a/src/protocols/bdx/BdxTransferSession.h b/src/protocols/bdx/BdxTransferSession.h index f5ef397ca7d9e8..5fd82a49f51579 100644 --- a/src/protocols/bdx/BdxTransferSession.h +++ b/src/protocols/bdx/BdxTransferSession.h @@ -144,6 +144,28 @@ class DLL_EXPORT TransferSession static OutputEvent QueryWithSkipEvent(TransferSkipData bytesToSkip); }; + /** + * @brief + * Gets the next pending output from the transfer session along with any data for the caller to take action on. + * + * It is a wrapper around PollOutput which is a misnomer since the intent of the PollOutput was not to use a polling + * mechanism to get the next action for the client to take. It is highly encourgaed to use GetNextAction in lieu of + * PollOutput to get the pending output event. + * + * This method should be called asynchronously based on events received by the exchange context or events sent by + * the entity using the Transfer session for BDX. + * + * It is possible that consecutive calls to this method may emit different outputs depending on the state of the + * TransferSession object and so we need to call this until we get an event of type - OutputEventType::kNone + * + * Note that if the type outputted is kMsgToSend, the caller is expected to send the message immediately + * + * See OutputEventType for all possible output event types. + * + * @param event Reference to an OutputEvent struct that will be filled out with any pending output data + */ + void GetNextAction(OutputEvent & event); + /** * @brief * Indicates the presence of pending output and includes any data for the caller to take action on.