diff --git a/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.h new file mode 100644 index 00000000000000..e96dc1b124626a --- /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 Init(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..1b22baee9f49cc --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAImageTransferHandler.mm @@ -0,0 +1,405 @@ +/** + * + * 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; + +@interface MTROTAImageTransferHandlerWrapper : NSObject +- (instancetype)initWithMTROTAImageTransferHandler:(MTROTAImageTransferHandler *)otaImageTransferHandler; +@property(nonatomic, nullable, readwrite, assign) MTROTAImageTransferHandler * otaImageTransferHandler; +@end + +@implementation MTROTAImageTransferHandlerWrapper + +- (instancetype)initWithMTROTAImageTransferHandler:(MTROTAImageTransferHandler *)otaImageTransferHandler +{ + if (self = [super init]) + { + _otaImageTransferHandler = otaImageTransferHandler; + } + return self; +} +@end + +MTROTAImageTransferHandlerWrapper * mOTAImageTransferHandlerWrapper; + +MTROTAImageTransferHandler::MTROTAImageTransferHandler() +{ + MTROTAUnsolicitedBDXMessageHandler::GetInstance()->OnDelegateCreated(this); + mOTAImageTransferHandlerWrapper = [[MTROTAImageTransferHandlerWrapper alloc] initWithMTROTAImageTransferHandler:this]; +} + +CHIP_ERROR MTROTAImageTransferHandler::Init(System::Layer * layer, + Messaging::ExchangeContext * exchangeCtx, FabricIndex fabricIndex, NodeId nodeId) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrReturnError(layer != nullptr, CHIP_ERROR_INCORRECT_STATE); + + mSystemLayer = layer; + + ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId)); + + BitFlags flags(bdx::TransferControlFlags::kReceiverDrive); + + return AsyncResponder::Init(mSystemLayer, exchangeCtx, kBdxRole, flags, kMaxBdxBlockSize, kBdxTimeout); +} + +MTROTAImageTransferHandler::~MTROTAImageTransferHandler() +{ + assertChipStackLockedByCurrentThread(); + mFabricIndex.ClearValue(); + mNodeId.ClearValue(); + mDelegate = nil; + mDelegateNotificationQueue = nil; + mSystemLayer = nil; + + MTROTAUnsolicitedBDXMessageHandler::GetInstance()->OnDelegateDestroyed(this); + mOTAImageTransferHandlerWrapper.otaImageTransferHandler = nullptr; +} + +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); + + MTROTAImageTransferHandlerWrapper * __weak weakWrapper = mOTAImageTransferHandlerWrapper; + + auto completionHandler = ^(NSError * _Nullable error) { + + // Check if the OTA image transfer handler is still valid. If not, return from the completion handler. + MTROTAImageTransferHandlerWrapper * strongWrapper = weakWrapper; + if (!strongWrapper || !strongWrapper.otaImageTransferHandler) + { + return; + } + [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); + + MTROTAImageTransferHandlerWrapper * __weak weakWrapper = mOTAImageTransferHandlerWrapper; + + auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) { + + // Check if the OTA image transfer handler is still valid. If not, return from the completion handler. + MTROTAImageTransferHandlerWrapper * strongWrapper = weakWrapper; + if (!strongWrapper || !strongWrapper.otaImageTransferHandler) + { + return; + } + + [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: + 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) +{ + assertChipStackLockedByCurrentThread(); + + 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. Otherwise we send the message + // received to the AsyncTransferFacilitator for processing. + if (payloadHeader.HasMessageType(MessageType::ReceiveInit)) { + NodeId nodeId = ec->GetSessionHandle()->GetPeer().GetNodeId(); + FabricIndex fabricIndex = ec->GetSessionHandle()->GetFabricIndex(); + + if (nodeId != kUndefinedNodeId && fabricIndex != kUndefinedFabricIndex) { + err = Init(&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..b506512e3eb712 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" @@ -33,8 +33,8 @@ #include #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,457 +54,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(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); } 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 { @@ -611,7 +192,6 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath return; } - auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto ourNodeId = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId(); auto * commandParams = [[MTROTASoftwareUpdateProviderClusterQueryImageParams alloc] init]; @@ -667,42 +247,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) { - - // 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); + // 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..0cb3ea785610f7 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.h @@ -0,0 +1,85 @@ +/** + * + * 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 +#import "MTROTAImageTransferHandler.h" + +#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) + { + sInstance = this; + } + + ~MTROTAUnsolicitedBDXMessageHandler() { mExchangeMgr = nullptr;} + + static MTROTAUnsolicitedBDXMessageHandler * GetInstance(); + + CHIP_ERROR Init(chip::Messaging::ExchangeManager * exchangeManager); + + // Returns the number of delegates that are currently handling BDX transfers. + static uint8_t GetNumberOfDelegates(); + + + static void IncrementNumberOfDelegates(); + + // Decrease the number of delegates handling BDX transfers by 1. + static void DecrementNumberOfDelegates(); + + void OnDelegateCreated(void * imageTransferHandler); + + void OnDelegateDestroyed(void * imageTransferHandler); + + 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; + + // TODO: #36181 - Have a set of MTROTAImageTransferHandler objects. + MTROTAImageTransferHandler * mOTAImageTransferHandler = nullptr; + + chip::Messaging::ExchangeManager * mExchangeMgr; + + static inline uint8_t mNumberOfDelegates = 0; + + static MTROTAUnsolicitedBDXMessageHandler * sInstance; + +}; + +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..127e544340ef2e --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROTAUnsolicitedBDXMessageHandler.mm @@ -0,0 +1,145 @@ +/** + * + * 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" + +#include + +using namespace chip; +using namespace chip::Messaging; +using namespace chip::bdx; + +MTROTAUnsolicitedBDXMessageHandler * MTROTAUnsolicitedBDXMessageHandler::sInstance = nullptr; + +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); +} + +MTROTAUnsolicitedBDXMessageHandler * MTROTAUnsolicitedBDXMessageHandler::GetInstance() +{ + return sInstance; +} + +void MTROTAUnsolicitedBDXMessageHandler::Shutdown() +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrReturn(mExchangeMgr != nullptr); + + mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::BDX::Id); + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates = 0; + + if (mOTAImageTransferHandler != nullptr) + { + mOTAImageTransferHandler->DestroySelf(); + delete mOTAImageTransferHandler; + } + +} + +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: #36181 - This class needs to keep a list of all MTROTAImageTransferHandlers mapped to the fabric index and only + // delete the MTROTAImageTransferHandler objects 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. + if (payloadHeader.HasMessageType(MessageType::ReceiveInit)) { + mOTAImageTransferHandler = new MTROTAImageTransferHandler(); + newDelegate = mOTAImageTransferHandler; + return CHIP_NO_ERROR; + } + } + return CHIP_ERROR_INCORRECT_STATE; +} + +void MTROTAUnsolicitedBDXMessageHandler::OnExchangeCreationFailed(ExchangeDelegate * delegate) +{ + assertChipStackLockedByCurrentThread(); + auto * otaTransferHandler = static_cast(delegate); + VerifyOrReturn(otaTransferHandler != nullptr); + + otaTransferHandler->DestroySelf(); +} + +uint8_t MTROTAUnsolicitedBDXMessageHandler::GetNumberOfDelegates() +{ + assertChipStackLockedByCurrentThread(); + return MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates; +} + +void MTROTAUnsolicitedBDXMessageHandler::IncrementNumberOfDelegates() +{ + assertChipStackLockedByCurrentThread(); + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates++; +} + +void MTROTAUnsolicitedBDXMessageHandler::DecrementNumberOfDelegates() +{ + assertChipStackLockedByCurrentThread(); + MTROTAUnsolicitedBDXMessageHandler::mNumberOfDelegates--; +} + +void MTROTAUnsolicitedBDXMessageHandler::OnDelegateCreated(void * imageTransferHandler) +{ + assertChipStackLockedByCurrentThread(); + + // TODO: #36181 - Store the imageTransferHandler in the set of MTROTAImageTransferHandler objects. + mOTAImageTransferHandler = static_cast(imageTransferHandler); + MTROTAUnsolicitedBDXMessageHandler::IncrementNumberOfDelegates(); +} + +void MTROTAUnsolicitedBDXMessageHandler::OnDelegateDestroyed(void * imageTransferHandler) +{ + assertChipStackLockedByCurrentThread(); + + // TODO: #36181 - Remove the object matching imageTransferHandler in the set of MTROTAImageTransferHandler objects. + if (mOTAImageTransferHandler == static_cast(imageTransferHandler)) + { + mOTAImageTransferHandler = nullptr; + } + MTROTAUnsolicitedBDXMessageHandler::DecrementNumberOfDelegates(); +} diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 8a39c63440f9ab..d4099178b8ff83 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -381,6 +381,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, ); }; }; @@ -828,6 +832,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 = ""; }; @@ -1272,6 +1280,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 */, @@ -1701,6 +1713,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 */, @@ -1745,6 +1758,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 */, @@ -2011,6 +2025,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 */, @@ -2046,6 +2061,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..eb0572249cca74 --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.cpp @@ -0,0 +1,202 @@ +/* + * 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 { + +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; +} + +CHIP_ERROR AsyncTransferFacilitator::Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, + System::Clock::Timeout timeout) +{ + VerifyOrReturnError(layer != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(!mExchange, CHIP_ERROR_INCORRECT_STATE); + + mSystemLayer = layer; + mExchange.Grab(exchangeCtx); + mTimeout = timeout; + return CHIP_NO_ERROR; +} + +/** + * 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::ProcessOutputEvents() +{ + if (mProcessingOutputEvents) + { + ChipLogDetail(BDX, "ProcessOutputEvents: Still getting and processing output events from a previous call. Return."); + return; + } + + mProcessingOutputEvents = 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) + { + CHIP_ERROR err = SendMessage(outEvent.msgTypeData, outEvent.MsgData); + + // If we failed to send the message across the exchange, there is no way to let the other side know there was an + // error so call DestroySelf so the exchange can be released and the other side will be notified the exchange is + // closing and will clean up. + if (err != CHIP_NO_ERROR) + { + DestroySelf(); + return; + } + + // If we send out a status report across the exchange, schedule a call to DestroySelf() at a little bit later time + // since we want the message to be sent before we clean up. + if (outEvent.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) + { + VerifyOrReturn(mSystemLayer != nullptr); + mSystemLayer->ScheduleWork( + [](auto * systemLayer, auto * appState) -> void { + auto * _this = static_cast(appState); + _this->DestroySelf(); + }, + this); + return; + } + } + else + { + HandleTransferSessionOutput(outEvent); + } + mTransfer.GetNextAction(outEvent); + } + mProcessingOutputEvents = 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); + return ec->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(msgBuf), sendFlags); +} + +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 ProcessOutputEvents 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(); + } + + ProcessOutputEvents(); + return err; +} + +void AsyncTransferFacilitator::OnResponseTimeout(Messaging::ExchangeContext * ec) +{ + ChipLogDetail(BDX, "OnResponseTimeout, ec: " ChipLogFormatExchange, ChipLogValueExchange(ec)); + DestroySelf(); +} + +CHIP_ERROR AsyncResponder::Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, + System::Clock::Timeout timeout) +{ + AsyncTransferFacilitator::Init(layer, exchangeCtx, timeout); + ReturnErrorOnFailure(mTransfer.WaitForTransfer(role, xferControlOpts, maxBlockSize, timeout)); + return CHIP_NO_ERROR; +} + +void AsyncResponder::NotifyEventHandled(const TransferSession::OutputEvent & event, CHIP_ERROR status) +{ + ChipLogDetail(BDX, "NotifyEventHandled : Event %s Error %" CHIP_ERROR_FORMAT, TransferSession::OutputEvent::TypeToString(event.EventType), status.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 DestroySelf(). + if (event.EventType == TransferSession::OutputEventType::kAckEOFReceived || + event.EventType == TransferSession::OutputEventType::kInternalError || + event.EventType == TransferSession::OutputEventType::kTransferTimeout || + event.EventType == TransferSession::OutputEventType::kStatusReceived) + { + DestroySelf(); + 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 ProcessOutputEvents below. + if (status != CHIP_NO_ERROR) + { + mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(status)); + } + + ProcessOutputEvents(); +} + +} // namespace bdx +} // namespace chip diff --git a/src/protocols/bdx/AsyncTransferFacilitator.h b/src/protocols/bdx/AsyncTransferFacilitator.h new file mode 100644 index 00000000000000..b78c8979aceb30 --- /dev/null +++ b/src/protocols/bdx/AsyncTransferFacilitator.h @@ -0,0 +1,132 @@ +/* + * 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 receiving and sending BDX messages on an ExchangeContext. It interacts with the Transfer + * Session state machine to process the received messages and send any outgoing messages. + * Note: If the relevant fabric shuts down, it is the responsibility of the subclass that implements the HandleTransferSessionOutput + * to destroy this object and its subclasses. + * + * 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; + +protected: + CHIP_ERROR Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, System::Clock::Timeout timeout); + + // If subclasses override this method and they call the superclass's OnMessageReceived, the superclass + // may destroy the subclass's object. + CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) override; + + // If subclasses override this method and they call the superclass's OnResponseTimeout, the superclass + // may destroy the subclass's object. + void OnResponseTimeout(Messaging::ExchangeContext * ec) override; + + static bdx::StatusCode GetBdxStatusCodeFromChipError(CHIP_ERROR err); + + /** + * 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; + + void ProcessOutputEvents(); + + // The transfer session coresponding to this AsyncTransferFacilitator object. + TransferSession mTransfer; + +private: + bool mProcessingOutputEvents = false; + + // 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; + + CHIP_ERROR SendMessage(const TransferSession::MessageTypeData msgTypeData, System::PacketBufferHandle & msgBuf); +}; + +/** + * An AsyncTransferFacilitator that is initialized to respond to an incoming BDX transfer request. + * An AsyncResponder object is associated with an exchange and handles all BDX messages sent over that exchange. + * + * 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 Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, TransferRole role, + BitFlags xferControlOpts, uint16_t maxBlockSize, System::Clock::Timeout timeout); + + /** + * Method that must be called by the subclass implementing HandleTransferSessionOutput to notify the AsyncResponder + * that it has handled the OutputEvent specified in "event" and "status" is the result of handling the event. + * + * Every call to HandleTransferSessionOutput must result in a call to NotifyEventHandled. The call + * to NotifyEventHandled may happen before HandleTransferSessionOutput returns, or may happen + * later, asynchronously. + * + * @param[in] event The OutputEvent that was handled by the subclass. + * @param[in] status The error code that occured when handling the event if an error occurs. Otherwise CHIP_NO_ERROR. + */ + void NotifyEventHandled(const TransferSession::OutputEvent & event, CHIP_ERROR status); +}; + +} // 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", diff --git a/src/protocols/bdx/BdxTransferSession.cpp b/src/protocols/bdx/BdxTransferSession.cpp index 0a513d7ea77460..997958fb01b377 100644 --- a/src/protocols/bdx/BdxTransferSession.cpp +++ b/src/protocols/bdx/BdxTransferSession.cpp @@ -948,6 +948,12 @@ const char * TransferSession::OutputEvent::ToString(OutputEventType outputEventT } } +const char * TransferSession::OutputEvent::TypeToString(const OutputEventType outputEventType) +{ + OutputEvent event(outputEventType); + return event.ToString(outputEventType); +} + TransferSession::OutputEvent TransferSession::OutputEvent::TransferInitEvent(TransferInitData data, System::PacketBufferHandle msg) { OutputEvent event(OutputEventType::kInitReceived); diff --git a/src/protocols/bdx/BdxTransferSession.h b/src/protocols/bdx/BdxTransferSession.h index 4a50e70fbb6c84..d4d83380557d74 100644 --- a/src/protocols/bdx/BdxTransferSession.h +++ b/src/protocols/bdx/BdxTransferSession.h @@ -134,7 +134,9 @@ class DLL_EXPORT TransferSession OutputEvent() : EventType(OutputEventType::kNone) { statusData = { StatusCode::kUnknown }; } OutputEvent(OutputEventType type) : EventType(type) { statusData = { StatusCode::kUnknown }; } - const char * ToString(OutputEventType outputEventType); + const char * ToString(const OutputEventType outputEventType); + + static const char * TypeToString(const OutputEventType outputEventType); static OutputEvent TransferInitEvent(TransferInitData data, System::PacketBufferHandle msg); static OutputEvent TransferAcceptEvent(TransferAcceptData data);