blob: 7d5e8c165669bdb8d0b312778d8db5e3c09e72c1 [file] [log] [blame]
/**
*
* Copyright (c) 2022 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 "MTROTAProviderDelegateBridge.h"
#import "MTRDeviceControllerFactory_Internal.h"
#import "MTRDeviceController_Internal.h"
#import "NSDataSpanConversion.h"
#import "NSStringSpanConversion.h"
#include <app/clusters/ota-provider/ota-provider.h>
#include <controller/CHIPDeviceController.h>
#include <lib/support/TypeTraits.h>
#include <platform/PlatformManager.h>
#include <protocols/interaction_model/Constants.h>
#include <MTRError_Internal.h>
#include <messaging/ExchangeMgr.h>
#include <platform/LockTracker.h>
#include <protocols/bdx/BdxUri.h>
#include <protocols/bdx/TransferFacilitator.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters::OtaSoftwareUpdateProvider;
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;
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(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(ConfigureState(fabricIndex, nodeId));
BitFlags<bdx::TransferControlFlags> 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);
mExchangeMgr = nullptr;
mSystemLayer = nullptr;
mDelegateNotificationQueue = nil;
ResetState();
return CHIP_NO_ERROR;
}
void ControllerShuttingDown(MTRDeviceController * controller)
{
assertChipStackLockedByCurrentThread();
if (mInitialized && mFabricIndex.Value() == controller.fabricIndex) {
ResetState();
}
}
void SetDelegate(id<MTROTAProviderDelegate> delegate, dispatch_queue_t delegateNotificationQueue)
{
if (delegate) {
mDelegate = delegate;
mDelegateNotificationQueue = delegateNotificationQueue;
} else {
ResetState();
mDelegate = nil;
mDelegateNotificationQueue = nil;
}
}
private:
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;
return mExchangeCtx->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(event.MsgData), sendFlags);
}
CHIP_ERROR 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);
auto fileDesignator = [[NSString alloc] initWithBytes:fd length:fdl encoding:NSUTF8StringEncoding];
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:^(chip::Controller::DeviceCommissioner *) {
assertChipStackLockedByCurrentThread();
if (!mInitialized || mTransferGeneration != transferGeneration) {
// Callback for a stale transfer.
return;
}
if (error != nil) {
LogErrorOnFailure([MTRError errorToCHIPErrorCode:error]);
LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
return;
}
// bdx::TransferSession will automatically reject a transfer if there are no
// common supported control modes. It will also default to the smaller
// block size.
TransferSession::TransferAcceptData acceptData;
acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive;
acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize();
acceptData.StartOffset = mTransfer.GetStartOffset();
acceptData.Length = mTransfer.GetTransferLength();
LogErrorOnFailure(mTransfer.AcceptTransfer(acceptData));
}
errorHandler:^(NSError *) {
// Not much we can do here
}];
};
auto nodeId = @(mNodeId.Value());
auto strongDelegate = mDelegate;
dispatch_async(mDelegateNotificationQueue, ^{
if ([strongDelegate respondsToSelector:@selector
(handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)]) {
[strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId
controller:controller
fileDesignator:fileDesignator
offset:offset
completion:completionHandler];
} else {
[strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId
controller:controller
fileDesignator:fileDesignator
offset:offset
completionHandler:completionHandler];
}
});
return CHIP_NO_ERROR;
}
CHIP_ERROR OnTransferSessionEnd(TransferSession::OutputEvent & event)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR error = CHIP_NO_ERROR;
if (event.EventType == TransferSession::OutputEventType::kTransferTimeout) {
error = CHIP_ERROR_TIMEOUT;
} else if (event.EventType != TransferSession::OutputEventType::kAckEOFReceived) {
error = CHIP_ERROR_INTERNAL;
}
auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()];
VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);
auto nodeId = @(mNodeId.Value());
auto strongDelegate = mDelegate;
dispatch_async(mDelegateNotificationQueue, ^{
[strongDelegate handleBDXTransferSessionEndForNodeID:nodeId
controller:controller
error:[MTRError errorForCHIPErrorCode:error]];
});
ResetState();
return CHIP_NO_ERROR;
}
CHIP_ERROR OnBlockQuery(TransferSession::OutputEvent & event)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE);
auto blockSize = @(mTransfer.GetTransferBlockSize());
auto blockIndex = @(mTransfer.GetNextBlockNum());
auto bytesToSkip = @(0);
if (event.EventType == TransferSession::OutputEventType::kQueryWithSkipReceived) {
bytesToSkip = @(event.bytesToSkip.BytesToSkip);
}
auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()];
VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);
auto transferGeneration = mTransferGeneration;
auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) {
[controller
asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
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<const uint8_t *>([data bytes]);
blockData.Length = static_cast<size_t>([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);
break;
case TransferSession::OutputEventType::kStatusReceived:
ChipLogError(BDX, "Got StatusReport %x", static_cast<uint16_t>(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();
}
mFabricIndex.SetValue(fabricIndex);
mNodeId.SetValue(nodeId);
mInitialized = true;
return CHIP_NO_ERROR;
}
void ResetState()
{
assertChipStackLockedByCurrentThread();
if (!mInitialized) {
return;
}
Responder::ResetTransfer();
++mTransferGeneration;
mFabricIndex.ClearValue();
mNodeId.ClearValue();
if (mExchangeCtx != nullptr) {
mExchangeCtx->Close();
mExchangeCtx = nullptr;
}
mInitialized = false;
}
bool mInitialized = false;
Optional<FabricIndex> mFabricIndex;
Optional<NodeId> mNodeId;
id<MTROTAProviderDelegate> 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;
};
BdxOTASender gOtaSender;
static NSInteger const kOtaProviderEndpoint = 0;
MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge(id<MTROTAProviderDelegate> delegate)
: mDelegate(delegate)
, mDelegateNotificationQueue(dispatch_queue_create("com.csa.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL))
{
gOtaSender.SetDelegate(delegate, mDelegateNotificationQueue);
Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this);
}
MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge()
{
gOtaSender.SetDelegate(nil, nil);
Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr);
}
CHIP_ERROR MTROTAProviderDelegateBridge::Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeManager)
{
return gOtaSender.Init(systemLayer, exchangeManager);
}
void MTROTAProviderDelegateBridge::Shutdown() { gOtaSender.Shutdown(); }
void MTROTAProviderDelegateBridge::ControllerShuttingDown(MTRDeviceController * controller)
{
gOtaSender.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
// error status to the CommandHandler.
//
// Otherwise set outNodeId and outController to values that identify the source
// node for the command.
bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath & commandPath, NodeId * outNodeId,
MTRDeviceController * __autoreleasing _Nonnull * _Nonnull outController)
{
auto desc = commandHandler->GetSubjectDescriptor();
if (desc.authMode != Access::AuthMode::kCase) {
commandHandler->AddStatus(commandPath, Status::Failure);
return false;
}
auto * controller =
[[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:commandHandler->GetAccessingFabricIndex()];
if (controller == nil) {
commandHandler->AddStatus(commandPath, Status::Failure);
return false;
}
*outController = controller;
*outNodeId = desc.subject;
return true;
}
// Ensures we have a usable CommandHandler and do not have an error.
//
// When this function returns non-null, it's safe to go ahead and use the return
// value to send a response.
//
// When this function returns null, the CommandHandler::Handle should not be
// used anymore.
CommandHandler * _Nullable EnsureValidState(
CommandHandler::Handle & handle, const ConcreteCommandPath & cachedCommandPath, const char * prefix, NSError * _Nullable error)
{
CommandHandler * handler = handle.Get();
if (handler == nullptr) {
ChipLogError(Controller, "%s: no CommandHandler to send response", prefix);
return nullptr;
}
if (error != nil) {
auto * desc = [error description];
auto err = [MTRError errorToCHIPErrorCode:error];
ChipLogError(Controller, "%s: application returned error: '%s', sending error: '%s'", prefix,
[desc cStringUsingEncoding:NSUTF8StringEncoding], chip::ErrorStr(err));
handler->AddStatus(cachedCommandPath, StatusIB(err).mStatus);
handle.Release();
return nullptr;
}
return handler;
}
// Ensures we have a usable CommandHandler and that our args don't involve any
// errors, for the case when we have data to send back.
//
// When this function returns non-null, it's safe to go ahead and use whatever
// object "data" points to to add a response to the command.
//
// When this function returns null, the CommandHandler::Handle should not be
// used anymore.
CommandHandler * _Nullable EnsureValidState(CommandHandler::Handle & handle, const ConcreteCommandPath & cachedCommandPath,
const char * prefix, NSObject * _Nullable data, NSError * _Nullable error)
{
CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, prefix, error);
VerifyOrReturnValue(handler != nullptr, nullptr);
if (data == nil) {
ChipLogError(Controller, "%s: no data to send as a response", prefix);
handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
handle.Release();
return nullptr;
}
return handler;
}
} // anonymous namespace
void MTROTAProviderDelegateBridge::HandleQueryImage(
CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::QueryImage::DecodableType & commandData)
{
assertChipStackLockedByCurrentThread();
NodeId nodeId;
MTRDeviceController * controller;
if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
return;
}
auto * commandParams = [[MTROTASoftwareUpdateProviderClusterQueryImageParams alloc] init];
CHIP_ERROR err = ConvertToQueryImageParams(commandData, commandParams);
if (err != CHIP_NO_ERROR) {
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand);
return;
}
// Make sure to hold on to the command handler and command path to be used in the completion block
__block CommandHandler::Handle handle(commandObj);
__block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId);
auto completionHandler
= ^(MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
[controller
asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
assertChipStackLockedByCurrentThread();
CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "QueryImage", data, error);
VerifyOrReturn(handler != nullptr);
ChipLogDetail(Controller, "QueryImage: application responded with: %s",
[[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
Commands::QueryImageResponse::Type response;
ConvertFromQueryImageResponseParms(data, response);
auto hasUpdate = [data.status isEqual:@(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)];
auto isBDXProtocolSupported = [commandParams.protocolsSupported
containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
if (hasUpdate && isBDXProtocolSupported) {
auto fabricIndex = handler->GetSubjectDescriptor().fabricIndex;
auto nodeId = handler->GetSubjectDescriptor().subject;
CHIP_ERROR err = gOtaSender.PrepareForTransfer(fabricIndex, nodeId);
if (CHIP_NO_ERROR != err) {
LogErrorOnFailure(err);
handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
handle.Release();
return;
}
auto targetNodeId
= handler->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId();
char uriBuffer[kMaxBDXURILen];
MutableCharSpan uri(uriBuffer);
err = bdx::MakeURI(targetNodeId.GetNodeId(), AsCharSpan(data.imageURI), uri);
if (CHIP_NO_ERROR != err) {
LogErrorOnFailure(err);
handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
handle.Release();
return;
}
response.imageURI.SetValue(uri);
handler->AddResponse(cachedCommandPath, response);
handle.Release();
return;
}
handler->AddResponse(cachedCommandPath, response);
handle.Release();
}
errorHandler:^(NSError *) {
// Not much we can do here
}];
};
auto strongDelegate = mDelegate;
dispatch_async(mDelegateNotificationQueue, ^{
if ([strongDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completion:)]) {
[strongDelegate handleQueryImageForNodeID:@(nodeId)
controller:controller
params:commandParams
completion:completionHandler];
} else {
// Cast is safe because subclass does not add any selectors.
[strongDelegate
handleQueryImageForNodeID:@(nodeId)
controller:controller
params:static_cast<MTROtaSoftwareUpdateProviderClusterQueryImageParams *>(commandParams)
completionHandler:^(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data,
NSError * _Nullable error) {
completionHandler(data, error);
}];
}
});
}
void MTROTAProviderDelegateBridge::HandleApplyUpdateRequest(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::ApplyUpdateRequest::DecodableType & commandData)
{
assertChipStackLockedByCurrentThread();
NodeId nodeId;
MTRDeviceController * controller;
if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
return;
}
// Make sure to hold on to the command handler and command path to be used in the completion block
__block CommandHandler::Handle handle(commandObj);
__block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId);
auto completionHandler
= ^(MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, NSError * _Nullable error) {
[controller
asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
assertChipStackLockedByCurrentThread();
CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "ApplyUpdateRequest", data, error);
VerifyOrReturn(handler != nullptr);
ChipLogDetail(Controller, "ApplyUpdateRequest: application responded with: %s",
[[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
Commands::ApplyUpdateResponse::Type response;
ConvertFromApplyUpdateRequestResponseParms(data, response);
handler->AddResponse(cachedCommandPath, response);
handle.Release();
}
errorHandler:^(NSError *) {
// Not much we can do here
}];
};
auto * commandParams = [[MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams alloc] init];
ConvertToApplyUpdateRequestParams(commandData, commandParams);
auto strongDelegate = mDelegate;
dispatch_async(mDelegateNotificationQueue, ^{
if ([strongDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)]) {
[strongDelegate handleApplyUpdateRequestForNodeID:@(nodeId)
controller:controller
params:commandParams
completion:completionHandler];
} else {
// Cast is safe because subclass does not add any selectors.
[strongDelegate
handleApplyUpdateRequestForNodeID:@(nodeId)
controller:controller
params:static_cast<MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams *>(
commandParams)
completionHandler:^(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data,
NSError * _Nullable error) {
completionHandler(data, error);
}];
}
});
}
void MTROTAProviderDelegateBridge::HandleNotifyUpdateApplied(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::NotifyUpdateApplied::DecodableType & commandData)
{
assertChipStackLockedByCurrentThread();
NodeId nodeId;
MTRDeviceController * controller;
if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
return;
}
// Make sure to hold on to the command handler and command path to be used in the completion block
__block CommandHandler::Handle handle(commandObj);
__block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId);
auto completionHandler = ^(NSError * _Nullable error) {
[controller
asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
assertChipStackLockedByCurrentThread();
CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "NotifyUpdateApplied", error);
VerifyOrReturn(handler != nullptr);
handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Success);
handle.Release();
}
errorHandler:^(NSError *) {
// Not much we can do here
}];
};
auto * commandParams = [[MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams alloc] init];
ConvertToNotifyUpdateAppliedParams(commandData, commandParams);
auto strongDelegate = mDelegate;
dispatch_async(mDelegateNotificationQueue, ^{
if ([strongDelegate respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completion:)]) {
[strongDelegate handleNotifyUpdateAppliedForNodeID:@(nodeId)
controller:controller
params:commandParams
completion:completionHandler];
} else {
// Cast is safe because subclass does not add any selectors.
[strongDelegate
handleNotifyUpdateAppliedForNodeID:@(nodeId)
controller:controller
params:static_cast<MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams *>(
commandParams)
completionHandler:completionHandler];
}
});
}
CHIP_ERROR MTROTAProviderDelegateBridge::ConvertToQueryImageParams(
const Commands::QueryImage::DecodableType & commandData, MTROTASoftwareUpdateProviderClusterQueryImageParams * commandParams)
{
commandParams.vendorId = [NSNumber numberWithUnsignedShort:commandData.vendorId];
commandParams.productId = [NSNumber numberWithUnsignedShort:commandData.productId];
commandParams.softwareVersion = [NSNumber numberWithUnsignedLong:commandData.softwareVersion];
auto iterator = commandData.protocolsSupported.begin();
NSMutableArray * protocolsSupported = [[NSMutableArray alloc] init];
while (iterator.Next()) {
OTADownloadProtocol protocol = iterator.GetValue();
[protocolsSupported addObject:[NSNumber numberWithInt:to_underlying(protocol)]];
}
ReturnErrorOnFailure(iterator.GetStatus());
commandParams.protocolsSupported = protocolsSupported;
if (commandData.hardwareVersion.HasValue()) {
commandParams.hardwareVersion = [NSNumber numberWithUnsignedShort:commandData.hardwareVersion.Value()];
}
if (commandData.location.HasValue()) {
commandParams.location = AsString(commandData.location.Value());
}
if (commandData.requestorCanConsent.HasValue()) {
commandParams.requestorCanConsent = [NSNumber numberWithBool:commandData.requestorCanConsent.Value()];
}
if (commandData.metadataForProvider.HasValue()) {
commandParams.metadataForProvider = AsData(commandData.metadataForProvider.Value());
}
return CHIP_NO_ERROR;
}
void MTROTAProviderDelegateBridge::ConvertFromQueryImageResponseParms(
const MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * responseParams,
Commands::QueryImageResponse::Type & response)
{
response.status = static_cast<OTAQueryStatus>([responseParams.status intValue]);
if (responseParams.delayedActionTime) {
response.delayedActionTime.SetValue([responseParams.delayedActionTime unsignedIntValue]);
}
if (responseParams.imageURI) {
response.imageURI.SetValue(AsCharSpan(responseParams.imageURI));
}
if (responseParams.softwareVersion) {
response.softwareVersion.SetValue([responseParams.softwareVersion unsignedIntValue]);
}
if (responseParams.softwareVersionString) {
response.softwareVersionString.SetValue(AsCharSpan(responseParams.softwareVersionString));
}
if (responseParams.updateToken) {
response.updateToken.SetValue(AsByteSpan(responseParams.updateToken));
}
if (responseParams.userConsentNeeded) {
response.userConsentNeeded.SetValue([responseParams.userConsentNeeded boolValue]);
}
if (responseParams.metadataForRequestor) {
response.metadataForRequestor.SetValue(AsByteSpan(responseParams.metadataForRequestor));
}
}
void MTROTAProviderDelegateBridge::ConvertToApplyUpdateRequestParams(
const Commands::ApplyUpdateRequest::DecodableType & commandData,
MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams * commandParams)
{
commandParams.updateToken = AsData(commandData.updateToken);
commandParams.newVersion = [NSNumber numberWithUnsignedLong:commandData.newVersion];
}
void MTROTAProviderDelegateBridge::ConvertFromApplyUpdateRequestResponseParms(
const MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * responseParams,
Commands::ApplyUpdateResponse::Type & response)
{
response.action = static_cast<OTAApplyUpdateAction>([responseParams.action intValue]);
response.delayedActionTime = [responseParams.delayedActionTime unsignedIntValue];
}
void MTROTAProviderDelegateBridge::ConvertToNotifyUpdateAppliedParams(
const Commands::NotifyUpdateApplied::DecodableType & commandData,
MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams)
{
commandParams.updateToken = AsData(commandData.updateToken);
commandParams.softwareVersion = [NSNumber numberWithUnsignedLong:commandData.softwareVersion];
}