diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index b87155b0f17b13..cd0f8ed1914480 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 @@ -901,7 +906,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 +980,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 +1000,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 3204ab60369b80..995072729d09c0 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,24 +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 -#include using namespace chip; using namespace chip::app; @@ -42,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 @@ -58,462 +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 { -Global gOtaSender; - -NSInteger constexpr kOtaProviderEndpoint = 0; +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 { @@ -616,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]; @@ -673,42 +235,30 @@ 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 b5a63a11efa6df..3ee956c68d9b1a 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -298,6 +298,10 @@ B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; B4E2621E2AA0D02D00DBA5BC /* WaitForCommissioneeCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */; }; 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 */ @@ -662,6 +666,10 @@ B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WaitForCommissioneeCommand.mm; 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 = ""; }; @@ -1088,6 +1096,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 */, @@ -1392,6 +1404,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 */, @@ -1411,6 +1424,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 */, @@ -1725,6 +1739,7 @@ 51565CB22A7AD77600469F18 /* MTRDeviceControllerDataStore.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 */, @@ -1734,6 +1749,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.