From db59df695190b0f34522ca2e24c8d108d7116a47 Mon Sep 17 00:00:00 2001 From: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:22:28 -0700 Subject: [PATCH] Add an AsyncTransferFacilitator that supports BDX transfer using an event driven approach rather than polling - Update the OTA provider darwin implementation to use the AsyncTransferFacilitator to transfer the OTA file using BDX --- .../CHIP/MTROTAImageTransferHandler.h | 76 +++ .../CHIP/MTROTAImageTransferHandler.mm | 363 ++++++++++++ .../CHIP/MTROTAProviderDelegateBridge.h | 7 +- .../CHIP/MTROTAProviderDelegateBridge.mm | 517 ++---------------- .../CHIP/MTROTAUnsolicitedBDXMessageHandler.h | 70 +++ .../MTROTAUnsolicitedBDXMessageHandler.mm | 100 ++++ .../Matter.xcodeproj/project.pbxproj | 16 + .../bdx/AsyncTransferFacilitator.cpp | 209 +++++++ src/protocols/bdx/AsyncTransferFacilitator.h | 130 +++++ src/protocols/bdx/BUILD.gn | 2 + 10 files changed, 1016 insertions(+), 474 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/MTROTAImageTransferHandler.h b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h new file mode 100644 index 00000000000000..802817b3ceaa98 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h @@ -0,0 +1,76 @@ +/** + * + * Copyright (c) 2024 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 HandleTransferSessionOutput virtual method and provides an implementation for it to handle + * the OutputEvents that are generated by the BDX transfer session state machine. + * + * An MTROTAImageTransferHandler will be associated with a specific BDX transfer session. + * + * The lifecycle of this class is managed by the AsyncFacilitator class which calls the virtual DestroySelf method + * that is implemented by this class to clean up and destroy itself and the AsyncFacilitator instances. + * Note: An object of this class can't be used after DestroySelf has been called. + */ +class MTROTAImageTransferHandler : public chip::bdx::AsyncResponder +{ +public: + MTROTAImageTransferHandler(); + ~MTROTAImageTransferHandler(); + + void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; + void DestroySelf() override; + +protected: + CHIP_ERROR OnMessageReceived(chip::Messaging::ExchangeContext * ec, const chip::PayloadHeader & payloadHeader, + chip::System::PacketBufferHandle && payload) override; + +private: + CHIP_ERROR PrepareForTransfer(chip::System::Layer * layer, chip::Messaging::ExchangeContext * exchangeCtx, chip::FabricIndex fabricIndex, + chip::NodeId nodeId); + + CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId); + + 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); + + // The fabric index of the node with which the BDX session is established. + chip::Optional mFabricIndex; + + // The node id of the node with which the BDX session is established. + chip::Optional mNodeId; + + // The OTA provider delegate used by the controller. + id mDelegate = nil; + chip::System::Layer * mSystemLayer = 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..5bbd35c36e63c1 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm @@ -0,0 +1,363 @@ +/** + * + * Copyright (c) 2024 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" + +using namespace chip; +using namespace chip::bdx; +using namespace chip::app; + +constexpr uint32_t kMaxBdxBlockSize = 1024; + +// Timeout for the BDX transfer session. The OTA Spec mandates this should be >= 5 minutes. +constexpr System::Clock::Timeout kBdxTimeout = System::Clock::Seconds16(5 * 60); + +constexpr bdx::TransferRole kBdxRole = bdx::TransferRole::kSender; + +MTROTAImageTransferHandler::MTROTAImageTransferHandler() +{ + // Increment the number of delegates handling BDX by 1. + MTROTAUnsolicitedBDXMessageHandler::IncrementNumberOfDelegates(); +} + +CHIP_ERROR MTROTAImageTransferHandler::PrepareForTransfer(System::Layer * layer, + Messaging::ExchangeContext * exchangeCtx, FabricIndex fabricIndex, NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + mSystemLayer = layer; + + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + + BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); + + return AsyncResponder::PrepareForTransfer(layer, exchangeCtx, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); +} + +MTROTAImageTransferHandler::~MTROTAImageTransferHandler() +{ + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + mDelegate = nil; + mDelegateNotificationQueue = nil; + mSystemLayer = nil; + + // Decrement the number of delegates handling BDX by 1. + MTROTAUnsolicitedBDXMessageHandler::DecrementNumberOfDelegates(); +} + + +CHIP_ERROR MTROTAImageTransferHandler::OnTransferSessionBegin(TransferSession::OutputEvent & event) +{ + assertChipStackLockedByCurrentThread(); + + 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); + OnTransferSessionEnd(event); + AsyncResponder::NotifyEventHandled(event, 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(); + + CHIP_ERROR err = mTransfer.AcceptTransfer(acceptData); + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandled(event, err); + } + 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::NotifyEventHandled(event, 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) { + 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) { + error = CHIP_ERROR_INCORRECT_STATE; + LogErrorOnFailure(error); + AsyncResponder::NotifyEventHandled(event, error); + return error; + } + + if ([strongDelegate respondsToSelector:@selector(handleBDXTransferSessionEndForNodeID:controller:error:)]) { + dispatch_async(delagateQueue, ^{ + [strongDelegate handleBDXTransferSessionEndForNodeID:nodeId + controller:controller + error:[MTRError errorForCHIPErrorCode:error]]; + }); + } + AsyncResponder::NotifyEventHandled(event, error); + return 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::NotifyEventHandled(event, 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); + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandled(event, 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::NotifyEventHandled(event, 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::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + VerifyOrReturn(mDelegate != nil); + + ChipLogError(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + + CHIP_ERROR err = CHIP_NO_ERROR; + switch (event.EventType) { + case TransferSession::OutputEventType::kInitReceived: + err = OnTransferSessionBegin(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandled(event, 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: + ChipLogError(BDX, "Got kAckEOFReceived/kInternalError/kTransferTimeout"); + err = OnTransferSessionEnd(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandled(event, err); + } + break; + case TransferSession::OutputEventType::kQueryWithSkipReceived: + case TransferSession::OutputEventType::kQueryReceived: + err = OnBlockQuery(event); + if (err != CHIP_NO_ERROR) { + LogErrorOnFailure(err); + AsyncResponder::NotifyEventHandled(event, err); + } + 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; + } +} + +void MTROTAImageTransferHandler::DestroySelf() +{ + assertChipStackLockedByCurrentThread(); + + delete this; +} + +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); + + mFabricIndex.SetValue(fabricIndex); + mNodeId.SetValue(nodeId); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MTROTAImageTransferHandler::OnMessageReceived( + Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, System::PacketBufferHandle && payload) +{ + ChipLogProgress(BDX, "MTROTAImageTransferHandler: OnMessageReceived: message " ChipLogFormatMessageType " protocol " ChipLogFormatProtocolId, + payloadHeader.GetMessageType(), ChipLogValueProtocolId(payloadHeader.GetProtocolID())); + + VerifyOrReturnError(ec != nullptr, CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR err; + + // 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(&DeviceLayer::SystemLayer(), ec, fabricIndex, nodeId); + if (err != CHIP_NO_ERROR) { + ChipLogError(Controller, "OnMessageReceived: Failed to prepare for transfer for BDX"); + return err; + } + } + } + + // 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..a9dc8354d1a377 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 - 2024 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,6 +17,8 @@ #import +#import "MTROTAUnsolicitedBDXMessageHandler.h" + #include NS_ASSUME_NONNULL_BEGIN @@ -65,6 +67,9 @@ class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDele static void ConvertToNotifyUpdateAppliedParams( const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::DecodableType & commandData, MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams); + +protected: + MTROTAUnsolicitedBDXMessageHandler mOtaUnsolicitedBDXMsgHandler; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index e191b15acc6cf0..e99f4f0754c1cb 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 - 2024 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,7 +15,7 @@ * limitations under the License. */ -#import "MTROTAProviderDelegateBridge.h" +#include "MTROTAProviderDelegateBridge.h" #import "MTRBaseClusters.h" #import "MTRCommandPayloadsObjC.h" #import "MTRDeviceControllerFactory_Internal.h" @@ -34,7 +34,7 @@ #include #include #include -#include +#include using namespace chip; using namespace chip::app; @@ -42,13 +42,9 @@ 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; +namespace { -// 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 uint32_t kMaxBDXURILen = 256; // 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 @@ -58,459 +54,46 @@ // 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(CHIP_ERROR_CANCELLED); - - mExchangeMgr = nullptr; - mSystemLayer = nullptr; - - return CHIP_NO_ERROR; - } - - void ControllerShuttingDown(MTRDeviceController * controller) - { - assertChipStackLockedByCurrentThread(); - - if (mInitialized && mFabricIndex.Value() == controller.fabricIndex) { - ResetState(CHIP_ERROR_CANCELLED); - } - } - - void ResetState(CHIP_ERROR error) - { - 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()); - - if (mTransferStarted) { - auto controller = [MTRDeviceControllerFactory.sharedInstance runningControllerForFabricIndex:mFabricIndex.Value()]; - if (controller) { - 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]]; - }); - } - } else { - ChipLogError(Controller, "Not notifying delegate of BDX Transfer Session End, controller is not running"); - } - } - } 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; - mTransferStarted = 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_TIMEOUT); - } - - 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(err); - } else if (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(CHIP_ERROR_INTERNAL); - } - return err; - } - - 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 - }]; - }; - - mTransferStarted = true; - 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; - } - - ResetState(error); // will notify the delegate - 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(CHIP_ERROR_CANCELLED); - } - - 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; - bool mTransferStarted = 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; -}; - -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); +} MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge() { - gOtaSender->ResetState(CHIP_ERROR_CANCELLED); Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr); + Shutdown(); } CHIP_ERROR MTROTAProviderDelegateBridge::Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeManager) { - return gOtaSender->Init(systemLayer, exchangeManager); + VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = mOtaUnsolicitedBDXMsgHandler.Init(exchangeManager); + if (err != CHIP_NO_ERROR) { + ChipLogError(Controller, "Failed to initialize the unsolicited BDX Message handler with err %s", err.AsString()); + } + return err; } -void MTROTAProviderDelegateBridge::Shutdown() { gOtaSender->Shutdown(); } +void MTROTAProviderDelegateBridge::Shutdown() +{ + assertChipStackLockedByCurrentThread(); + + mOtaUnsolicitedBDXMsgHandler.Shutdown(); +} void MTROTAProviderDelegateBridge::ControllerShuttingDown(MTRDeviceController * controller) { - gOtaSender->ControllerShuttingDown(controller); + assertChipStackLockedByCurrentThread(); + + mOtaUnsolicitedBDXMsgHandler.ControllerShuttingDown(controller); } + namespace { // Return false if we could not get peer node info (a running controller for // the fabric and a node id). In that case we will have already added an @@ -611,7 +194,6 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath return; } - auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto ourNodeId = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId(); auto * commandParams = [[MTROTASoftwareUpdateProviderClusterQueryImageParams alloc] init]; @@ -662,47 +244,36 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath // If update is not available, return the delegate response if (!hasUpdate) { + ChipLogError(BDX, "delegate bridge. update not available"); handler->AddResponse(cachedCommandPath, delegateResponse); handle.Release(); 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) { - - // 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(MTROTASoftwareUpdateProviderStatusBusy); - 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); + // If the MTROTAUnsolicitedBDXMessageHandler already has a delegate, send busy error. + if (MTROTAUnsolicitedBDXMessageHandler::GetNumberOfDelegates() >= 1) { + ChipLogError( + Controller, "Responding with Busy due to being in the middle of handling another BDX transfer"); + Commands::QueryImageResponse::Type response; + response.status = static_cast(MTROTASoftwareUpdateProviderStatusBusy); + 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(err); - return; + // 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; } 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(err); + Shutdown(); 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..dcbc10eb3f4403 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h @@ -0,0 +1,70 @@ +/** + * + * Copyright (c) 2024 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 +#include + +@class MTRDeviceController; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class creates an unsolicited handler for listening to all unsolicited BDX messages + * and when it receives a BDX ReceiveInit message from a node, it creates a new + * MTROTAImageTransferHandler object as a delegate that will prepare for transfer and + * handle all BDX messages for the BDX transfer session with that node. If it receives an out of order + * BDX message or if the message is received on a non-valid session, the OnUnsolicitedMessageReceived + * returns CHIP_ERROR_INCORRECT_STATE. + * + */ +class MTROTAUnsolicitedBDXMessageHandler : public chip::Messaging::UnsolicitedMessageHandler +{ +public: + MTROTAUnsolicitedBDXMessageHandler() : mExchangeMgr(nullptr) {} + ~MTROTAUnsolicitedBDXMessageHandler() { mExchangeMgr = nullptr; } + + 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 handling BDX transfers by 1. + static void IncrementNumberOfDelegates(); + + // Decrease the number of delegates handling BDX transfers by 1. + static void DecrementNumberOfDelegates(); + + void Shutdown(); + + void ControllerShuttingDown(MTRDeviceController * controller); + +private: + + CHIP_ERROR OnUnsolicitedMessageReceived(const chip::PayloadHeader & payloadHeader, const chip::SessionHandle & session, + chip::Messaging::ExchangeDelegate * _Nonnull & newDelegate) override; + + void OnExchangeCreationFailed(chip::Messaging::ExchangeDelegate * _Nonnull delegate) override; + +protected: + chip::Messaging::ExchangeManager * mExchangeMgr; + + 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..41b81738d2a77e --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm @@ -0,0 +1,100 @@ +/** + * + * Copyright (c) 2024 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; + +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); +} + +void MTROTAUnsolicitedBDXMessageHandler::Shutdown() +{ + assertChipStackLockedByCurrentThread(); + + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates = 0; + VerifyOrReturn(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); + + mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); +} + +void MTROTAUnsolicitedBDXMessageHandler::ControllerShuttingDown(MTRDeviceController * controller) +{ + assertChipStackLockedByCurrentThread(); + + // Since the OTA provider delegate only supports one BDX transfer at a time, calling ShutDown is fine for now. + // TODO: This class needs to keep a list of all MTROTAImageTransferHandlers mapped to the fabric index and only + // delete the MTROTAImageTransferHandler with a fabric index matching the MTRDeviceController's fabric index. + Shutdown(); +} + +CHIP_ERROR MTROTAUnsolicitedBDXMessageHandler::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, const SessionHandle & session, + ExchangeDelegate * _Nonnull & newDelegate) +{ + assertChipStackLockedByCurrentThread(); + + ChipLogDetail(BDX, "MTROTAUnsolicitedBDXMessageHandler: OnUnsolicitedMessageReceived: message " ChipLogFormatMessageType " protocol " ChipLogFormatProtocolId, + payloadHeader.GetMessageType(), ChipLogValueProtocolId(payloadHeader.GetProtocolID())); + + VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); + + if (GetNumberOfDelegates() >= 1) + { + return CHIP_ERROR_BUSY; + } + + // Only proceed if there is a valid fabric index for the SessionHandle. + if (session->IsSecureSession() && session->AsSecureSession() != nullptr && session->AsSecureSession()->GetFabricIndex() != kUndefinedFabricIndex) + { + // If we receive a ReceiveInit BDX message, create a new MTROTAImageTransferHandler and register it + // as the handler for all BDX messages that will come over this exchange and increment the number of delegates. + if (payloadHeader.HasMessageType(MessageType::ReceiveInit)) { + MTROTAImageTransferHandler * otaImageTransferHandler = new MTROTAImageTransferHandler(); + newDelegate = otaImageTransferHandler; + return CHIP_NO_ERROR; + } + } + return CHIP_ERROR_INCORRECT_STATE; +} + +void MTROTAUnsolicitedBDXMessageHandler::OnExchangeCreationFailed(ExchangeDelegate * delegate) +{ + auto * otaTransferHandler = static_cast(delegate); + otaTransferHandler->DestroySelf(); +} + +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 afc1df19338b46..67725abbb7b97c 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -379,6 +379,10 @@ B4FCD5722B603A6300832859 /* DownloadLogCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */; }; B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; + CF3B63CF2CA31E71003C1C87 /* MTROTAImageTransferHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CF3B63CB2CA31E71003C1C87 /* MTROTAImageTransferHandler.h */; }; + CF3B63D02CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = CF3B63CC2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.h */; }; + CF3B63D12CA31E71003C1C87 /* MTROTAImageTransferHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF3B63CD2CA31E71003C1C87 /* MTROTAImageTransferHandler.mm */; }; + CF3B63D22CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF3B63CE2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.mm */; }; D4288E872C8A273F002FEC53 /* MTRDevice_XPC_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = D4288E862C8A273F002FEC53 /* MTRDevice_XPC_Internal.h */; }; D444F9A72C6E8F9D007761E5 /* MTRXPCServerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D444F9A62C6E8F9D007761E5 /* MTRXPCServerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D444F9AA2C6E9A08007761E5 /* MTRXPCClientProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D444F9A82C6E99CA007761E5 /* MTRXPCClientProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -824,6 +828,10 @@ B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DownloadLogCommand.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 = ""; }; + CF3B63CB2CA31E71003C1C87 /* MTROTAImageTransferHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROTAImageTransferHandler.h; sourceTree = ""; }; + CF3B63CC2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROTAUnsolicitedBDXMessageHandler.h; sourceTree = ""; }; + CF3B63CD2CA31E71003C1C87 /* MTROTAImageTransferHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAImageTransferHandler.mm; sourceTree = ""; }; + CF3B63CE2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROTAUnsolicitedBDXMessageHandler.mm; sourceTree = ""; }; D4288E862C8A273F002FEC53 /* MTRDevice_XPC_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDevice_XPC_Internal.h; 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 = ""; }; @@ -1267,6 +1275,10 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + CF3B63CB2CA31E71003C1C87 /* MTROTAImageTransferHandler.h */, + CF3B63CD2CA31E71003C1C87 /* MTROTAImageTransferHandler.mm */, + CF3B63CC2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.h */, + CF3B63CE2CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.mm */, D444F9A12C6E8058007761E5 /* XPC Protocol */, 9B231B032C62EF650030EB37 /* MTRDeviceController_Concrete.mm */, 9B0484F42C701154006C2D5F /* MTRDeviceController_Concrete.h */, @@ -1686,6 +1698,7 @@ 3CF134AB289D8DF70017A19E /* MTRDeviceAttestationInfo.h in Headers */, B2E0D7B2245B0B5C003C5B48 /* MTRManualSetupPayloadParser.h in Headers */, 3CF134A7289D8ADA0017A19E /* MTRCSRInfo.h in Headers */, + CF3B63D02CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.h in Headers */, 88E07D612B9A89A4005FD53E /* MTRMetricKeys.h in Headers */, 3D4733B32BE2D1DA003DC19B /* MTRUtilities.h in Headers */, B2E0D7B1245B0B5C003C5B48 /* Matter.h in Headers */, @@ -1730,6 +1743,7 @@ 9B231B042C62EF650030EB37 /* (null) in Headers */, 515BE4ED2B72C0C5000BC1FD /* MTRUnfairLock.h in Headers */, 998F286F26D55EC5001846C6 /* MTRP256KeypairBridge.h in Headers */, + CF3B63CF2CA31E71003C1C87 /* MTROTAImageTransferHandler.h in Headers */, 2C222ADF255C811800E446B9 /* MTRBaseDevice_Internal.h in Headers */, 514C7A022B64223400DD6D7B /* MTRServerEndpoint_Internal.h in Headers */, 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */, @@ -1995,6 +2009,7 @@ 997DED162695343400975E97 /* MTRThreadOperationalDataset.mm in Sources */, 515C1C6F284F9FFB00A48F0C /* MTRFramework.mm in Sources */, 51029DF6293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm in Sources */, + CF3B63D22CA31E71003C1C87 /* MTROTAUnsolicitedBDXMessageHandler.mm in Sources */, 27A53C1827FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm in Sources */, 93B2CF9A2B56E45C00E4D187 /* MTRClusterNames.mm in Sources */, 998F287126D56940001846C6 /* MTRP256KeypairBridge.mm in Sources */, @@ -2030,6 +2045,7 @@ 1EC4CE5D25CC26E900D7304F /* MTRBaseClusters.mm in Sources */, 514C79F62B62F0B900DD6D7B /* util.cpp in Sources */, 51565CB22A7AD77600469F18 /* MTRDeviceControllerDataStore.mm in Sources */, + CF3B63D12CA31E71003C1C87 /* MTROTAImageTransferHandler.mm in Sources */, 51D0B12F2B617800006E3511 /* MTRAccessGrant.mm in Sources */, 88E6C9482B6334ED001A1FE0 /* MTRMetrics.mm in Sources */, 1ED276E226C5812A00547A89 /* MTRCluster.mm in Sources */, diff --git a/src/protocols/bdx/AsyncTransferFacilitator.cpp b/src/protocols/bdx/AsyncTransferFacilitator.cpp new file mode 100644 index 00000000000000..8ff303520a2696 --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 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 { + +/** + * Releases the exchange, resets the transfer session and schedules calling the DestroySelf + * virtual method implemented by the subclass to delete the subclass. + */ +void AsyncTransferFacilitator::CleanUp() +{ + mExchange.Release(); + mTransfer.Reset(); + VerifyOrReturn(mSystemLayer != nullptr, ChipLogError(BDX, "CleanUp: mSystemLayer is null")); + + mSystemLayer->ScheduleWork( + [](auto * systemLayer, auto * appState) -> void { + auto * _this = static_cast(appState); + _this->DestroySelf(); + }, + this); +} + +AsyncTransferFacilitator::~AsyncTransferFacilitator() +{ +} + +bdx::StatusCode AsyncTransferFacilitator::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; +} + +/** + * Calls the GetNextAction on the TransferSession to get the next output events until it receives TransferSession::OutputEventType::kNone + * If the output event is of type TransferSession::OutputEventType::kMsgToSend, it sends the message over the exchange context, otherwise it + * calls the HandleTransferSessionOutput method implemented by the subclass to handle the BDX message. + */ +void AsyncTransferFacilitator::HandleNextOutputEvents() +{ + if (mHandlingOutputEvents) + { + ChipLogDetail(BDX, "HandleNextOutputEvents: Still getting and processing output events from a previous call. Return."); + return; + } + + mHandlingOutputEvents = true; + + // Get the next output event and handle it based on the type of event. + // If its of type kMsgToSend send it over the exchange, otherwise call the HandleTransferSessionOutput + // virtual method that must be implemeted by the subclass of this class to handle the BDX message. + TransferSession::OutputEvent outEvent; + + mTransfer.GetNextAction(outEvent); + while (outEvent.EventType != TransferSession::OutputEventType::kNone) + { + if (outEvent.EventType == TransferSession::OutputEventType::kMsgToSend) + { + SendMessage(outEvent.msgTypeData, outEvent.MsgData); + } + else + { + HandleTransferSessionOutput(outEvent); + } + mTransfer.GetNextAction(outEvent); + } + mHandlingOutputEvents = false; +} + +CHIP_ERROR AsyncTransferFacilitator::SendMessage(const TransferSession::MessageTypeData msgTypeData, System::PacketBufferHandle & msgBuf) +{ + VerifyOrReturnError(mExchange, CHIP_ERROR_INCORRECT_STATE); + + Messaging::SendFlags sendFlags; + + // All messages that are sent expect a response, except for a StatusReport which would indicate an error and + // the end of the transfer. + if (!msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) + { + sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse); + } + + Messaging::ExchangeContext * ec = mExchange.Get(); + + // Set the response timeout on the exchange before sending the message. + ec->SetResponseTimeout(mTimeout); + + CHIP_ERROR err = ec->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(msgBuf), sendFlags); + + // If we failed to send the message across the exchange, there is no way to let the other side know there was an error sending + // the message so call CleanUp to release the exchange so the other side can get notified the exchange is closing + // and clean up as needed. Also the CleanUp API resets the transfer session and destroys the subclass. + if (err != CHIP_NO_ERROR) + { + CleanUp(); + } + return err; +} + +CHIP_ERROR AsyncTransferFacilitator::OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) +{ + VerifyOrReturnError(mExchange, CHIP_ERROR_INCORRECT_STATE); + + VerifyOrReturnError(ec == mExchange.Get(), CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = + mTransfer.HandleMessageReceived(payloadHeader, std::move(payload), System::SystemClock().GetMonotonicTimestamp()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(BDX, "OnMessageReceived: Failed to handle message: %" CHIP_ERROR_FORMAT, err.Format()); + + // This should notify the tranfer object to abort transfer so it can send a status report across the exchange + // when we call HandleNextOutputEvents below. + mTransfer.AbortTransfer(AsyncResponder::GetBdxStatusCodeFromChipError(err)); + + } + else if (!payloadHeader.HasMessageType(MessageType::BlockAckEOF)) + { + + // Almost every BDX message expect BlockAckEOF will follow up with a response on the exchange. + ec->WillSendMessage(); + } + + HandleNextOutputEvents(); + return err; +} + +void AsyncTransferFacilitator::OnExchangeClosing(Messaging::ExchangeContext * ec) +{ + ChipLogDetail(BDX, "OnExchangeClosing, ec: " ChipLogFormatExchange, ChipLogValueExchange(ec)); + CleanUp(); +} + +void AsyncTransferFacilitator::OnResponseTimeout(Messaging::ExchangeContext * ec) +{ + ChipLogDetail(BDX, "OnResponseTimeout, ec: " ChipLogFormatExchange, ChipLogValueExchange(ec)); + CleanUp(); +} + +CHIP_ERROR AsyncResponder::PrepareForTransfer(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, + System::Clock::Timeout timeout) +{ + VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(!mExchange, CHIP_ERROR_INCORRECT_STATE); + + mSystemLayer = layer; + mTimeout = timeout; + + ReturnErrorOnFailure(mTransfer.WaitForTransfer(role, xferControlOpts, maxBlockSize, mTimeout)); + + mExchange.Grab(exchangeCtx); + + return CHIP_NO_ERROR; +} + +void AsyncResponder::NotifyEventHandled(TransferSession::OutputEvent & event, CHIP_ERROR error) +{ + ChipLogDetail(BDX, "NotifyEventHandled : Event %s Error %" CHIP_ERROR_FORMAT, event.ToString(event.EventType), error.Format()); + + // If it's a message indicating either the end of the transfer or a timeout reported by the transfer session + // or an error occured, we need to call CleanUp. + if (event.EventType == TransferSession::OutputEventType::kAckEOFReceived || + event.EventType == TransferSession::OutputEventType::kInternalError || + event.EventType == TransferSession::OutputEventType::kTransferTimeout || + event.EventType == TransferSession::OutputEventType::kStatusReceived) + { + CleanUp(); + return; + } + + // If there was an error handling the output event, this should notify the tranfer object to abort transfer so it can send a status report + // across the exchange when we call HandleNextOutputEvents below. + if (error != CHIP_NO_ERROR) + { + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(error)); + } + + HandleNextOutputEvents(); +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/AsyncTransferFacilitator.h b/src/protocols/bdx/AsyncTransferFacilitator.h new file mode 100644 index 00000000000000..b11fe8b29b9da4 --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 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 +#include + +#pragma once + +namespace chip { +namespace bdx { + +/** + * An abstract class with methods for handling BDX messages received from an ExchangeContext. Once a message is receievd, this + * class passes the message to the TransferSession to process the received message and gets the next output events from the + * TransferSession state machine and either sends a message accross the exchange or calls the HandleTransferSessionOutput virtual method + * to notify the subclass of the event generated. It keeps getting the next output event until it receieves an output event of type + * TransferSession::OutputEventType::kNone. For messages that are sent to the HandleTransferSessionOutput method, the subclass must call the + * NotifyEventHandled to notify the AsyncTransferFacilitator that the event has been handled and returns an error code for error cases or success. + * + * This class does not define any methods for beginning a transfer or initializing the underlying TransferSession object. + * See AsyncResponder for a class that does. + * TODO: # 29334 - Add AsyncInitiator to handle the initiating side of a transfer. + * + * An AsyncTransferFacilitator is associated with a specific BDX transfer. + */ +class AsyncTransferFacilitator : public Messaging::ExchangeDelegate +{ +public: + AsyncTransferFacilitator() : mExchange(*this) {} + ~AsyncTransferFacilitator() override; + + /** + * This method should be implemented to contain business-logic handling of BDX messages + * and other TransferSession events. + * + * @param[in] event An OutputEvent that contains the output from the TransferSession object. + */ + virtual void HandleTransferSessionOutput(TransferSession::OutputEvent & event) = 0; + + /** + * This method should be implemented to destroy the object subclassing AsyncTransferFacilitator. + */ + virtual void DestroySelf() = 0; + +protected: + CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) override; + void OnResponseTimeout(Messaging::ExchangeContext * ec) override; + void OnExchangeClosing(Messaging::ExchangeContext * ec) override; + + CHIP_ERROR SendMessage(const TransferSession::MessageTypeData msgTypeData, System::PacketBufferHandle & msgBuf); + + static bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err); + + void CleanUp(); + + void HandleNextOutputEvents(); + + // The transfer session coresponding to this AsyncTransferFacilitator object. + TransferSession mTransfer; + + // The Exchange holder that holds the exchange context used for sending and receiving BDX messages. + Messaging::ExchangeHolder mExchange; + + // The timeout for the BDX transfer session. + System::Clock::Timeout mTimeout; + + System::Layer * mSystemLayer; + +private: + + bool mHandlingOutputEvents; +}; + +/** + * An AsyncTransferFacilitator that is initialized to respond to an incoming BDX transfer request. + * + * Provides a method for initializing the TransferSession members but still needs to be extended to implement + * HandleTransferSessionOutput. + * + * An instance of some subclass of this class should be used as the exchange delegate for a BDX transfer. + */ +class AsyncResponder : public AsyncTransferFacilitator +{ +public: + /** + * Initialize the TransferSession state machine to be ready for an incoming transfer request. + * + * @param[in] exchangeCtx The exchange to use for the transfer. + * @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 maximum supported size of BDX Block data + * @param[in] timeout The chosen timeout delay for the BDX transfer + */ + CHIP_ERROR PrepareForTransfer(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, + System::Clock::Timeout timeout); + + /** + * This is called by the subclass implementing HandleTransferSessionOutput to notify the AsyncTransferFacilitator + * that it has handled the OutputEvent specified in event and returns an error code (if any) or success in the error paramter. + * Once this is called the AsyncTransferFacilitator either aborts the transfer if an error has ocurred or drives the TransferSession + * state machine to generate the next output events to establish and continue the BDX session further. + * + * + * @param[in] event The OutputEvent that was handled by the subclass. + * @param[in] error The error code that occured when handling the event if an error occurs. Otherwise has CHIP_NO_ERROR. + */ + void NotifyEventHandled(TransferSession::OutputEvent & event, CHIP_ERROR error); +}; + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/BUILD.gn b/src/protocols/bdx/BUILD.gn index a95fd02202f626..118410183f9508 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", "BdxTransferDiagnosticLog.cpp",