From 372609448b94d82a9466cae3a04b0a04c25e9164 Mon Sep 17 00:00:00 2001 From: Sergei Lissianoi <54454955+selissia@users.noreply.github.com> Date: Tue, 30 Nov 2021 11:29:27 -0500 Subject: [PATCH] OTA Requestor Refactoring -- Part 1 (#12119) * Introduce ota-requestor.h containing Requestor API declarations * Split OTA Requestor class declarations into multiple headers * Restyled by whitespace * Restyled by clang-format * Clean up comments and function names * Misc API format changes based on PR comments * Restyled by clang-format * First take at moving the core Requestor logic out of the Linux example * Further OTA Requestor refactoring -- moving logic out of Linux example app * Further changes for OTA Rerquestor refactoring * More OTA Requestor refactoring changes * Refactoring OTA Requestor. Code compiles and links with this commit * Further OTA Requestor refactoring. Requestor and chip-tool build successfully. The top-level build fails for all-cluster app. * OTA Refactor:Only build clusters/ota-requestor in the context of Requestor app Update .gn files so that clusters/ota-requestor is not built for all example apps (this is different from the rest of the clusters). Instead, explicitly specify clusters/ota-requestor sources in the OTA-Requestor example app * OTA-Requestor: Initialize mOtaStartDelayMs * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Push submodule pointers * Clean up an extra statement introduced by mistake * Remove ExampleOTARequestor.cpp/h as they are replaced by the OTARequestor.cpp/h * Add OTA Requestor README * OTARequestor refactoring. With this the image transfer scenario works on Linux: rm -r /tmp/chip_* ./out/debug/chip-ota-provider-app -f /tmp/ota.txt ./out/chip-tool pairing onnetwork 1 20202021 ./out/debug/chip-ota-requestor-app -u 5560 -d 42 -i ::1 ./out/chip-tool pairing onnetwork-long 2 20202021 42 ./out/chip-tool otasoftwareupdaterequestor announce-ota-provider 1 0 0 2 0 * Requestor refactoring: Enable automatic ImageQuery in Test Mode * OTA Requestor refactoring -- add LinuxOTARequestorDriver.cpp, comments * Requestor refactor: Rename and clean up files * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Restyled by prettier-markdown * OTA Requestor changes * Remove unused variable Co-authored-by: Restyled.io --- .github/.wordlist.txt | 8 + examples/ota-requestor-app/linux/BUILD.gn | 5 +- .../linux/LinuxOTAImageProcessor.h | 45 ++ .../linux/LinuxOTARequestorDriver.cpp | 34 ++ .../linux/LinuxOTARequestorDriver.h | 38 ++ examples/ota-requestor-app/linux/main.cpp | 200 ++------- .../ota-requestor-common/BUILD.gn | 8 +- .../ExampleOTARequestor.cpp | 118 ------ .../ExampleOTARequestor.h | 59 --- .../clusters/ota-requestor}/BDXDownloader.cpp | 0 .../clusters/ota-requestor}/BDXDownloader.h | 0 .../ota-requestor/ClusterInterface.cpp | 52 +++ .../{ota-downloader.h => OTADownloader.h} | 19 +- ...-image-processor.h => OTAImageProcessor.h} | 8 +- .../clusters/ota-requestor/OTARequestor.cpp | 389 ++++++++++++++++++ src/app/clusters/ota-requestor/OTARequestor.h | 96 +++++ ...equestor-driver.h => OTARequestorDriver.h} | 3 + ...or-interface.h => OTARequestorInterface.h} | 29 +- src/app/clusters/ota-requestor/README.md | 50 +++ .../clusters/ota-requestor/ota-requestor.h | 49 --- 20 files changed, 809 insertions(+), 401 deletions(-) create mode 100644 examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h create mode 100644 examples/ota-requestor-app/linux/LinuxOTARequestorDriver.cpp create mode 100644 examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h delete mode 100644 examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.cpp delete mode 100644 examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.h rename {examples/ota-requestor-app/ota-requestor-common => src/app/clusters/ota-requestor}/BDXDownloader.cpp (100%) rename {examples/ota-requestor-app/ota-requestor-common => src/app/clusters/ota-requestor}/BDXDownloader.h (100%) create mode 100644 src/app/clusters/ota-requestor/ClusterInterface.cpp rename src/app/clusters/ota-requestor/{ota-downloader.h => OTADownloader.h} (78%) rename src/app/clusters/ota-requestor/{ota-image-processor.h => OTAImageProcessor.h} (88%) create mode 100644 src/app/clusters/ota-requestor/OTARequestor.cpp create mode 100644 src/app/clusters/ota-requestor/OTARequestor.h rename src/app/clusters/ota-requestor/{ota-requestor-driver.h => OTARequestorDriver.h} (96%) rename src/app/clusters/ota-requestor/{ota-requestor-interface.h => OTARequestorInterface.h} (58%) create mode 100644 src/app/clusters/ota-requestor/README.md delete mode 100644 src/app/clusters/ota-requestor/ota-requestor.h diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 07fe32993db502..ac58409264db79 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -549,6 +549,7 @@ lightin LightingColor LightingState LinkSoftwareAndDocumentationPack +LinuxOTARequestorDriver LocalConfigDisabled localhost localstatedir @@ -701,9 +702,13 @@ optionsMask optionsOverride orgs OTA +OTADownloader +OTAImageProcessorDriver OTAProviderIpAddress OTAProviderNodeId OTAProviderSerialPort +OTARequestor +OTARequestorDriver OTARequesterImpl OTARequestorSerialPort OTBR @@ -860,7 +865,10 @@ SERIALDEVICE SerialNumber ServiceId SetDns +SetImageProcessorDelegate +SetOtaRequestorDriver SetpointRaiseLower +SetRequestorInstance SetUpPINCode SetupQRCode sexualized diff --git a/examples/ota-requestor-app/linux/BUILD.gn b/examples/ota-requestor-app/linux/BUILD.gn index 2d698377703640..aa54192e4333d7 100644 --- a/examples/ota-requestor-app/linux/BUILD.gn +++ b/examples/ota-requestor-app/linux/BUILD.gn @@ -16,7 +16,10 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") executable("chip-ota-requestor-app") { - sources = [ "main.cpp" ] + sources = [ + "LinuxOTARequestorDriver.cpp", + "main.cpp", + ] deps = [ "${chip_root}/examples/ota-requestor-app/ota-requestor-common", diff --git a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h new file mode 100644 index 00000000000000..2a5a41ad2de52f --- /dev/null +++ b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the decalarions for the Linux implementation of the + * the OTAImageProcessorDriver interface class + */ + +#include "app/clusters/ota-requestor/OTAImageProcessor.h" + +class LinuxOTAImageProcessor : public OTAImageProcessorDriver +{ + + // Virtuial functions from OTAImageProcessorDriver -- start + // Open file, find block of space in persistent memory, or allocate a buffer, etc. + CHIP_ERROR PrepareDownload() { return CHIP_NO_ERROR; } + + // Must not be a blocking call to support cases that require IO to elements such as // external peripherals/radios + CHIP_ERROR ProcessBlock(chip::ByteSpan & data) { return CHIP_NO_ERROR; } + + // Close file, close persistent storage, etc + CHIP_ERROR Finalize() { return CHIP_NO_ERROR; } + + chip::Optional PercentComplete() { return chip::Optional(0); } + + // Clean up the download which could mean erasing everything that was written, + // releasing buffers, etc. + CHIP_ERROR Abort() { return CHIP_NO_ERROR; } + + // Virtuial functions from OTAImageProcessorDriver -- end +}; diff --git a/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.cpp b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.cpp new file mode 100644 index 00000000000000..fb971ffbdb58a6 --- /dev/null +++ b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.cpp @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the Linux implementation of the OTAImageProcessorDriver + * interface class + */ + +#include "LinuxOTARequestorDriver.h" + +// A call into the application logic to give it a chance to allow or stop the Requestor +// from proceeding with actual image download. Returning TRUE will allow the download +// to proceed, returning FALSE will abort the download process. +bool LinuxOTARequestorDriver::CheckImageDownloadAllowed() +{ + return true; +} + +// Notify the application that the download is complete and the image can be applied +void LinuxOTARequestorDriver::ImageDownloadComplete() {} diff --git a/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h new file mode 100644 index 00000000000000..c219aa5c679f05 --- /dev/null +++ b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the decalarions for the Linux implementation of the + * the OTARequestorDriver interface class + */ +#include "app/clusters/ota-requestor/OTARequestorDriver.h" + +class LinuxOTARequestorDriver : public OTARequestorDriver +{ + + // Virtual functions from OTARequestorDriver -- start + + // A call into the application logic to give it a chance to allow or stop the Requestor + // from proceeding with actual image download. Returning TRUE will allow the download + // to proceed, returning FALSE will abort the download process. + bool CheckImageDownloadAllowed(); + + // Notify the application that the download is complete and the image can be applied + void ImageDownloadComplete(); + + // Virtual functions from OTARequestorDriver -- end +}; diff --git a/examples/ota-requestor-app/linux/main.cpp b/examples/ota-requestor-app/linux/main.cpp index 08dd38e3d21a64..e4aa27ee85ddf4 100644 --- a/examples/ota-requestor-app/linux/main.cpp +++ b/examples/ota-requestor-app/linux/main.cpp @@ -16,23 +16,17 @@ * limitations under the License. */ -#include -#include #include #include -#include -#include -#include #include -#include #include #include #include -#include -#include -#include "BDXDownloader.h" -#include "ExampleOTARequestor.h" +#include "LinuxOTAImageProcessor.h" +#include "LinuxOTARequestorDriver.h" +#include "app/clusters/ota-requestor/OTADownloader.h" +#include "app/clusters/ota-requestor/OTARequestor.h" using chip::ByteSpan; using chip::CharSpan; @@ -45,7 +39,6 @@ using chip::OnDeviceConnectionFailure; using chip::PeerId; using chip::Server; using chip::VendorId; -using chip::bdx::TransferSession; using chip::Callback::Callback; using chip::Inet::IPAddress; using chip::System::Layer; @@ -54,17 +47,8 @@ using namespace chip::ArgParser; using namespace chip::Messaging; using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands; -void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response); -void OnQueryImageFailure(void * context, EmberAfStatus status); -void OnConnected(void * context, chip::OperationalDeviceProxy * deviceProxy); -void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error); bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue); - -// TODO: would be nicer to encapsulate these globals and the callbacks in some sort of class -ExchangeContext * exchangeCtx = nullptr; -BdxDownloader bdxDownloader; -Callback mOnConnectedCallback(OnConnected, nullptr); -Callback mOnConnectionFailureCallback(OnConnectionFailure, nullptr); +void OnStartDelayTimerHandler(Layer * systemLayer, void * appState); constexpr uint16_t kOptionProviderNodeId = 'n'; constexpr uint16_t kOptionProviderFabricIndex = 'f'; @@ -113,90 +97,6 @@ HelpOptions helpOptions("ota-requestor-app", "Usage: ota-requestor-app [options] OptionSet * allOptions[] = { &cmdLineOptions, &helpOptions, nullptr }; -void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) -{ - ChipLogDetail(SoftwareUpdate, "QueryImageResponse responded with action %" PRIu8, response.status); - - TransferSession::TransferInitData initOptions; - initOptions.TransferCtlFlags = chip::bdx::TransferControlFlags::kReceiverDrive; - initOptions.MaxBlockSize = 1024; - char testFileDes[9] = { "test.txt" }; - initOptions.FileDesLength = static_cast(strlen(testFileDes)); - initOptions.FileDesignator = reinterpret_cast(testFileDes); - - chip::OperationalDeviceProxy * operationalDeviceProxy = Server::GetInstance().GetOperationalDeviceProxy(); - if (operationalDeviceProxy != nullptr) - { - chip::Messaging::ExchangeManager * exchangeMgr = operationalDeviceProxy->GetExchangeManager(); - chip::Optional session = operationalDeviceProxy->GetSecureSession(); - if (exchangeMgr != nullptr && session.HasValue()) - { - exchangeCtx = exchangeMgr->NewContext(session.Value(), &bdxDownloader); - } - - if (exchangeCtx == nullptr) - { - ChipLogError(BDX, "unable to allocate ec: exchangeMgr=%p sessionExists? %u", exchangeMgr, session.HasValue()); - return; - } - } - - bdxDownloader.SetInitialExchange(exchangeCtx); - - // This will kick of a timer which will regularly check for updates to the bdx::TransferSession state machine. - bdxDownloader.InitiateTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kReceiver, initOptions, - chip::System::Clock::Seconds16(20)); -} - -void OnQueryImageFailure(void * context, EmberAfStatus status) -{ - ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); -} - -void OnConnected(void * context, chip::OperationalDeviceProxy * deviceProxy) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - chip::Controller::OtaSoftwareUpdateProviderCluster cluster; - constexpr EndpointId kOtaProviderEndpoint = 0; - - // These QueryImage params have been chosen arbitrarily - constexpr VendorId kExampleVendorId = VendorId::Common; - constexpr uint16_t kExampleProductId = 77; - constexpr uint16_t kExampleHWVersion = 3; - constexpr uint16_t kExampleSoftwareVersion = 0; - constexpr EmberAfOTADownloadProtocol kExampleProtocolsSupported[] = { EMBER_ZCL_OTA_DOWNLOAD_PROTOCOL_BDX_SYNCHRONOUS }; - const char locationBuf[] = { 'U', 'S' }; - CharSpan exampleLocation(locationBuf); - constexpr bool kExampleClientCanConsent = false; - ByteSpan metadata; - - err = cluster.Associate(deviceProxy, kOtaProviderEndpoint); - if (err != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Associate() failed: %" CHIP_ERROR_FORMAT, err.Format()); - return; - } - QueryImage::Type args; - args.vendorId = kExampleVendorId; - args.productId = kExampleProductId; - args.softwareVersion = kExampleSoftwareVersion; - args.protocolsSupported = kExampleProtocolsSupported; - args.hardwareVersion.Emplace(kExampleHWVersion); - args.location.Emplace(exampleLocation); - args.requestorCanConsent.Emplace(kExampleClientCanConsent); - args.metadataForProvider.Emplace(metadata); - err = cluster.InvokeCommand(args, /* context = */ nullptr, OnQueryImageResponse, OnQueryImageFailure); - if (err != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "QueryImage() failed: %" CHIP_ERROR_FORMAT, err.Format()); - } -} - -void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error) -{ - ChipLogError(SoftwareUpdate, "failed to connect to 0x%" PRIX64 ": %" CHIP_ERROR_FORMAT, deviceId, error.Format()); -} - bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue) { bool retval = true; @@ -252,54 +152,6 @@ bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, return (retval); } -void SendQueryImageCommand(chip::NodeId peerNodeId = providerNodeId, chip::FabricIndex peerFabricIndex = providerFabricIndex) -{ - Server * server = &(Server::GetInstance()); - chip::FabricInfo * fabric = server->GetFabricTable().FindFabricWithIndex(peerFabricIndex); - if (fabric == nullptr) - { - ChipLogError(SoftwareUpdate, "Did not find fabric for index %d", peerFabricIndex); - return; - } - - chip::DeviceProxyInitParams initParams = { - .sessionManager = &(server->GetSecureSessionManager()), - .exchangeMgr = &(server->GetExchangeManager()), - .idAllocator = &(server->GetSessionIDAllocator()), - .fabricInfo = fabric, - // TODO: Determine where this should be instantiated - .imDelegate = chip::Platform::New(), - }; - - chip::OperationalDeviceProxy * operationalDeviceProxy = - chip::Platform::New(initParams, fabric->GetPeerIdForNode(peerNodeId)); - if (operationalDeviceProxy == nullptr) - { - ChipLogError(SoftwareUpdate, "Failed in creating an instance of OperationalDeviceProxy"); - return; - } - - server->SetOperationalDeviceProxy(operationalDeviceProxy); - - // Explicitly calling UpdateDeviceData() should not be needed once OperationalDeviceProxy can resolve IP address from node ID - // and fabric index - IPAddress ipAddr; - IPAddress::FromString(ipAddress, ipAddr); - PeerAddress addr = PeerAddress::UDP(ipAddr, CHIP_PORT); - operationalDeviceProxy->UpdateDeviceData(addr, operationalDeviceProxy->GetMRPConfig()); - - CHIP_ERROR err = operationalDeviceProxy->Connect(&mOnConnectedCallback, &mOnConnectionFailureCallback); - if (err != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Cannot establish connection to peer device: %" CHIP_ERROR_FORMAT, err.Format()); - } -} - -void OnStartDelayTimerHandler(Layer * systemLayer, void * appState) -{ - SendQueryImageCommand(); -} - int main(int argc, char * argv[]) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -340,12 +192,42 @@ int main(int argc, char * argv[]) // Initialize device attestation config SetDeviceAttestationCredentialsProvider(chip::Credentials::Examples::GetExampleDACProvider()); - // This will allow ExampleOTARequestor to call SendQueryImageCommand - ExampleOTARequestor::GetInstance().SetConnectToProviderCallback(SendQueryImageCommand); + // Initialize and interconnect the Requestor and Image Processor objects -- START + // Initialize the instance of the main Requestor Class + OTARequestor * requestorCore = new OTARequestor; + SetRequestorInstance(requestorCore); + + // Initialize an instance of the Requestor Driver + LinuxOTARequestorDriver * requestorUser = new LinuxOTARequestorDriver; + + // Connect the Requestor and Requestor Driver objects + requestorCore->SetOtaRequestorDriver(requestorUser); - // If a delay is provided, QueryImage after the timer expires + // Initialize the Downloader object + OTADownloader * downloaderCore = new OTADownloader; + // TODO: enable SetDownloaderInstance(downloaderCore); + + // Initialize the Image Processor object + LinuxOTAImageProcessor * downloaderUser = new LinuxOTAImageProcessor; + + // Connect the Downloader and Image Processor objects + downloaderCore->SetImageProcessorDelegate(downloaderUser); + // Initialize and interconnect the Requestor and Image Processor objects -- END + + // Pass the IP Address to the OTARequestor object: Use of explicit IP address is temporary + // until the Exchange Layer implements address resolution + { + IPAddress ipAddr; + IPAddress::FromString(ipAddress, ipAddr); + requestorCore->SetIpAddress(ipAddr); + } + + // Test Mode operation: If a delay is provided, QueryImage after the timer expires if (delayQueryTimeInSec > 0) { + // In this mode Provider node ID and fabric idx must be supplied explicitly from program args + requestorCore->TestModeSetProviderParameters(providerNodeId, providerFabricIndex); + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(delayQueryTimeInSec * 1000), OnStartDelayTimerHandler, nullptr); } @@ -354,3 +236,9 @@ int main(int argc, char * argv[]) return 0; } + +// Test mode operation +void OnStartDelayTimerHandler(Layer * systemLayer, void * appState) +{ + static_cast(GetRequestorInstance())->TriggerImmediateQuery(); +} diff --git a/examples/ota-requestor-app/ota-requestor-common/BUILD.gn b/examples/ota-requestor-app/ota-requestor-common/BUILD.gn index a48a9897f96cfd..44b115461860d4 100644 --- a/examples/ota-requestor-app/ota-requestor-common/BUILD.gn +++ b/examples/ota-requestor-app/ota-requestor-common/BUILD.gn @@ -31,10 +31,10 @@ chip_data_model("ota-requestor-common") { deps = [ "${chip_root}/src/lib" ] sources = [ - "BDXDownloader.cpp", - "BDXDownloader.h", - "ExampleOTARequestor.cpp", - "ExampleOTARequestor.h", + "${chip_root}/src/app/clusters/ota-requestor/BDXDownloader.cpp", + "${chip_root}/src/app/clusters/ota-requestor/BDXDownloader.h", + "${chip_root}/src/app/clusters/ota-requestor/ClusterInterface.cpp", + "${chip_root}/src/app/clusters/ota-requestor/OTARequestor.cpp", ] is_server = true diff --git a/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.cpp b/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.cpp deleted file mode 100644 index b251d3bfc07c46..00000000000000 --- a/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * All rights reserved. - * - * 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 - -ExampleOTARequestor ExampleOTARequestor::sInstance; - -constexpr uint32_t kImmediateStartDelayMs = 1; // Start the timer with this value when starting OTA "immediately" - -// OTA Software Update Requestor Cluster AnnounceOtaProvider Command callback (from client) -bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOtaProviderCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) -{ - EmberAfStatus status = ExampleOTARequestor::GetInstance().HandleAnnounceOTAProvider(commandObj, commandPath, commandData); - emberAfSendImmediateDefaultResponse(status); - return true; -} - -ExampleOTARequestor::ExampleOTARequestor() -{ - mOtaStartDelayMs = 0; - mProviderNodeId = chip::kUndefinedNodeId; - mProviderFabricIndex = chip::kUndefinedFabricIndex; -} - -void ExampleOTARequestor::Init(uint32_t startDelayMs) -{ - mOtaStartDelayMs = startDelayMs; -} - -void ExampleOTARequestor::ConnectToProvider() -{ - - if (mConnectToProviderCallback != nullptr) - { - ChipLogProgress(SoftwareUpdate, "Attempting to connect to 0x" ChipLogFormatX64 " on FabricIndex 0x%" PRIu8, - ChipLogValueX64(mProviderNodeId), mProviderFabricIndex); - - mConnectToProviderCallback(mProviderNodeId, mProviderFabricIndex); - } - else - { - ChipLogError(SoftwareUpdate, "ConnectToProviderCallback is not set"); - } -} - -EmberAfStatus ExampleOTARequestor::HandleAnnounceOTAProvider( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) -{ - auto & providerLocation = commandData.providerLocation; - auto & announcementReason = commandData.announcementReason; - - if (commandObj == nullptr) - { - ChipLogError(SoftwareUpdate, "Cannot access get FabricIndex"); - return EMBER_ZCL_STATUS_INVALID_COMMAND; - } - - mProviderNodeId = providerLocation; - mProviderFabricIndex = commandObj->GetAccessingFabricIndex(); - - ChipLogProgress(SoftwareUpdate, "OTA Requestor received AnnounceOTAProvider"); - ChipLogDetail(SoftwareUpdate, " FabricIndex: %" PRIu8, mProviderFabricIndex); - ChipLogDetail(SoftwareUpdate, " ProviderNodeID: 0x" ChipLogFormatX64, ChipLogValueX64(mProviderNodeId)); - ChipLogDetail(SoftwareUpdate, " VendorID: 0x%" PRIx16, commandData.vendorId); - ChipLogDetail(SoftwareUpdate, " AnnouncementReason: %" PRIu8, announcementReason); - if (commandData.metadataForNode.HasValue()) - { - ChipLogDetail(SoftwareUpdate, " MetadataForNode: %zu", commandData.metadataForNode.Value().size()); - } - - // If reason is URGENT_UPDATE_AVAILABLE, we start OTA immediately. Otherwise, respect the timer value set in mOtaStartDelayMs. - // This is done to exemplify what a real-world OTA Requestor might do while also being configurable enough to use as a test app. - uint32_t msToStart = 0; - switch (announcementReason) - { - case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_SIMPLE_ANNOUNCEMENT): - case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_UPDATE_AVAILABLE): - msToStart = mOtaStartDelayMs; - break; - case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_URGENT_UPDATE_AVAILABLE): - msToStart = kImmediateStartDelayMs; - break; - default: - ChipLogError(SoftwareUpdate, "Unexpected announcementReason: %" PRIu8, static_cast(announcementReason)); - return EMBER_ZCL_STATUS_INVALID_COMMAND; - } - - chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(msToStart), StartDelayTimerHandler, this); - - return EMBER_ZCL_STATUS_SUCCESS; -} - -void ExampleOTARequestor::StartDelayTimerHandler(chip::System::Layer * systemLayer, void * appState) -{ - VerifyOrReturn(appState != nullptr); - static_cast(appState)->ConnectToProvider(); -} diff --git a/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.h b/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.h deleted file mode 100644 index 6348c56b6aa88f..00000000000000 --- a/examples/ota-requestor-app/ota-requestor-common/ExampleOTARequestor.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * All rights reserved. - * - * 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. - */ - -#pragma once - -#include -#include -#include -#include - -// An example implementation for how an application might handle receiving an AnnounceOTAProvider command. In this case, the -// AnnounceOTAProvider command will be used as a trigger to send a QueryImage command and begin the OTA process. This class also -// contains other application-specific logic related to OTA Software Update. -class ExampleOTARequestor -{ -public: - static ExampleOTARequestor & GetInstance() { return sInstance; } - - void Init(uint32_t startDelayMs); - - EmberAfStatus HandleAnnounceOTAProvider( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData); - - // Setter for mConnectToProviderCallback - void SetConnectToProviderCallback(void (*f)(chip::NodeId, chip::FabricIndex)) { mConnectToProviderCallback = f; } - -private: - ExampleOTARequestor(); - - static void StartDelayTimerHandler(chip::System::Layer * systemLayer, void * appState); - void ConnectToProvider(); - - static ExampleOTARequestor sInstance; - - chip::NodeId mProviderNodeId; - chip::FabricIndex mProviderFabricIndex; - uint32_t mOtaStartDelayMs; - - // TODO: This will be redone once the full Requestor app design is in place - // Pointer to the function that establishes a session with the Provider and initiates - // the BDX download - void (*mConnectToProviderCallback)(chip::NodeId, chip::FabricIndex); -}; diff --git a/examples/ota-requestor-app/ota-requestor-common/BDXDownloader.cpp b/src/app/clusters/ota-requestor/BDXDownloader.cpp similarity index 100% rename from examples/ota-requestor-app/ota-requestor-common/BDXDownloader.cpp rename to src/app/clusters/ota-requestor/BDXDownloader.cpp diff --git a/examples/ota-requestor-app/ota-requestor-common/BDXDownloader.h b/src/app/clusters/ota-requestor/BDXDownloader.h similarity index 100% rename from examples/ota-requestor-app/ota-requestor-common/BDXDownloader.h rename to src/app/clusters/ota-requestor/BDXDownloader.h diff --git a/src/app/clusters/ota-requestor/ClusterInterface.cpp b/src/app/clusters/ota-requestor/ClusterInterface.cpp new file mode 100644 index 00000000000000..3637d9392951ff --- /dev/null +++ b/src/app/clusters/ota-requestor/ClusterInterface.cpp @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the glue code for passing the incoming OTA Requestor cluster commands + * to the OTA Requestor object that handles them + */ + +#include "OTARequestorInterface.h" + +// OTA Software Update Requestor Cluster AnnounceOtaProvider Command callback +bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOtaProviderCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) +{ + EmberAfStatus status; + OTARequestorInterface * requestor = GetRequestorInstance(); + + if (requestor != nullptr) + { + status = requestor->HandleAnnounceOTAProvider(commandObj, commandPath, commandData); + } + else + { + status = EMBER_ZCL_STATUS_FAILURE; + } + + emberAfSendImmediateDefaultResponse(status); + return true; +} + +// TBD for now. Not clear what is the API for registering a command response handler +/* Callbacks for QueryImage response */ +// Callback mQueryImageResponseCallback(QueryImageResponseHandler, +// nullptr); + +// void QueryImageResponseHandler(void * context, const QueryImageResponse::DecodableType & response) +//{ } diff --git a/src/app/clusters/ota-requestor/ota-downloader.h b/src/app/clusters/ota-requestor/OTADownloader.h similarity index 78% rename from src/app/clusters/ota-requestor/ota-downloader.h rename to src/app/clusters/ota-requestor/OTADownloader.h index 1039a5994c14c3..3f7147f79aace8 100644 --- a/src/app/clusters/ota-requestor/ota-downloader.h +++ b/src/app/clusters/ota-requestor/OTADownloader.h @@ -23,7 +23,7 @@ * must include this file */ -#include "ota-image-processor.h" +#include "OTAImageProcessor.h" #pragma once @@ -35,10 +35,10 @@ class OTADownloader // API declarations start // Application calls this method to direct OTADownloader to begin the download - void virtual BeginDownload(); + void virtual BeginDownload(){}; // Platform calls this method upon the completion of PrepareDownload() processing - void virtual OnPreparedForDownload(); + void virtual OnPreparedForDownload(){}; // Action parameter type for the OnBlockProcessed() enum BlockActionType @@ -48,13 +48,22 @@ class OTADownloader }; // Platform calls this method upon the completion of ProcessBlock() processing - void virtual OnBlockProcessed(BlockActionType action); + void virtual OnBlockProcessed(BlockActionType action){}; // A setter for the delegate class pointer - void SetImageProcessorDelegate(OTAImageProcessorDriver * delegate); + void SetImageProcessorDelegate(OTAImageProcessorDriver * delegate) { mImageProcessorDelegate = delegate; } // API declarations end + // Destructor + virtual ~OTADownloader() = default; + private: OTAImageProcessorDriver * mImageProcessorDelegate; }; + +// Set the object implementing OTADownloader +void SetDownloaderInstance(OTADownloader * instance); + +// Get the object implementing OTADownloaderInterface +OTADownloader * GetDownloaderInstance(); diff --git a/src/app/clusters/ota-requestor/ota-image-processor.h b/src/app/clusters/ota-requestor/OTAImageProcessor.h similarity index 88% rename from src/app/clusters/ota-requestor/ota-image-processor.h rename to src/app/clusters/ota-requestor/OTAImageProcessor.h index b9a7ee9856c261..3bd9a65b7bd90c 100644 --- a/src/app/clusters/ota-requestor/ota-image-processor.h +++ b/src/app/clusters/ota-requestor/OTAImageProcessor.h @@ -16,7 +16,7 @@ * limitations under the License. */ -/* This file contains the declarations for OTAImageProcessor, a platform-agnostic +/* This file contains the declarations for OTAImageProcessorDriver, a platform-agnostic * interface for processing downloaded chunks of OTA image data. * Each platform should provide an implementation of this interface. */ @@ -27,6 +27,7 @@ // chunks of OTA image data (data could be raw image data meant for flash or // metadata). Each platform should provide an implementation of this // interface. + class OTAImageProcessorDriver { public: @@ -34,7 +35,7 @@ class OTAImageProcessorDriver virtual CHIP_ERROR PrepareDownload() = 0; // Must not be a blocking call to support cases that require IO to elements such as // external peripherals/radios - virtual CHIP_ERROR ProcessBlock(ByteSpan & data) = 0; + virtual CHIP_ERROR ProcessBlock(chip::ByteSpan & data) = 0; // Close file, close persistent storage, etc virtual CHIP_ERROR Finalize() = 0; @@ -44,4 +45,7 @@ class OTAImageProcessorDriver // Clean up the download which could mean erasing everything that was written, // releasing buffers, etc. virtual CHIP_ERROR Abort() = 0; + + // Destructor + virtual ~OTAImageProcessorDriver() = default; }; diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp new file mode 100644 index 00000000000000..cc489768065bfa --- /dev/null +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -0,0 +1,389 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the implementation of the OTARequestor class. All the core + * OTA Requestor logic is contained in this class. + */ +#include "OTARequestor.h" + +#include +#include +#include +#include + +#include "BDXDownloader.h" + +#include +#include +#include + +#include +#include + +using chip::ByteSpan; +using chip::CASESessionManager; +using chip::CASESessionManagerConfig; +using chip::CharSpan; +using chip::DeviceProxy; +using chip::EndpointId; +using chip::FabricIndex; +using chip::FabricInfo; +using chip::NodeId; +using chip::OnDeviceConnected; +using chip::OnDeviceConnectionFailure; +using chip::PeerId; +using chip::Server; +using chip::VendorId; +using chip::bdx::TransferSession; +using chip::Callback::Callback; +using chip::System::Layer; +using chip::Transport::PeerAddress; +// using namespace chip::ArgParser; +using namespace chip::Messaging; +using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands; +using chip::Inet::IPAddress; + +// Global instance of the OTARequestorInterface. +OTARequestorInterface * globalOTARequestorInstance = nullptr; + +constexpr uint32_t kImmediateStartDelayMs = 1; // Start the timer with this value when starting OTA "immediately" + +// Callbacks for connection management +void OnConnected(void * context, chip::OperationalDeviceProxy * deviceProxy); +Callback mOnConnectedCallback(OnConnected, nullptr); + +void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error); +Callback mOnConnectionFailureCallback(OnConnectionFailure, nullptr); + +void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response); +void OnQueryImageFailure(void * context, EmberAfStatus status); + +void SetRequestorInstance(OTARequestorInterface * instance) +{ + globalOTARequestorInstance = instance; +} + +OTARequestorInterface * GetRequestorInstance() +{ + return globalOTARequestorInstance; +} + +void StartDelayTimerHandler(chip::System::Layer * systemLayer, void * appState) +{ + VerifyOrReturn(appState != nullptr); + static_cast(appState)->ConnectToProvider(); +} + +void OnQueryImageFailure(void * context, EmberAfStatus status) +{ + ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); +} + +void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error) +{ + ChipLogError(SoftwareUpdate, "failed to connect to 0x%" PRIX64 ": %" CHIP_ERROR_FORMAT, deviceId, error.Format()); +} +// Finds the Requestor instance and calls the corresponding OTARequestor member function +void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) +{ + OTARequestor * requestorCore = static_cast(GetRequestorInstance()); + + assert(requestorCore != nullptr); + + requestorCore->mOnQueryImageResponse(context, response); +} + +void OTARequestor::mOnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) +{ + ChipLogDetail(SoftwareUpdate, "QueryImageResponse responded with action %" PRIu8, response.status); + + TransferSession::TransferInitData initOptions; + initOptions.TransferCtlFlags = chip::bdx::TransferControlFlags::kReceiverDrive; + initOptions.MaxBlockSize = 1024; + char testFileDes[9] = { "test.txt" }; + initOptions.FileDesLength = static_cast(strlen(testFileDes)); + initOptions.FileDesignator = reinterpret_cast(testFileDes); + + chip::OperationalDeviceProxy * operationalDeviceProxy = Server::GetInstance().GetOperationalDeviceProxy(); + if (operationalDeviceProxy != nullptr) + { + chip::Messaging::ExchangeManager * exchangeMgr = operationalDeviceProxy->GetExchangeManager(); + chip::Optional session = operationalDeviceProxy->GetSecureSession(); + if (exchangeMgr != nullptr && session.HasValue()) + { + exchangeCtx = exchangeMgr->NewContext(session.Value(), &bdxDownloader); + } + + if (exchangeCtx == nullptr) + { + ChipLogError(BDX, "unable to allocate ec: exchangeMgr=%p sessionExists? %u", exchangeMgr, session.HasValue()); + return; + } + } + + bdxDownloader.SetInitialExchange(exchangeCtx); + + // This will kick of a timer which will regularly check for updates to the bdx::TransferSession state machine. + bdxDownloader.InitiateTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kReceiver, initOptions, + chip::System::Clock::Seconds16(20)); +} + +EmberAfStatus OTARequestor::HandleAnnounceOTAProvider( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) +{ + auto & providerLocation = commandData.providerLocation; + auto & announcementReason = commandData.announcementReason; + + if (commandObj == nullptr || commandObj->GetExchangeContext() == nullptr) + { + ChipLogError(SoftwareUpdate, "Cannot access ExchangeContext for FabricIndex"); + return EMBER_ZCL_STATUS_FAILURE; + } + + mProviderNodeId = providerLocation; + mProviderFabricIndex = commandObj->GetExchangeContext()->GetSessionHandle().GetFabricIndex(); + + ChipLogProgress(SoftwareUpdate, "OTA Requestor received AnnounceOTAProvider"); + ChipLogDetail(SoftwareUpdate, " FabricIndex: %" PRIu8, mProviderFabricIndex); + ChipLogDetail(SoftwareUpdate, " ProviderNodeID: 0x" ChipLogFormatX64, ChipLogValueX64(mProviderNodeId)); + ChipLogDetail(SoftwareUpdate, " VendorID: 0x%" PRIx16, commandData.vendorId); + ChipLogDetail(SoftwareUpdate, " AnnouncementReason: %" PRIu8, announcementReason); + if (commandData.metadataForNode.HasValue()) + { + ChipLogDetail(SoftwareUpdate, " MetadataForNode: %zu", commandData.metadataForNode.Value().size()); + } + + // If reason is URGENT_UPDATE_AVAILABLE, we start OTA immediately. Otherwise, respect the timer value set in mOtaStartDelayMs. + // This is done to exemplify what a real-world OTA Requestor might do while also being configurable enough to use as a test app. + uint32_t msToStart = 0; + switch (announcementReason) + { + case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_SIMPLE_ANNOUNCEMENT): + case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_UPDATE_AVAILABLE): + msToStart = mOtaStartDelayMs; + break; + case static_cast(EMBER_ZCL_OTA_ANNOUNCEMENT_REASON_URGENT_UPDATE_AVAILABLE): + msToStart = kImmediateStartDelayMs; + break; + default: + ChipLogError(SoftwareUpdate, "Unexpected announcementReason: %" PRIu8, static_cast(announcementReason)); + return EMBER_ZCL_STATUS_FAILURE; + } + + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(msToStart), StartDelayTimerHandler, this); + + return EMBER_ZCL_STATUS_SUCCESS; +} + +CHIP_ERROR OTARequestor::SetupCASESessionManager(chip::FabricIndex fabricIndex) +{ + // A previous CASE session had been established + if (mCASESessionManager != nullptr) + { + if (mCASESessionManager->GetFabricInfo()->GetFabricIndex() != fabricIndex) + { + // CSM is per fabric so if fabric index does not match the previous session, CSM needs to be set up again + chip::Platform::Delete(mCASESessionManager); + mCASESessionManager = nullptr; + } + else + { + // Fabric index matches so use previous instance + return CHIP_NO_ERROR; + } + } + + // CSM has not been setup so create a new instance of it + if (mCASESessionManager == nullptr) + { + chip::Server * server = &(chip::Server::GetInstance()); + chip::FabricInfo * fabricInfo = server->GetFabricTable().FindFabricWithIndex(fabricIndex); + if (fabricInfo == nullptr) + { + ChipLogError(SoftwareUpdate, "Did not find fabric for index %d", fabricIndex); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + chip::DeviceProxyInitParams initParams = { + .sessionManager = &(server->GetSecureSessionManager()), + .exchangeMgr = &(server->GetExchangeManager()), + .idAllocator = &(server->GetSessionIDAllocator()), + .fabricInfo = fabricInfo, + // TODO: Determine where this should be instantiated + .imDelegate = chip::Platform::New(), + }; + + chip::CASESessionManagerConfig sessionManagerConfig = { + .sessionInitParams = initParams, + .dnsCache = nullptr, + }; + + mCASESessionManager = chip::Platform::New(sessionManagerConfig); + } + + if (mCASESessionManager == nullptr) + { + ChipLogError(SoftwareUpdate, "Failed in creating an instance of CASESessionManager"); + return CHIP_ERROR_NO_MEMORY; + } + + return CHIP_NO_ERROR; +} + +// Converted from SendQueryImageCommand() +void OTARequestor::ConnectToProvider() +{ + chip::NodeId peerNodeId = mProviderNodeId; + chip::FabricIndex peerFabricIndex = mProviderFabricIndex; + + Server * server = &(Server::GetInstance()); + chip::FabricInfo * fabric = server->GetFabricTable().FindFabricWithIndex(peerFabricIndex); + if (fabric == nullptr) + { + ChipLogError(SoftwareUpdate, "Did not find fabric for index %d", peerFabricIndex); + return; + } + + chip::DeviceProxyInitParams initParams = { + .sessionManager = &(server->GetSecureSessionManager()), + .exchangeMgr = &(server->GetExchangeManager()), + .idAllocator = &(server->GetSessionIDAllocator()), + .fabricInfo = fabric, + // TODO: Determine where this should be instantiated + .imDelegate = chip::Platform::New(), + }; + + chip::OperationalDeviceProxy * operationalDeviceProxy = + chip::Platform::New(initParams, fabric->GetPeerIdForNode(peerNodeId)); + if (operationalDeviceProxy == nullptr) + { + ChipLogError(SoftwareUpdate, "Failed in creating an instance of OperationalDeviceProxy"); + return; + } + + server->SetOperationalDeviceProxy(operationalDeviceProxy); + + // Explicitly calling UpdateDeviceData() should not be needed once OperationalDeviceProxy can resolve IP address from node ID + // and fabric index + PeerAddress addr = PeerAddress::UDP(mIpAddress, CHIP_PORT); + operationalDeviceProxy->UpdateDeviceData(addr, operationalDeviceProxy->GetMRPConfig()); + + CHIP_ERROR err = operationalDeviceProxy->Connect(&mOnConnectedCallback, &mOnConnectionFailureCallback); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot establish connection to peer device: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + +// Called whenever FindOrEstablishSession is successful. Finds the Requestor instance +// and calls the corresponding OTARequestor member function +void OnConnected(void * context, chip::OperationalDeviceProxy * deviceProxy) +{ + OTARequestor * requestorCore = static_cast(GetRequestorInstance()); + + assert(requestorCore != nullptr); + + requestorCore->mOnConnected(context, deviceProxy); +} + +// Member function called whenever FindOrEstablishSession is successful +void OTARequestor::mOnConnected(void * context, chip::DeviceProxy * deviceProxy) +{ + switch (onConnectedState) + { + case kQueryImage: { + CHIP_ERROR err = CHIP_NO_ERROR; + chip::Controller::OtaSoftwareUpdateProviderCluster cluster; + constexpr EndpointId kOtaProviderEndpoint = 0; + + // These QueryImage params have been chosen arbitrarily + constexpr VendorId kExampleVendorId = VendorId::Common; + constexpr uint16_t kExampleProductId = 77; + constexpr uint16_t kExampleHWVersion = 3; + constexpr uint16_t kExampleSoftwareVersion = 0; + constexpr EmberAfOTADownloadProtocol kExampleProtocolsSupported[] = { EMBER_ZCL_OTA_DOWNLOAD_PROTOCOL_BDX_SYNCHRONOUS }; + const char locationBuf[] = { 'U', 'S' }; + CharSpan exampleLocation(locationBuf); + constexpr bool kExampleClientCanConsent = false; + ByteSpan metadata; + + err = cluster.Associate(deviceProxy, kOtaProviderEndpoint); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Associate() failed: %" CHIP_ERROR_FORMAT, err.Format()); + return; + } + QueryImage::Type args; + args.vendorId = kExampleVendorId; + args.productId = kExampleProductId; + args.softwareVersion = kExampleSoftwareVersion; + args.protocolsSupported = kExampleProtocolsSupported; + args.hardwareVersion.Emplace(kExampleHWVersion); + args.location.Emplace(exampleLocation); + args.requestorCanConsent.Emplace(kExampleClientCanConsent); + args.metadataForProvider.Emplace(metadata); + err = cluster.InvokeCommand(args, /* context = */ nullptr, OnQueryImageResponse, OnQueryImageFailure); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "QueryImage() failed: %" CHIP_ERROR_FORMAT, err.Format()); + } + + break; + } + case kStartBDX: { + TransferSession::TransferInitData initOptions; + initOptions.TransferCtlFlags = chip::bdx::TransferControlFlags::kReceiverDrive; + initOptions.MaxBlockSize = 1024; + char testFileDes[9] = { "test.txt" }; + initOptions.FileDesLength = static_cast(strlen(testFileDes)); + initOptions.FileDesignator = reinterpret_cast(testFileDes); + + if (deviceProxy != nullptr) + { + chip::Messaging::ExchangeManager * exchangeMgr = deviceProxy->GetExchangeManager(); + chip::Optional session = deviceProxy->GetSecureSession(); + if (exchangeMgr != nullptr && session.HasValue()) + { + exchangeCtx = exchangeMgr->NewContext(session.Value(), &bdxDownloader); + } + + if (exchangeCtx == nullptr) + { + ChipLogError(BDX, "unable to allocate ec: exchangeMgr=%p sessionExists? %u", exchangeMgr, session.HasValue()); + return; + } + } + + bdxDownloader.SetInitialExchange(exchangeCtx); + + // This will kick of a timer which will regularly check for updates to the bdx::TransferSession state machine. + bdxDownloader.InitiateTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kReceiver, initOptions, + chip::System::Clock::Seconds16(20)); + break; + } + default: + break; + } +} + +void OTARequestor::TriggerImmediateQuery() +{ + // Perhaps we don't need a separate function ConnectToProvider, revisit this + ConnectToProvider(); +} diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h new file mode 100644 index 00000000000000..048776bbf246b8 --- /dev/null +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -0,0 +1,96 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* This file contains the declarations for the Matter OTA Requestor implementation and API. + * Applications implementing the OTA Requestor functionality must include this file. + */ + +#include "BDXDownloader.h" +#include "OTARequestorDriver.h" +#include "OTARequestorInterface.h" +#include +#pragma once + +// This class implements all of the core logic of the OTA Requestor +class OTARequestor : public OTARequestorInterface +{ +public: + // Application interface declarations -- start + + // Application directs the Requestor to start the Image Query process + // and download the new image if available + void TriggerImmediateQuery(); + + // A setter for the delegate class pointer + void SetOtaRequestorDriver(OTARequestorDriver * driver) { mOtaRequestorDriver = driver; } + + // Application directs the Requestor to abort any processing related to + // the image update + void AbortImageUpdate(); + + // Application interface declarations -- end + + // Virtual functions from OTARequestorInterface start + // Handler for the AnnounceOTAProvider command + EmberAfStatus HandleAnnounceOTAProvider( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData); + + // Virtual functions from OTARequestorInterface -- end + void ConnectToProvider(); + + void mOnConnected(void * context, chip::DeviceProxy * deviceProxy); + void mOnQueryImageResponse( + void * context, + const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType & response); + + // When the Requestor is used as a test tool (Tesm Mode) the Provider parameters may be supplied explicitly + void TestModeSetProviderParameters(chip::NodeId NodeId, chip::FabricIndex FabIndex) + { + mProviderNodeId = NodeId; + mProviderFabricIndex = FabIndex; + } + + // Temporary until IP address resolution is implemented in the Exchange Layer + void SetIpAddress(chip::Inet::IPAddress IpAddress) { mIpAddress = IpAddress; } + +private: + // Enums + // Various cases for when OnConnected callback could be called + enum OnConnectedState + { + kQueryImage = 0, + kStartBDX, + }; + + // Variables + OTARequestorDriver * mOtaRequestorDriver; + chip::NodeId mProviderNodeId; + chip::FabricIndex mProviderFabricIndex; + uint32_t mOtaStartDelayMs = 0; + chip::CASESessionManager * mCASESessionManager = nullptr; + OnConnectedState onConnectedState = kQueryImage; + chip::Messaging::ExchangeContext * exchangeCtx = nullptr; + BdxDownloader bdxDownloader; + + // Temporary until IP address resolution is implemented in the Exchange layer + chip::Inet::IPAddress mIpAddress; + + // Functions + CHIP_ERROR SetupCASESessionManager(chip::FabricIndex fabricIndex); +}; diff --git a/src/app/clusters/ota-requestor/ota-requestor-driver.h b/src/app/clusters/ota-requestor/OTARequestorDriver.h similarity index 96% rename from src/app/clusters/ota-requestor/ota-requestor-driver.h rename to src/app/clusters/ota-requestor/OTARequestorDriver.h index 786c49c27c6715..098417926d922d 100644 --- a/src/app/clusters/ota-requestor/ota-requestor-driver.h +++ b/src/app/clusters/ota-requestor/OTARequestorDriver.h @@ -36,4 +36,7 @@ class OTARequestorDriver // Notify the application that the download is complete and the image can be applied virtual void ImageDownloadComplete() = 0; + + // Destructor + virtual ~OTARequestorDriver() = default; }; diff --git a/src/app/clusters/ota-requestor/ota-requestor-interface.h b/src/app/clusters/ota-requestor/OTARequestorInterface.h similarity index 58% rename from src/app/clusters/ota-requestor/ota-requestor-interface.h rename to src/app/clusters/ota-requestor/OTARequestorInterface.h index a6c5c592d67d63..240e9aa5c7926b 100644 --- a/src/app/clusters/ota-requestor/ota-requestor-interface.h +++ b/src/app/clusters/ota-requestor/OTARequestorInterface.h @@ -21,6 +21,11 @@ * this interface. */ +#include +#include + +#include + #pragma once // Interface class to connect the OTA Software Update Requestor cluster command processing @@ -29,15 +34,25 @@ class OTARequestorInterface { public: // Handler for the AnnounceOTAProvider command - virtual bool HandleAnnounceOTAProvider( + virtual EmberAfStatus HandleAnnounceOTAProvider( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData ch) = 0; + const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) = 0; + // TBD: This probably doesn't need to be a method OTARequestorInterface as the response handler is + // explicitly supplied at command invocation // Handler for the QueryImageResponse command - virtual bool HandleQueryImageResponse(void * context, uint8_t status, uint32_t delayedActionTime, CharSpan imageURI, - uint32_t softwareVersion, CharSpan softwareVersionString, ByteSpan updateToken, - bool userConsentNeeded, ByteSpan metadataForRequester) = 0; + // virtual bool + // HandleQueryImageResponse(chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType) = 0; - // Handler for the ApplyUpdateResponse command - virtual bool HandleApplyUpdateResponse(ApplyUpdateResponse::DecodableType); + // Destructor + virtual ~OTARequestorInterface() = default; }; + +// The instance of the class implementing OTARequestorInterface must be managed through +// the following global getter and setter functions. + +// Set the object implementing OTARequestorInterface +void SetRequestorInstance(OTARequestorInterface * instance); + +// Get the object implementing OTARequestorInterface +OTARequestorInterface * GetRequestorInstance(); diff --git a/src/app/clusters/ota-requestor/README.md b/src/app/clusters/ota-requestor/README.md new file mode 100644 index 00000000000000..cc04baecfd1492 --- /dev/null +++ b/src/app/clusters/ota-requestor/README.md @@ -0,0 +1,50 @@ +# OTA Requestor + +This is an implementation of the Matter OTA Requestor functionality that can be +used by Matter applications for OTA software updates + +## Steps for including the OTA Requestor functionality in a Matter application + +- Enable the OTA Requestor Server cluster and the OTA Provider cluster in the + application + +- Explicitly list all the source files in `src/app/clusters/ota-requestor` in + the application's make/build file. See for example + `examples/ota-requestor-app/ota-requestor-common/BUILD.gn` + +- Implement a class derived from `OTARequestorDriver`, see for example + `LinuxOTARequestorDriver`. This class would typically be a part of the + application. + +- In the application initialization logic create an instance of the + `OTARequestor` class and register it through `SetRequestorInstance()`. + Create an instance of the `OTARequestorDriver` implementation and connect it + to the `OTARequestor` object by calling + `OTARequestor::SetOtaRequestorDriver()` + +- Implement a class derived from `OTAImageProcessorDriver`, see for example + `LinuxOTARequestorDriver`. This class would typically be a part of the + platform. + +- In the application initialization logic create an instance of the + `OTADownloader` class. (TODO: How do we register it?) Create an instance of + the `OTAImageProcessorDriver` implementation and connect it to the + `OTADownloader` object by calling + `OTADownloader::SetImageProcessorDelegate()` + +- See `examples/ota-requestor-app/linux/main.cpp` for an example of the + initialization code discussed above + +- Implement application- and platform-specific logic related to image download + and update such as platform-specific image storage and validation, + application-specific logic for triggering image query and applying the + downloaded image, etc. + +- The interface between the core OTA Requestor functionality and the + platform/application logic is realized through the virtual functions of + `OTARequestorDriver` and `OTAImageProcessorDriver` and through the + application interface methods of `OTARequestor` and `OTADownloader`. + +## Design Overview + +To be completed.. diff --git a/src/app/clusters/ota-requestor/ota-requestor.h b/src/app/clusters/ota-requestor/ota-requestor.h deleted file mode 100644 index 4cfacba97b64f1..00000000000000 --- a/src/app/clusters/ota-requestor/ota-requestor.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * All rights reserved. - * - * 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. - */ - -/* This file contains the declarations for the Matter OTA Requestor implementation and API. - * Applications implementing the OTA Requestor functionality must include this file. - */ - -#include "ota-requestor-driver.h" -#include "ota-requestor-interface.h" - -#pragma once - -// This class implements all of the core logic of the OTA Requestor -class OTARequestor : public OTARequestorInterface -{ -public: - // Application interface declarations start - - // Application directs the Requestor to start the Image Query process - // and download the new image if available - void TriggerImmediateQuery(); - - // Application directs the Requestor to abort any processing related to - // the image update - void AbortImageUpdate(); - - // A setter for the delegate class pointer - void SetOtaRequestorDriver(OTARequestorDriver * driver); - - // Application interface declarations end - -private: - OTARequestorDriver * mOtaRequestorDriver; -};