From 3f9bac22f7eac0f623bb1629fa1a8cf995483be1 Mon Sep 17 00:00:00 2001 From: Marius Tache <102153746+marius-alex-tache@users.noreply.github.com> Date: Mon, 13 May 2024 15:53:47 +0300 Subject: [PATCH 01/10] [K32W] Fix apply action corner case in OTATlvProcessor interface (#33214) * [k32w] Add mShouldNotApply flag in OTATlvProcessor interface OTATlvProcessor::ApplyAction now has a default implementation, but derived classes are still able to overwrite this. The flag is used by the default ApplyAction implementation. If something goes wrong during ExitAction of the TLV processor, then mShouldNotApply should be set to true and the image processor should abort. In this case, the BDX transfer was already finished and calling CancelImageUpdate will not abort the transfer, hence the device will reboot even though it should not have. If ApplyAction fails during HandleApply, then the process will be aborted. Signed-off-by: marius-alex-tache * [k32w0] Use mShouldNotApply flag in ExitAction During ExitAction, set mShouldNotApply to true if an error occurs. This ensures that the OTA will be aborted and the device does not reboot. Also remove the ApplyAction override, since the default implementation is enough. Signed-off-by: marius-alex-tache * [k32w1] Use mShouldNotApply during ExitAction Signed-off-by: marius-alex-tache * [k32w] Update OTA error naming All OTA errors should be prefixed with CHIP_ERROR. Signed-off-by: marius-alex-tache * [k32w] Replace boolean mShouldNotApply with an enum class Signed-off-by: marius-alex-tache --------- Signed-off-by: marius-alex-tache --- .../nxp/k32w/common/OTAImageProcessorImpl.cpp | 8 +-- .../nxp/k32w/common/OTATlvProcessor.cpp | 11 ++- .../nxp/k32w/common/OTATlvProcessor.h | 72 ++++++++++++------- .../nxp/k32w/k32w0/OTAFirmwareProcessor.cpp | 26 +++---- .../nxp/k32w/k32w0/OTAFirmwareProcessor.h | 1 - .../nxp/k32w/k32w1/OTAFirmwareProcessor.cpp | 17 ++--- 6 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp index 1e00ddc293c0f0..e972550c536d58 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp @@ -142,7 +142,7 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessPayload(ByteSpan & block) } status = mCurrentProcessor->Process(block); - if (status == CHIP_OTA_CHANGE_PROCESSOR) + if (status == CHIP_ERROR_OTA_CHANGE_PROCESSOR) { mAccumulator.Clear(); mAccumulator.Init(sizeof(OTATlvHeader)); @@ -180,7 +180,7 @@ CHIP_ERROR OTAImageProcessorImpl::SelectProcessor(ByteSpan & block) if (pair == mProcessorMap.end()) { ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %" PRIu32, header.tag); - return CHIP_OTA_PROCESSOR_NOT_REGISTERED; + return CHIP_ERROR_OTA_PROCESSOR_NOT_REGISTERED; } ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %ld", pair->first); @@ -197,7 +197,7 @@ CHIP_ERROR OTAImageProcessorImpl::RegisterProcessor(uint32_t tag, OTATlvProcesso if (pair != mProcessorMap.end()) { ChipLogError(SoftwareUpdate, "A processor for tag %" PRIu32 " is already registered.", tag); - return CHIP_OTA_PROCESSOR_ALREADY_REGISTERED; + return CHIP_ERROR_OTA_PROCESSOR_ALREADY_REGISTERED; } mProcessorMap.insert({ tag, processor }); @@ -249,7 +249,7 @@ void OTAImageProcessorImpl::HandleStatus(CHIP_ERROR status) mParams.downloadedBytes += mBlock.size(); FetchNextData(0); } - else if (status == CHIP_OTA_FETCH_ALREADY_SCHEDULED) + else if (status == CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED) { mParams.downloadedBytes += mBlock.size(); } diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp index d9d2ccba6f28e3..5dc0eaebd83627 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp @@ -31,6 +31,12 @@ namespace chip { #if OTA_ENCRYPTION_ENABLE constexpr uint8_t au8Iv[] = { 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00 }; #endif + +CHIP_ERROR OTATlvProcessor::ApplyAction() +{ + return mApplyState == ApplyState::kApply ? CHIP_NO_ERROR : CHIP_ERROR_OTA_PROCESSOR_DO_NOT_APPLY; +} + CHIP_ERROR OTATlvProcessor::Process(ByteSpan & block) { CHIP_ERROR status = CHIP_NO_ERROR; @@ -50,7 +56,7 @@ CHIP_ERROR OTATlvProcessor::Process(ByteSpan & block) // If current block was processed fully and the block still contains data, it // means that the block contains another TLV's data and the current processor // should be changed by OTAImageProcessorImpl. - return CHIP_OTA_CHANGE_PROCESSOR; + return CHIP_ERROR_OTA_CHANGE_PROCESSOR; } } } @@ -63,6 +69,7 @@ void OTATlvProcessor::ClearInternal() mLength = 0; mProcessedLength = 0; mWasSelected = false; + mApplyState = ApplyState::kApply; #if OTA_ENCRYPTION_ENABLE mIVOffset = 0; #endif @@ -70,7 +77,7 @@ void OTATlvProcessor::ClearInternal() bool OTATlvProcessor::IsError(CHIP_ERROR & status) { - return status != CHIP_NO_ERROR && status != CHIP_ERROR_BUFFER_TOO_SMALL && status != CHIP_OTA_FETCH_ALREADY_SCHEDULED; + return status != CHIP_NO_ERROR && status != CHIP_ERROR_BUFFER_TOO_SMALL && status != CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED; } void OTADataAccumulator::Init(uint32_t threshold) diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.h b/src/platform/nxp/k32w/common/OTATlvProcessor.h index 65d1d68830f2b8..f1faef7e8eecf9 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.h +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.h @@ -27,20 +27,20 @@ namespace chip { #define CHIP_ERROR_TLV_PROCESSOR(e) \ ChipError(ChipError::Range::kLastRange, ((uint8_t) ChipError::Range::kLastRange << 3) | e, __FILE__, __LINE__) -#define CHIP_OTA_TLV_CONTINUE_PROCESSING CHIP_ERROR_TLV_PROCESSOR(0x01) -#define CHIP_OTA_CHANGE_PROCESSOR CHIP_ERROR_TLV_PROCESSOR(0x02) -#define CHIP_OTA_PROCESSOR_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x03) -#define CHIP_OTA_PROCESSOR_ALREADY_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x04) -#define CHIP_OTA_PROCESSOR_CLIENT_INIT CHIP_ERROR_TLV_PROCESSOR(0x05) -#define CHIP_OTA_PROCESSOR_MAKE_ROOM CHIP_ERROR_TLV_PROCESSOR(0x06) -#define CHIP_OTA_PROCESSOR_PUSH_CHUNK CHIP_ERROR_TLV_PROCESSOR(0x07) -#define CHIP_OTA_PROCESSOR_IMG_AUTH CHIP_ERROR_TLV_PROCESSOR(0x08) -#define CHIP_OTA_FETCH_ALREADY_SCHEDULED CHIP_ERROR_TLV_PROCESSOR(0x09) -#define CHIP_OTA_PROCESSOR_IMG_COMMIT CHIP_ERROR_TLV_PROCESSOR(0x0A) -#define CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x0B) -#define CHIP_OTA_PROCESSOR_EEPROM_OFFSET CHIP_ERROR_TLV_PROCESSOR(0x0C) -#define CHIP_OTA_PROCESSOR_EXTERNAL_STORAGE CHIP_ERROR_TLV_PROCESSOR(0x0D) -#define CHIP_OTA_PROCESSOR_START_IMAGE CHIP_ERROR_TLV_PROCESSOR(0x0E) +#define CHIP_ERROR_OTA_CHANGE_PROCESSOR CHIP_ERROR_TLV_PROCESSOR(0x02) +#define CHIP_ERROR_OTA_PROCESSOR_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x03) +#define CHIP_ERROR_OTA_PROCESSOR_ALREADY_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x04) +#define CHIP_ERROR_OTA_PROCESSOR_CLIENT_INIT CHIP_ERROR_TLV_PROCESSOR(0x05) +#define CHIP_ERROR_OTA_PROCESSOR_MAKE_ROOM CHIP_ERROR_TLV_PROCESSOR(0x06) +#define CHIP_ERROR_OTA_PROCESSOR_PUSH_CHUNK CHIP_ERROR_TLV_PROCESSOR(0x07) +#define CHIP_ERROR_OTA_PROCESSOR_IMG_AUTH CHIP_ERROR_TLV_PROCESSOR(0x08) +#define CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED CHIP_ERROR_TLV_PROCESSOR(0x09) +#define CHIP_ERROR_OTA_PROCESSOR_IMG_COMMIT CHIP_ERROR_TLV_PROCESSOR(0x0A) +#define CHIP_ERROR_OTA_PROCESSOR_CB_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x0B) +#define CHIP_ERROR_OTA_PROCESSOR_EEPROM_OFFSET CHIP_ERROR_TLV_PROCESSOR(0x0C) +#define CHIP_ERROR_OTA_PROCESSOR_EXTERNAL_STORAGE CHIP_ERROR_TLV_PROCESSOR(0x0D) +#define CHIP_ERROR_OTA_PROCESSOR_START_IMAGE CHIP_ERROR_TLV_PROCESSOR(0x0E) +#define CHIP_ERROR_OTA_PROCESSOR_DO_NOT_APPLY CHIP_ERROR_TLV_PROCESSOR(0x0F) // Descriptor constants constexpr size_t kVersionStringSize = 64; @@ -77,13 +77,19 @@ struct OTATlvHeader class OTATlvProcessor { public: + enum class ApplyState : uint8_t + { + kApply = 0, + kDoNotApply + }; + virtual ~OTATlvProcessor() {} virtual CHIP_ERROR Init() = 0; virtual CHIP_ERROR Clear() = 0; - virtual CHIP_ERROR ApplyAction() = 0; virtual CHIP_ERROR AbortAction() = 0; virtual CHIP_ERROR ExitAction() { return CHIP_NO_ERROR; } + virtual CHIP_ERROR ApplyAction(); CHIP_ERROR Process(ByteSpan & block); void RegisterDescriptorCallback(ProcessDescriptor callback) { mCallbackProcessDescriptor = callback; } @@ -102,7 +108,7 @@ class OTATlvProcessor * If more image chunks are needed, CHIP_ERROR_BUFFER_TOO_SMALL error is returned. * Other error codes indicate that an error occurred during processing. Fetching * next data is scheduled automatically by OTAImageProcessorImpl if the return value - * is neither an error code, nor CHIP_OTA_FETCH_ALREADY_SCHEDULED (which implies the + * is neither an error code, nor CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED (which implies the * scheduling is done inside ProcessInternal or will be done in the future, through a * callback). * @@ -110,13 +116,13 @@ class OTATlvProcessor * returns CHIP_NO_ERROR, the byte span is used to return a remaining part * of the chunk, not used by current TLV processor. * - * @retval CHIP_NO_ERROR Block was processed successfully. - * @retval CHIP_ERROR_BUFFER_TOO_SMALL Provided buffers are insufficient to decode some - * metadata (e.g. a descriptor). - * @retval CHIP_OTA_FETCH_ALREADY_SCHEDULED Should be returned if ProcessInternal schedules - * fetching next data (e.g. through a callback). - * @retval Error code Something went wrong. Current OTA process will be - * canceled. + * @retval CHIP_NO_ERROR Block was processed successfully. + * @retval CHIP_ERROR_BUFFER_TOO_SMALL Provided buffers are insufficient to decode some + * metadata (e.g. a descriptor). + * @retval CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED Should be returned if ProcessInternal schedules + * fetching next data (e.g. through a callback). + * @retval Error code Something went wrong. Current OTA process will be + * canceled. */ virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0; @@ -130,9 +136,23 @@ class OTATlvProcessor /* Expected byte size of the OTAEncryptionKeyLength */ static constexpr size_t kOTAEncryptionKeyLength = 16; #endif - uint32_t mLength = 0; - uint32_t mProcessedLength = 0; - bool mWasSelected = false; + uint32_t mLength = 0; + uint32_t mProcessedLength = 0; + bool mWasSelected = false; + + /** + * @brief A flag to account for corner cases during OTA apply + * + * Used by the default ApplyAction implementation. + * + * If something goes wrong during ExitAction of the TLV processor, + * then mApplyState should be set to kDoNotApply and the image processor + * should abort. In this case, the BDX transfer was already finished + * and calling CancelImageUpdate will not abort the transfer, hence + * the device will reboot even though it should not have. If ApplyAction + * fails during HandleApply, then the process will be aborted. + */ + ApplyState mApplyState = ApplyState::kApply; ProcessDescriptor mCallbackProcessDescriptor = nullptr; }; diff --git a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp index 760c9ef2eec8fc..f63aa745378851 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp @@ -28,12 +28,12 @@ namespace chip { CHIP_ERROR OTAFirmwareProcessor::Init() { - ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED); + ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_ERROR_OTA_PROCESSOR_CB_NOT_REGISTERED); mAccumulator.Init(sizeof(Descriptor)); #if OTA_ENCRYPTION_ENABLE mUnalignmentNum = 0; #endif - ReturnErrorCodeIf(gOtaSuccess_c != OTA_ClientInit(), CHIP_OTA_PROCESSOR_CLIENT_INIT); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_ClientInit(), CHIP_ERROR_OTA_PROCESSOR_CLIENT_INIT); auto offset = OTA_GetCurrentEepromAddressOffset(); if (offset != 0) @@ -41,8 +41,8 @@ CHIP_ERROR OTAFirmwareProcessor::Init() offset += 1; } - ReturnErrorCodeIf(OTA_UTILS_IMAGE_INVALID_ADDR == OTA_SetStartEepromOffset(offset), CHIP_OTA_PROCESSOR_EEPROM_OFFSET); - ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_OTA_PROCESSOR_START_IMAGE); + ReturnErrorCodeIf(OTA_UTILS_IMAGE_INVALID_ADDR == OTA_SetStartEepromOffset(offset), CHIP_ERROR_OTA_PROCESSOR_EEPROM_OFFSET); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_ERROR_OTA_PROCESSOR_START_IMAGE); return CHIP_NO_ERROR; } @@ -95,7 +95,7 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) if (gOtaSuccess_c != status) { ChipLogError(SoftwareUpdate, "Failed to make room for next block. Status: %d", status); - return CHIP_OTA_PROCESSOR_MAKE_ROOM; + return CHIP_ERROR_OTA_PROCESSOR_MAKE_ROOM; } #if OTA_ENCRYPTION_ENABLE status = OTA_PushImageChunk((uint8_t *) mBlock.data(), (uint16_t) mBlock.size(), NULL, NULL); @@ -105,10 +105,10 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) if (gOtaSuccess_c != status) { ChipLogError(SoftwareUpdate, "Failed to write image block. Status: %d", status); - return CHIP_OTA_PROCESSOR_PUSH_CHUNK; + return CHIP_ERROR_OTA_PROCESSOR_PUSH_CHUNK; } - return CHIP_OTA_FETCH_ALREADY_SCHEDULED; + return CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED; } CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block) @@ -122,12 +122,6 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block) return CHIP_NO_ERROR; } -CHIP_ERROR OTAFirmwareProcessor::ApplyAction() -{ - - return CHIP_NO_ERROR; -} - CHIP_ERROR OTAFirmwareProcessor::AbortAction() { OTA_CancelImage(); @@ -144,13 +138,15 @@ CHIP_ERROR OTAFirmwareProcessor::ExitAction() if (OTA_CommitImage(NULL) != gOtaSuccess_c) { ChipLogError(SoftwareUpdate, "Failed to commit firmware image."); - return CHIP_OTA_PROCESSOR_IMG_COMMIT; + mApplyState = ApplyState::kDoNotApply; + return CHIP_ERROR_OTA_PROCESSOR_IMG_COMMIT; } if (OTA_ImageAuthenticate() != gOtaImageAuthPass_c) { ChipLogError(SoftwareUpdate, "Failed to authenticate firmware image."); - return CHIP_OTA_PROCESSOR_IMG_AUTH; + mApplyState = ApplyState::kDoNotApply; + return CHIP_ERROR_OTA_PROCESSOR_IMG_AUTH; } OTA_AddNewImageFlag(); diff --git a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h index 885dc239dd22b3..5a1092ef5fbc46 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h @@ -35,7 +35,6 @@ class OTAFirmwareProcessor : public OTATlvProcessor CHIP_ERROR Init() override; CHIP_ERROR Clear() override; - CHIP_ERROR ApplyAction() override; CHIP_ERROR AbortAction() override; CHIP_ERROR ExitAction() override; diff --git a/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp index 528261b12b5e59..ee3ff9b655c504 100644 --- a/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp +++ b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp @@ -26,16 +26,16 @@ namespace chip { CHIP_ERROR OTAFirmwareProcessor::Init() { - ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED); + ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_ERROR_OTA_PROCESSOR_CB_NOT_REGISTERED); mAccumulator.Init(sizeof(Descriptor)); - ReturnErrorCodeIf(gOtaSuccess_c != OTA_SelectExternalStoragePartition(), CHIP_OTA_PROCESSOR_EXTERNAL_STORAGE); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_SelectExternalStoragePartition(), CHIP_ERROR_OTA_PROCESSOR_EXTERNAL_STORAGE); otaResult_t ota_status; ota_status = OTA_ServiceInit(&mPostedOperationsStorage[0], NB_PENDING_TRANSACTIONS * TRANSACTION_SZ); - ReturnErrorCodeIf(ota_status != gOtaSuccess_c, CHIP_OTA_PROCESSOR_CLIENT_INIT); - ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_OTA_PROCESSOR_START_IMAGE); + ReturnErrorCodeIf(ota_status != gOtaSuccess_c, CHIP_ERROR_OTA_PROCESSOR_CLIENT_INIT); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_ERROR_OTA_PROCESSOR_START_IMAGE); return CHIP_NO_ERROR; } @@ -70,7 +70,7 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) if (gOtaSuccess_c != status) { ChipLogError(SoftwareUpdate, "Failed to make room for next block. Status: %d", status); - return CHIP_OTA_PROCESSOR_MAKE_ROOM; + return CHIP_ERROR_OTA_PROCESSOR_MAKE_ROOM; } } else @@ -82,10 +82,10 @@ CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) if (gOtaSuccess_c != status) { ChipLogError(SoftwareUpdate, "Failed to write image block. Status: %d", status); - return CHIP_OTA_PROCESSOR_PUSH_CHUNK; + return CHIP_ERROR_OTA_PROCESSOR_PUSH_CHUNK; } - return CHIP_OTA_FETCH_ALREADY_SCHEDULED; + return CHIP_ERROR_OTA_FETCH_ALREADY_SCHEDULED; } CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block) @@ -117,7 +117,8 @@ CHIP_ERROR OTAFirmwareProcessor::ExitAction() if (OTA_CommitImage(NULL) != gOtaSuccess_c) { ChipLogError(SoftwareUpdate, "Failed to commit firmware image."); - return CHIP_OTA_PROCESSOR_IMG_COMMIT; + mApplyState = ApplyState::kDoNotApply; + return CHIP_ERROR_OTA_PROCESSOR_IMG_COMMIT; } return CHIP_NO_ERROR; From 37b2fe61f57ca740cd5cee7a23081e44f4a2ba33 Mon Sep 17 00:00:00 2001 From: zhhyu7 Date: Mon, 13 May 2024 21:02:06 +0800 Subject: [PATCH 02/10] Added compile support for NuttX system. (#31236) This is a basic version of compile support and only x86 simulator lightning-app is supported now The compilation method is shown below: ./scripts/build/build_examples.py --target nuttx-x64-light build and compiled binaries are in the out/nuttx-x64-light directory. Detailed introduction about NuttX system can refer to this link: https://github.com/apache/nuttx Signed-off-by: zhanghongyu --- .github/.wordlist.txt | 1 + .github/workflows/examples-nuttx.yaml | 55 + .github/workflows/lint.yml | 3 +- build/config/compiler/BUILD.gn | 5 + config/nuttx/chip-gn/.gn | 29 + config/nuttx/chip-gn/BUILD.gn | 57 + config/nuttx/chip-gn/args.gni | 32 + config/nuttx/chip-gn/toolchain/BUILD.gn | 33 + examples/lighting-app/linux/main.cpp | 2 +- scripts/build/build/targets.py | 18 + scripts/build/builders/nuttx.py | 98 + .../build/testdata/all_targets_linux_x64.txt | 1 + scripts/checkout_submodules.py | 1 + scripts/tools/check_includes_config.py | 1 + src/app/BUILD.gn | 1 + src/inet/BUILD.gn | 3 + src/lib/core/CHIPError.h | 8 +- src/platform/BUILD.gn | 9 + src/platform/NuttX/BLEManagerImpl.cpp | 897 ++++++++ src/platform/NuttX/BLEManagerImpl.h | 241 +++ src/platform/NuttX/BUILD.gn | 160 ++ src/platform/NuttX/BlePlatformConfig.h | 41 + src/platform/NuttX/CHIPDevicePlatformConfig.h | 73 + src/platform/NuttX/CHIPDevicePlatformEvent.h | 108 + src/platform/NuttX/CHIPLinuxStorage.cpp | 353 +++ src/platform/NuttX/CHIPLinuxStorage.h | 98 + src/platform/NuttX/CHIPLinuxStorageIni.cpp | 404 ++++ src/platform/NuttX/CHIPLinuxStorageIni.h | 63 + src/platform/NuttX/CHIPPlatformConfig.h | 71 + .../NuttX/ConfigurationManagerImpl.cpp | 385 ++++ src/platform/NuttX/ConfigurationManagerImpl.h | 95 + .../NuttX/ConnectivityManagerImpl.cpp | 1904 +++++++++++++++++ src/platform/NuttX/ConnectivityManagerImpl.h | 320 +++ src/platform/NuttX/ConnectivityUtils.cpp | 734 +++++++ src/platform/NuttX/ConnectivityUtils.h | 69 + .../NuttX/DeviceInstanceInfoProviderImpl.cpp | 37 + .../NuttX/DeviceInstanceInfoProviderImpl.h | 44 + .../NuttX/DiagnosticDataProviderImpl.cpp | 830 +++++++ .../NuttX/DiagnosticDataProviderImpl.h | 117 + src/platform/NuttX/DnssdImpl.cpp | 1061 +++++++++ src/platform/NuttX/DnssdImpl.h | 205 ++ src/platform/NuttX/InetPlatformConfig.h | 48 + .../NuttX/KeyValueStoreManagerImpl.cpp | 115 + src/platform/NuttX/KeyValueStoreManagerImpl.h | 79 + src/platform/NuttX/Logging.cpp | 89 + .../NuttX/NetworkCommissioningDriver.h | 241 +++ .../NetworkCommissioningEthernetDriver.cpp | 43 + .../NetworkCommissioningThreadDriver.cpp | 223 ++ .../NuttX/NetworkCommissioningWiFiDriver.cpp | 361 ++++ src/platform/NuttX/OTAImageProcessorImpl.cpp | 279 +++ src/platform/NuttX/OTAImageProcessorImpl.h | 75 + src/platform/NuttX/PlatformManagerImpl.cpp | 327 +++ src/platform/NuttX/PlatformManagerImpl.h | 159 ++ src/platform/NuttX/PosixConfig.cpp | 588 +++++ src/platform/NuttX/PosixConfig.h | 131 ++ src/platform/NuttX/README.md | 14 + src/platform/NuttX/SystemPlatformConfig.h | 44 + src/platform/NuttX/SystemTimeSupport.cpp | 105 + src/platform/NuttX/ThreadStackManagerImpl.cpp | 770 +++++++ src/platform/NuttX/ThreadStackManagerImpl.h | 179 ++ src/platform/NuttX/WirelessDefs.h | 186 ++ src/platform/NuttX/args.gni | 15 + src/platform/device.gni | 11 +- 63 files changed, 12740 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/examples-nuttx.yaml create mode 100644 config/nuttx/chip-gn/.gn create mode 100644 config/nuttx/chip-gn/BUILD.gn create mode 100644 config/nuttx/chip-gn/args.gni create mode 100644 config/nuttx/chip-gn/toolchain/BUILD.gn create mode 100644 scripts/build/builders/nuttx.py create mode 100644 src/platform/NuttX/BLEManagerImpl.cpp create mode 100644 src/platform/NuttX/BLEManagerImpl.h create mode 100644 src/platform/NuttX/BUILD.gn create mode 100644 src/platform/NuttX/BlePlatformConfig.h create mode 100644 src/platform/NuttX/CHIPDevicePlatformConfig.h create mode 100644 src/platform/NuttX/CHIPDevicePlatformEvent.h create mode 100644 src/platform/NuttX/CHIPLinuxStorage.cpp create mode 100644 src/platform/NuttX/CHIPLinuxStorage.h create mode 100644 src/platform/NuttX/CHIPLinuxStorageIni.cpp create mode 100644 src/platform/NuttX/CHIPLinuxStorageIni.h create mode 100644 src/platform/NuttX/CHIPPlatformConfig.h create mode 100644 src/platform/NuttX/ConfigurationManagerImpl.cpp create mode 100644 src/platform/NuttX/ConfigurationManagerImpl.h create mode 100644 src/platform/NuttX/ConnectivityManagerImpl.cpp create mode 100644 src/platform/NuttX/ConnectivityManagerImpl.h create mode 100644 src/platform/NuttX/ConnectivityUtils.cpp create mode 100644 src/platform/NuttX/ConnectivityUtils.h create mode 100644 src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp create mode 100644 src/platform/NuttX/DeviceInstanceInfoProviderImpl.h create mode 100644 src/platform/NuttX/DiagnosticDataProviderImpl.cpp create mode 100644 src/platform/NuttX/DiagnosticDataProviderImpl.h create mode 100644 src/platform/NuttX/DnssdImpl.cpp create mode 100644 src/platform/NuttX/DnssdImpl.h create mode 100644 src/platform/NuttX/InetPlatformConfig.h create mode 100644 src/platform/NuttX/KeyValueStoreManagerImpl.cpp create mode 100644 src/platform/NuttX/KeyValueStoreManagerImpl.h create mode 100644 src/platform/NuttX/Logging.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningDriver.h create mode 100644 src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningThreadDriver.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp create mode 100644 src/platform/NuttX/OTAImageProcessorImpl.cpp create mode 100644 src/platform/NuttX/OTAImageProcessorImpl.h create mode 100644 src/platform/NuttX/PlatformManagerImpl.cpp create mode 100644 src/platform/NuttX/PlatformManagerImpl.h create mode 100644 src/platform/NuttX/PosixConfig.cpp create mode 100644 src/platform/NuttX/PosixConfig.h create mode 100644 src/platform/NuttX/README.md create mode 100644 src/platform/NuttX/SystemPlatformConfig.h create mode 100644 src/platform/NuttX/SystemTimeSupport.cpp create mode 100644 src/platform/NuttX/ThreadStackManagerImpl.cpp create mode 100644 src/platform/NuttX/ThreadStackManagerImpl.h create mode 100644 src/platform/NuttX/WirelessDefs.h create mode 100644 src/platform/NuttX/args.gni diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 33889f519989be..25c9ef8daa6594 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -965,6 +965,7 @@ NTP nullable nullptr NUM +NuttX NVM NVS nwdiag diff --git a/.github/workflows/examples-nuttx.yaml b/.github/workflows/examples-nuttx.yaml new file mode 100644 index 00000000000000..bfe90b7363e4a6 --- /dev/null +++ b/.github/workflows/examples-nuttx.yaml @@ -0,0 +1,55 @@ +# 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. + +name: Build example - NuttX + +on: + push: + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }} + cancel-in-progress: true + +env: + CHIP_NO_LOG_TIMESTAMPS: true + +jobs: + nuttx: + name: NuttX + + runs-on: ubuntu-latest + if: github.actor != 'restyled-io[bot]' + + container: + image: ghcr.io/project-chip/chip-build-nuttx:51 + volumes: + - "/tmp/bloat_reports:/tmp/bloat_reports" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Checkout submodules & Bootstrap + uses: ./.github/actions/checkout-submodules-and-bootstrap + with: + platform: nuttx + extra-submodule-parameters: " --recursive" + - name: Build example simulator NuttX Lighting App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py \ + --target nuttx-x64-light \ + build \ + " diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c810884ed98d30..7336213ba81a30 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -90,6 +90,7 @@ jobs: --skip-dir platform/webos \ --skip-dir platform/Zephyr \ --skip-dir test_driver \ + --skip-dir platform/NuttX \ --known-failure app/app-platform/ContentApp.cpp \ --known-failure app/app-platform/ContentApp.h \ --known-failure app/app-platform/ContentAppPlatform.cpp \ @@ -205,7 +206,7 @@ jobs: # TODO: TLVDebug should ideally not be excluded here. # TODO: protocol_decoder.cpp should ideally not be excluded here. # TODO: PersistentStorageMacros.h should ideally not be excluded here. - git grep -I -n "PRI.64" -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)examples/chip-tool' ':(exclude)examples/tv-casting-app' ':(exclude)src/app/MessageDef/MessageDefHelper.cpp' ':(exclude)src/app/tests/integration/chip_im_initiator.cpp' ':(exclude)src/lib/core/TLVDebug.cpp' ':(exclude)src/lib/dnssd/tests/TestTxtFields.cpp' ':(exclude)src/lib/format/protocol_decoder.cpp' ':(exclude)src/lib/support/PersistentStorageMacros.h' ':(exclude)src/messaging/tests/echo/echo_requester.cpp' ':(exclude)src/platform/Linux' ':(exclude)src/platform/Ameba' ':(exclude)src/platform/ESP32' ':(exclude)src/platform/Darwin' ':(exclude)src/darwin' ':(exclude)src/platform/webos' ':(exclude)zzz_generated/chip-tool' ':(exclude)src/tools/chip-cert/Cmd_PrintCert.cpp' && exit 1 || exit 0 + git grep -I -n "PRI.64" -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)examples/chip-tool' ':(exclude)examples/tv-casting-app' ':(exclude)src/app/MessageDef/MessageDefHelper.cpp' ':(exclude)src/app/tests/integration/chip_im_initiator.cpp' ':(exclude)src/lib/core/TLVDebug.cpp' ':(exclude)src/lib/dnssd/tests/TestTxtFields.cpp' ':(exclude)src/lib/format/protocol_decoder.cpp' ':(exclude)src/lib/support/PersistentStorageMacros.h' ':(exclude)src/messaging/tests/echo/echo_requester.cpp' ':(exclude)src/platform/Linux' ':(exclude)src/platform/Ameba' ':(exclude)src/platform/ESP32' ':(exclude)src/platform/Darwin' ':(exclude)src/darwin' ':(exclude)src/platform/webos' ':(exclude)zzz_generated/chip-tool' ':(exclude)src/tools/chip-cert/Cmd_PrintCert.cpp' ':(exclude)src/platform/NuttX' && exit 1 || exit 0 # git grep exits with 0 if it finds a match, but we want # to fail (exit nonzero) on match. And we want to exclude this file, diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index df85153297cad7..63b5eef6003b1b 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -271,6 +271,11 @@ config("strict_warnings") { cflags_cc = [ "-Wnon-virtual-dtor" ] + if (current_os == "nuttx") { + cflags -= [ "-Wshadow" ] + cflags_cc -= [ "-Wnon-virtual-dtor" ] + } + configs = [] ldflags = [] diff --git a/config/nuttx/chip-gn/.gn b/config/nuttx/chip-gn/.gn new file mode 100644 index 00000000000000..b81b0bba35934b --- /dev/null +++ b/config/nuttx/chip-gn/.gn @@ -0,0 +1,29 @@ +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + target_cpu = "" + target_os = "nuttx" + + import("${chip_root}/config/nuttx/chip-gn/args.gni") +} diff --git a/config/nuttx/chip-gn/BUILD.gn b/config/nuttx/chip-gn/BUILD.gn new file mode 100644 index 00000000000000..fbefd303425475 --- /dev/null +++ b/config/nuttx/chip-gn/BUILD.gn @@ -0,0 +1,57 @@ +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tests.gni") + +assert(current_os == "nuttx") + +declare_args() { + chip_build_example_providers = false + chip_example_lighting = false +} + +static_library("nuttx") { + output_name = "libchipnuttx" + + public_deps = [ + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/src/lib", + ] + + if (chip_build_tests) { + public_deps += [ "${chip_root}/src:tests" ] + } + + if (chip_build_example_providers) { + public_deps += [ "${chip_root}/examples/providers:device_info_provider" ] + } + + if (chip_example_lighting) { + public_deps += [ + "${chip_root}/examples/lighting-app/lighting-common", + "${chip_root}/examples/lighting-app/lighting-common:lighting-manager", + ] + } + + output_dir = "${root_out_dir}/lib" + + complete_static_lib = true +} + +group("default") { + deps = [ ":nuttx" ] +} diff --git a/config/nuttx/chip-gn/args.gni b/config/nuttx/chip-gn/args.gni new file mode 100644 index 00000000000000..1c114464aca2ec --- /dev/null +++ b/config/nuttx/chip-gn/args.gni @@ -0,0 +1,32 @@ +# 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("//build_overrides/chip.gni") + +import("${chip_root}/src/crypto/crypto.gni") + +chip_device_platform = "nuttx" + +chip_build_tests = false + +chip_project_config_include = "" +chip_system_project_config_include = "" +chip_ble_project_config_include = "" + +chip_crypto = "mbedtls" +chip_external_mbedtls = true + +custom_toolchain = "${chip_root}/config/nuttx/chip-gn/toolchain:nuttx" + +pw_build_PIP_CONSTRAINTS = [ "${chip_root}/scripts/setup/constraints.txt" ] diff --git a/config/nuttx/chip-gn/toolchain/BUILD.gn b/config/nuttx/chip-gn/toolchain/BUILD.gn new file mode 100644 index 00000000000000..199979e0cd2c1d --- /dev/null +++ b/config/nuttx/chip-gn/toolchain/BUILD.gn @@ -0,0 +1,33 @@ +# 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("//build/toolchain/gcc_toolchain.gni") +import("//build_overrides/build.gni") + +declare_args() { + nuttx_ar = "" + nuttx_cc = "" + nuttx_cxx = "" +} + +gcc_toolchain("nuttx") { + ar = nuttx_ar + cc = nuttx_cc + cxx = nuttx_cxx + + toolchain_args = { + current_os = "nuttx" + is_clang = false + } +} diff --git a/examples/lighting-app/linux/main.cpp b/examples/lighting-app/linux/main.cpp index 56cafc2e527509..8e586b5cf56d54 100644 --- a/examples/lighting-app/linux/main.cpp +++ b/examples/lighting-app/linux/main.cpp @@ -95,7 +95,7 @@ void ApplicationShutdown() } } -int main(int argc, char * argv[]) +extern "C" int main(int argc, char * argv[]) { if (ChipLinuxAppInit(argc, argv) != 0) { diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 1456c240e57719..33e37670040e2b 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -27,6 +27,7 @@ from builders.mbed import MbedApp, MbedBoard, MbedBuilder, MbedProfile from builders.mw320 import MW320App, MW320Builder from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder +from builders.nuttx import NuttXApp, NuttXBoard, NuttXBuilder from builders.nxp import NxpApp, NxpBoard, NxpBuilder from builders.openiotsdk import OpenIotSdkApp, OpenIotSdkBuilder, OpenIotSdkCryptoBackend from builders.qpg import QpgApp, QpgBoard, QpgBuilder @@ -324,6 +325,22 @@ def BuildNrfTarget(): return target +def BuildNuttXTarget(): + target = BuildTarget('nuttx', NuttXBuilder) + + # Boards + target.AppendFixedTargets([ + TargetPart('x64', board=NuttXBoard.SIM), + ]) + + # Apps + target.AppendFixedTargets([ + TargetPart('light', app=NuttXApp.LIGHT), + ]) + + return target + + def BuildAndroidTarget(): target = BuildTarget('android', AndroidBuilder) @@ -795,6 +812,7 @@ def BuildOpenIotSdkTargets(): BuildMW320Target(), BuildNrfTarget(), BuildNrfNativeTarget(), + BuildNuttXTarget(), BuildQorvoTarget(), BuildStm32Target(), BuildTizenTarget(), diff --git a/scripts/build/builders/nuttx.py b/scripts/build/builders/nuttx.py new file mode 100644 index 00000000000000..ae198ca71f07ac --- /dev/null +++ b/scripts/build/builders/nuttx.py @@ -0,0 +1,98 @@ +# 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 logging +import os +from enum import Enum, auto + +from .gn import Builder + + +class NuttXApp(Enum): + LIGHT = auto() + + def ExampleName(self): + if self == NuttXApp.LIGHT: + return 'lighting-app' + else: + raise Exception('Unknown app type: %r' % self) + + def AppNamePrefix(self, chip_name): + if self == NuttXApp.LIGHT: + return ('chip-%s-lighting-example' % chip_name) + else: + raise Exception('Unknown app type: %r' % self) + + +class NuttXBoard(Enum): + SIM = auto() + + +def NuttXTarget(board, app): + if board == NuttXBoard.SIM: + if app == NuttXApp.LIGHT: + return 'sim:matter' + return 'none' + + +class NuttXBuilder(Builder): + + def __init__(self, + root, + runner, + app: NuttXApp = NuttXApp.LIGHT, + board: NuttXBoard = NuttXBoard.SIM, + ): + + nuttx_chip = 'nuttx' + + super(NuttXBuilder, self).__init__( + root=os.path.join(root, 'examples', + app.ExampleName(), nuttx_chip), + runner=runner + ) + + self.chip_name = nuttx_chip + self.app = app + self.board = board + + def generate(self): + self._Execute(['mkdir', '-p', self.output_dir], + title='Generating ' + self.identifier) + + def _build(self): + logging.info('Compiling NuttX %s at %s, ', + NuttXTarget(self.board, self.app), self.output_dir) + nuttx_dir = os.path.join(os.sep, 'opt', 'nuttx', 'nuttx') + + self._Execute(['cmake', '-S', nuttx_dir, '-B', self.output_dir, '-DCHIP_ROOT=' + os.getenv('PW_PROJECT_ROOT'), + '-DBOARD_CONFIG=' + NuttXTarget(self.board, self.app), + '-DCMAKE_C_COMPILER=/opt/nuttx/gcc-13/bin/gcc', + '-DCMAKE_CXX_COMPILER=/opt/nuttx/gcc-13/bin/g++', + '-GNinja'], + title='Building ' + self.identifier) + self._Execute(['cmake', '--build', self.output_dir]) + + def build_outputs(self): + logging.info('Compiling outputs NuttX at %s', self.output_dir) + items = { + '%s.out' % self.app.AppNamePrefix(self.chip_name): + os.path.join(self.output_dir, '%s.out' % + self.app.AppNamePrefix(self.chip_name)), + '%s.out.map' % self.app.AppNamePrefix(self.chip_name): + os.path.join(self.output_dir, + '%s.out.map' % self.app.AppNamePrefix(self.chip_name)), + } + + return items diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index fcdf79baf41bc0..b2273d694acdc6 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -19,6 +19,7 @@ mbed-cy8cproto_062_4343w-{lock,light,all-clusters,all-clusters-minimal,pigweed,o mw320-all-clusters-app nrf-{nrf5340dk,nrf52840dk,nrf52840dongle}-{all-clusters,all-clusters-minimal,lock,light,light-switch,shell,pump,pump-controller,window-covering}[-rpc] nrf-native-posix-64-tests +nuttx-x64-light qpg-qpg6105-{lock,light,shell,persistent-storage,light-switch,thermostat}[-updateimage] stm32-stm32wb5mm-dk-light tizen-arm-{all-clusters,all-clusters-minimal,chip-tool,light,tests}[-no-ble][-no-thread][-no-wifi][-asan][-ubsan][-with-ui] diff --git a/scripts/checkout_submodules.py b/scripts/checkout_submodules.py index 81dafa1fb91a29..0290182b5b4bff 100755 --- a/scripts/checkout_submodules.py +++ b/scripts/checkout_submodules.py @@ -41,6 +41,7 @@ 'linux', 'mbed', 'nrfconnect', + 'nuttx', 'qpg', 'stm32', 'telink', diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 227d65a99877c6..20c853b9906df8 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -53,6 +53,7 @@ '/platform/webos/', '/platform/mt793x/', '/platform/ASR/', + '/platform/NuttX/', r'POSIX\.h$', } diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index e40847403cd01f..0eb7e1456e02e0 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -118,6 +118,7 @@ source_set("global-attributes") { # as a dependency public_deps = [ ":app_config", + "${chip_root}/src/app/common:ids", "${chip_root}/src/lib/support", ] } diff --git a/src/inet/BUILD.gn b/src/inet/BUILD.gn index 94d6c253660491..e30d417ffc0b39 100644 --- a/src/inet/BUILD.gn +++ b/src/inet/BUILD.gn @@ -185,4 +185,7 @@ static_library("inet") { } cflags = [ "-Wconversion" ] + if (current_os == "nuttx") { + cflags -= [ "-Wconversion" ] + } } diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 370a7d37d096ef..3a8ad9d5f24811 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -409,13 +409,13 @@ class ChipError * * This template ensures that the numeric value is constant and well-formed. */ - template + template struct SdkErrorConstant { static_assert(FitsInField(kSdkPartLength, to_underlying(PART)), "part is too large"); - static_assert(FitsInField(kSdkCodeLength, CODE), "code is too large"); - static_assert(MakeInteger(PART, CODE) != 0, "value is zero"); - static constexpr StorageType value = MakeInteger(PART, CODE); + static_assert(FitsInField(kSdkCodeLength, SCODE), "code is too large"); + static_assert(MakeInteger(PART, SCODE) != 0, "value is zero"); + static constexpr StorageType value = MakeInteger(PART, SCODE); }; }; diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 16287f1afb16de..97365a2ee72e0f 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -308,6 +308,12 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { } else if (chip_device_platform == "stm32") { device_layer_target_define = "STM32" defines += [ "CHIP_DEVICE_LAYER_TARGET=stm32" ] + } else if (chip_device_platform == "nuttx") { + device_layer_target_define = "NUTTX" + defines += [ + "CHIP_DEVICE_LAYER_TARGET=NuttX", + "CHIP_DEVICE_CONFIG_ENABLE_WIFI=${chip_enable_wifi}", + ] } else { device_layer_target_define = "" } @@ -346,6 +352,7 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "OPEN_IOT_SDK", "ASR", "STM32", + "NUTTX", ] foreach(possible_device_layer_target_define, possible_device_layer_target_defines) { @@ -576,6 +583,8 @@ if (chip_device_platform != "none") { _platform_target = "ASR" } else if (chip_device_platform == "stm32") { _platform_target = "stm32" + } else if (chip_device_platform == "nuttx") { + _platform_target = "NuttX" } else { assert(false, "Unknown chip_device_platform: ${chip_device_platform}") } diff --git a/src/platform/NuttX/BLEManagerImpl.cpp b/src/platform/NuttX/BLEManagerImpl.cpp new file mode 100644 index 00000000000000..e472e805a1154e --- /dev/null +++ b/src/platform/NuttX/BLEManagerImpl.cpp @@ -0,0 +1,897 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the BLEManager singleton object + * for Linux platforms. + */ + +/** + * Note: BLEManager requires ConnectivityManager to be defined beforehand, + * otherwise we will face circular dependency between them. */ +#include + +/** + * Note: Use public include for BLEManager which includes our local + * platform//BLEManagerImpl.h after defining interface class. */ +#include "platform/internal/BLEManager.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "bluez/BluezEndpoint.h" + +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION +#include +#endif + +using namespace ::nl; +using namespace ::chip::Ble; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +namespace { + +static constexpr System::Clock::Timeout kNewConnectionScanTimeout = System::Clock::Seconds16(20); +static constexpr System::Clock::Timeout kConnectTimeout = System::Clock::Seconds16(20); +static constexpr System::Clock::Timeout kFastAdvertiseTimeout = + System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING +// The CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS specifies the transition time +// starting from advertisement commencement. Since the extended advertisement timer is started after +// the fast-to-slow transition, we have to subtract the time spent in fast advertising. +static constexpr System::Clock::Timeout kSlowAdvertiseTimeout = System::Clock::Milliseconds32( + CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS - CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); +static_assert(CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS >= + CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME, + "The extended advertising interval change time must be greater than the fast advertising interval change time"); +#endif + +const ChipBleUUID ChipUUID_CHIPoBLEChar_RX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x11 } }; +const ChipBleUUID ChipUUID_CHIPoBLEChar_TX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x12 } }; + +void HandleConnectTimeout(chip::System::Layer *, void * apEndpoint) +{ + VerifyOrDie(apEndpoint != nullptr); + static_cast(apEndpoint)->CancelConnect(); + BLEManagerImpl::HandleConnectFailed(CHIP_ERROR_TIMEOUT); +} + +} // namespace + +BLEManagerImpl BLEManagerImpl::sInstance; + +void HandleIncomingBleConnection(BLEEndPoint * bleEP) +{ + ChipLogProgress(DeviceLayer, "CHIPoBluez con rcvd"); +} + +CHIP_ERROR BLEManagerImpl::_Init() +{ + CHIP_ERROR err; + + err = BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer()); + SuccessOrExit(err); + + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Enabled; + mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART && !mIsCentral); + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + + memset(mDeviceName, 0, sizeof(mDeviceName)); + + OnChipBleConnectReceived = HandleIncomingBleConnection; + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + +exit: + return err; +} + +void BLEManagerImpl::_Shutdown() +{ + // Ensure scan resources are cleared (e.g. timeout timers). + mDeviceScanner.Shutdown(); + // Stop advertising and free resources. + mBLEAdvertisement.Shutdown(); + // Make sure that the endpoint is not used by the timer. + DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, &mEndpoint); + // Release BLE connection resources (unregister from BlueZ). + mEndpoint.Shutdown(); + mFlags.Clear(Flags::kBluezBLELayerInitialized); +} + +CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (mFlags.Has(Flags::kAdvertisingEnabled) != val) + { + mFlags.Set(Flags::kAdvertisingEnabled, val); + } + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + + return err; +} + +CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) +{ + switch (mode) + { + case BLEAdvertisingMode::kFastAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + break; + case BLEAdvertisingMode::kSlowAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, false); + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + mFlags.Set(Flags::kAdvertisingRefreshNeeded); + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + return CHIP_NO_ERROR; +} + +CHIP_ERROR BLEManagerImpl::_GetDeviceName(char * buf, size_t bufSize) +{ + if (strlen(mDeviceName) >= bufSize) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + strcpy(buf, mDeviceName); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR BLEManagerImpl::_SetDeviceName(const char * deviceName) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(mServiceMode != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + if (deviceName != nullptr && deviceName[0] != 0) + { + VerifyOrExit(strlen(deviceName) < kMaxDeviceNameLength, err = CHIP_ERROR_INVALID_ARGUMENT); + strcpy(mDeviceName, deviceName); + mFlags.Set(Flags::kUseCustomDeviceName); + } + else + { + uint16_t discriminator; + SuccessOrExit(err = GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator)); + snprintf(mDeviceName, sizeof(mDeviceName), "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator); + mDeviceName[kMaxDeviceNameLength] = 0; + mFlags.Clear(Flags::kUseCustomDeviceName); + } + +exit: + return err; +} + +uint16_t BLEManagerImpl::_NumConnections() +{ + uint16_t numCons = 0; + return numCons; +} + +CHIP_ERROR BLEManagerImpl::ConfigureBle(uint32_t aAdapterId, bool aIsCentral) +{ + mAdapterId = aAdapterId; + mIsCentral = aIsCentral; + mpBLEAdvUUID = "0xFFF6"; + return CHIP_NO_ERROR; +} + +void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + switch (event->Type) + { + case DeviceEventType::kCHIPoBLESubscribe: + HandleSubscribeReceived(event->CHIPoBLESubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + { + ChipDeviceEvent connectionEvent; + connectionEvent.Type = DeviceEventType::kCHIPoBLEConnectionEstablished; + PlatformMgr().PostEventOrDie(&connectionEvent); + } + break; + + case DeviceEventType::kCHIPoBLEUnsubscribe: + HandleUnsubscribeReceived(event->CHIPoBLEUnsubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; + + case DeviceEventType::kCHIPoBLEWriteReceived: + HandleWriteReceived(event->CHIPoBLEWriteReceived.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX, + PacketBufferHandle::Adopt(event->CHIPoBLEWriteReceived.Data)); + break; + + case DeviceEventType::kCHIPoBLEIndicateConfirm: + HandleIndicationConfirmation(event->CHIPoBLEIndicateConfirm.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; + + case DeviceEventType::kCHIPoBLEConnectionError: + HandleConnectionError(event->CHIPoBLEConnectionError.ConId, event->CHIPoBLEConnectionError.Reason); + break; + case DeviceEventType::kServiceProvisioningChange: + // Force the advertising configuration to be refreshed to reflect new provisioning state. + mFlags.Clear(Flags::kAdvertisingConfigured); + + DriveBLEState(); + break; + default: + HandlePlatformSpecificBLEEvent(event); + break; + } +} + +void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool controlOpComplete = false; + ChipLogDetail(DeviceLayer, "HandlePlatformSpecificBLEEvent %d", apEvent->Type); + switch (apEvent->Type) + { + case DeviceEventType::kPlatformLinuxBLECentralConnected: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionComplete(mBLEScanConfig.mAppState, + apEvent->Platform.BLECentralConnected.mConnection); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformLinuxBLECentralConnectFailed: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, apEvent->Platform.BLECentralConnectFailed.mError); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformLinuxBLEWriteComplete: + HandleWriteConfirmation(apEvent->Platform.BLEWriteComplete.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX); + break; + case DeviceEventType::kPlatformLinuxBLESubscribeOpComplete: + if (apEvent->Platform.BLESubscribeOpComplete.mIsSubscribed) + HandleSubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + else + HandleUnsubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + break; + case DeviceEventType::kPlatformLinuxBLEIndicationReceived: + HandleIndicationReceived(apEvent->Platform.BLEIndicationReceived.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX, + PacketBufferHandle::Adopt(apEvent->Platform.BLEIndicationReceived.mData)); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvStartComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralAdvStartComplete.mError); + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + // Do not restart the timer if it is still active. This is to avoid the timer from being restarted + // if the advertising is stopped due to a premature release. + if (!DeviceLayer::SystemLayer().IsTimerActive(HandleAdvertisingTimer, this)) + { + // Start a timer to make sure that the fast advertising is stopped after specified timeout. + SuccessOrExit(err = DeviceLayer::SystemLayer().StartTimer(kFastAdvertiseTimeout, HandleAdvertisingTimer, this)); + } + sInstance.mFlags.Set(Flags::kAdvertising); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvStopComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralAdvStopComplete.mError); + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + + // Transition to the not Advertising state... + if (sInstance.mFlags.Has(Flags::kAdvertising)) + { + sInstance.mFlags.Clear(Flags::kAdvertising); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising stopped"); + } + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvReleased: + // If the advertising was stopped due to a premature release, check if it needs to be restarted. + sInstance.mFlags.Clear(Flags::kAdvertising); + DriveBLEState(); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralRegisterAppComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralRegisterAppComplete.mError); + mFlags.Set(Flags::kAppRegistered); + controlOpComplete = true; + break; + default: + break; + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + sInstance.mFlags.Clear(Flags::kControlOpInProgress); + } + + if (controlOpComplete) + { + mFlags.Clear(Flags::kControlOpInProgress); + DriveBLEState(); + } +} + +uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const +{ + uint16_t mtu = 0; + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + mtu = conId->GetMTU(); +exit: + return mtu; +} + +bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "SubscribeCharacteristic() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_TX), + ChipLogError(DeviceLayer, "SubscribeCharacteristic() called with invalid characteristic ID")); + + VerifyOrExit(conId->SubscribeCharacteristic() == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SubscribeCharacteristic() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_TX), + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() called with invalid characteristic ID")); + + VerifyOrExit(conId->UnsubscribeCharacteristic() == CHIP_NO_ERROR, + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + ChipLogProgress(DeviceLayer, "Closing BLE GATT connection (con %p)", conId); + + VerifyOrExit(conId->CloseConnection() == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "CloseConnection() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendIndication(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(conId->SendIndication(std::move(pBuf)) == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SendIndication() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "SendWriteRequest() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_RX), + ChipLogError(DeviceLayer, "SendWriteRequest() called with invalid characteristic ID")); + + VerifyOrExit(conId->SendWriteRequest(std::move(pBuf)) == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SendWriteRequest() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendReadRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + ChipLogError(Ble, "SendReadRequest: Not implemented"); + return true; +} + +bool BLEManagerImpl::SendReadResponse(BLE_CONNECTION_OBJECT conId, BLE_READ_REQUEST_CONTEXT requestContext, + const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId) +{ + ChipLogError(Ble, "SendReadRBluezonse: Not implemented"); + return true; +} + +void BLEManagerImpl::HandleNewConnection(BLE_CONNECTION_OBJECT conId) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLECentralConnected; + event.Platform.BLECentralConnected.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLECentralConnectFailed; + event.Platform.BLECentralConnectFailed.mError = error; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleWriteComplete(BLE_CONNECTION_OBJECT conId) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEWriteComplete; + event.Platform.BLEWriteComplete.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLESubscribeOpComplete; + event.Platform.BLESubscribeOpComplete.mConnection = conId; + event.Platform.BLESubscribeOpComplete.mIsSubscribed = subscribed; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(value, len); + + ChipLogDetail(DeviceLayer, "Indication received, conn = %p", conId); + + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEIndicationReceived; + event.Platform.BLEIndicationReceived.mConnection = conId; + event.Platform.BLEIndicationReceived.mData = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); + +exit: + if (err != CHIP_NO_ERROR) + ChipLogError(DeviceLayer, "HandleTXCharChanged() failed: %s", ErrorStr(err)); +} + +void BLEManagerImpl::HandleRXCharWrite(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf; + + // Copy the data to a packet buffer. + buf = System::PacketBufferHandle::NewWithData(value, len); + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + // Post an event to the Chip queue to deliver the data into the Chip stack. + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEWriteReceived; + ChipLogProgress(Ble, "Write request received debug %p", conId); + event.CHIPoBLEWriteReceived.ConId = conId; + event.CHIPoBLEWriteReceived.Data = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "HandleRXCharWrite() failed: %s", ErrorStr(err)); + } +} + +void BLEManagerImpl::CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT conId) +{ + ChipLogProgress(DeviceLayer, "Bluez notify CHIPoBluez connection disconnected"); + + // If this was a CHIPoBLE connection, post an event to deliver a connection error to the CHIPoBLE layer. + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEConnectionError; + event.CHIPoBLEConnectionError.ConId = conId; + event.CHIPoBLEConnectionError.Reason = BLE_ERROR_REMOTE_DEVICE_DISCONNECTED; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT conId) +{ + VerifyOrReturn(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + + // Post an event to the Chip queue to process either a CHIPoBLE Subscribe or Unsubscribe based on + // whether the client is enabling or disabling indications. + ChipDeviceEvent event; + event.Type = conId->IsNotifyAcquired() ? DeviceEventType::kCHIPoBLESubscribe : DeviceEventType::kCHIPoBLEUnsubscribe; + event.CHIPoBLESubscribe.ConId = conId; + PlatformMgr().PostEventOrDie(&event); + + ChipLogProgress(DeviceLayer, "CHIPoBLE %s received", + (event.Type == DeviceEventType::kCHIPoBLESubscribe) ? "subscribe" : "unsubscribe"); +} + +void BLEManagerImpl::HandleTXComplete(BLE_CONNECTION_OBJECT conId) +{ + // Post an event to the Chip queue to process the indicate confirmation. + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEIndicateConfirm; + event.CHIPoBLEIndicateConfirm.ConId = conId; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::DriveBLEState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Perform any initialization actions that must occur after the Chip task is running. + if (!mFlags.Has(Flags::kAsyncInitCompleted)) + { + mFlags.Set(Flags::kAsyncInitCompleted); + ExitNow(); + } + + // If there's already a control operation in progress, wait until it completes. + VerifyOrExit(!mFlags.Has(Flags::kControlOpInProgress), /* */); + + // Initializes the Bluez BLE layer if needed. + if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !mFlags.Has(Flags::kBluezBLELayerInitialized)) + { + SuccessOrExit(err = mEndpoint.Init(mIsCentral, mAdapterId)); + mFlags.Set(Flags::kBluezBLELayerInitialized); + } + + // Register the CHIPoBLE application with the Bluez BLE layer if needed. + if (!mIsCentral && mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !mFlags.Has(Flags::kAppRegistered)) + { + SuccessOrExit(err = mEndpoint.RegisterGattApplication()); + mFlags.Set(Flags::kControlOpInProgress); + ExitNow(); + } + + // If the application has enabled CHIPoBLE and BLE advertising... + if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && mFlags.Has(Flags::kAdvertisingEnabled)) + { + // Start/re-start advertising if not already advertising, or if the advertising state of the + // Bluez BLE layer needs to be refreshed. + if (!mFlags.Has(Flags::kAdvertising) || mFlags.Has(Flags::kAdvertisingRefreshNeeded)) + { + mFlags.Clear(Flags::kAdvertisingRefreshNeeded); + + // Configure advertising data if it hasn't been done yet. + if (!mFlags.Has(Flags::kAdvertisingConfigured)) + { + SuccessOrExit(err = mBLEAdvertisement.Init(mEndpoint, mpBLEAdvUUID, mDeviceName)); + mFlags.Set(Flags::kAdvertisingConfigured); + } + + // Setup service data for advertising. + auto serviceDataFlags = BluezAdvertisement::kServiceDataNone; +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (mFlags.Has(Flags::kExtAdvertisingEnabled)) + serviceDataFlags |= BluezAdvertisement::kServiceDataExtendedAnnouncement; +#endif + SuccessOrExit(err = mBLEAdvertisement.SetupServiceData(serviceDataFlags)); + + // Set or update the advertising intervals. + SuccessOrExit(err = mBLEAdvertisement.SetIntervals(GetAdvertisingIntervals())); + + if (!mFlags.Has(Flags::kAdvertising)) + { + // Start advertising. This is an asynchronous step. BLE manager will be notified of + // advertising start completion via a call to NotifyBLEPeripheralAdvStartComplete. + SuccessOrExit(err = mBLEAdvertisement.Start()); + mFlags.Set(Flags::kControlOpInProgress); + ExitNow(); + } + } + } + + // Otherwise stop advertising if needed... + else + { + if (mFlags.Has(Flags::kAdvertising)) + { + SuccessOrExit(err = mBLEAdvertisement.Stop()); + mFlags.Set(Flags::kControlOpInProgress); + + ExitNow(); + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + } +} + +void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) +{ + ChipLogProgress(Ble, "Got notification regarding chip connection closure"); +#if CHIP_DEVICE_CONFIG_ENABLE_WPA && !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + if (mState == kState_NotInitialized) + { + // Close BLE GATT connections to disconnect BlueZ + CloseConnection(conId); + // In Non-Concurrent mode start the Wi-Fi, as BLE has been stopped + DeviceLayer::ConnectivityMgrImpl().StartNonConcurrentWiFiManagement(); + } +#endif // CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION +} + +void BLEManagerImpl::CheckNonConcurrentBleClosing() +{ +#if CHIP_DEVICE_CONFIG_ENABLE_WPA && !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + if (mState == kState_Disconnecting) + { + DeviceLayer::DeviceControlServer::DeviceControlSvr().PostCloseAllBLEConnectionsToOperationalNetworkEvent(); + } +#endif +} + +BluezAdvertisement::AdvertisingIntervals BLEManagerImpl::GetAdvertisingIntervals() const +{ + if (mFlags.Has(Flags::kFastAdvertisingEnabled)) + return { CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX }; +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (mFlags.Has(Flags::kExtAdvertisingEnabled)) + return { CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MAX }; +#endif + return { CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX }; +} + +void BLEManagerImpl::HandleAdvertisingTimer(chip::System::Layer *, void * appState) +{ + auto * self = static_cast(appState); + + if (self->mFlags.Has(Flags::kFastAdvertisingEnabled)) + { + ChipLogDetail(DeviceLayer, "bleAdv Timeout : Start slow advertisement"); + self->_SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + self->mFlags.Clear(Flags::kExtAdvertisingEnabled); + DeviceLayer::SystemLayer().StartTimer(kSlowAdvertiseTimeout, HandleAdvertisingTimer, self); + } + else + { + ChipLogDetail(DeviceLayer, "bleAdv Timeout : Start extended advertisement"); + self->mFlags.Set(Flags::kExtAdvertisingEnabled); + // This will trigger advertising intervals update in the DriveBLEState() function. + self->_SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); +#endif + } +} + +void BLEManagerImpl::InitiateScan(BleScanState scanType) +{ + DriveBLEState(); + + if (scanType == BleScanState::kNotScanning) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "Invalid scan type requested"); + return; + } + + if (!mFlags.Has(Flags::kBluezBLELayerInitialized)) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "BLE Layer is not yet initialized"); + return; + } + + if (mEndpoint.GetAdapter() == nullptr) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "No adapter available for new connection establishment"); + return; + } + + mBLEScanConfig.mBleScanState = scanType; + + CHIP_ERROR err = mDeviceScanner.Init(mEndpoint.GetAdapter(), this); + if (err != CHIP_NO_ERROR) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INTERNAL); + ChipLogError(Ble, "Failed to create a BLE device scanner"); + return; + } + + err = mDeviceScanner.StartScan(kNewConnectionScanTimeout); + if (err != CHIP_NO_ERROR) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + ChipLogError(Ble, "Failed to start a BLE can: %s", chip::ErrorStr(err)); + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err); + return; + } +} + +void BLEManagerImpl::CleanScanConfig() +{ + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, &mEndpoint); + + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; +} + +void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator) +{ + mBLEScanConfig.mDiscriminator = connDiscriminator; + mBLEScanConfig.mAppState = appState; + + // Scan initiation performed async, to ensure that the BLE subsystem is initialized. + DeviceLayer::SystemLayer().ScheduleLambda([this] { InitiateScan(BleScanState::kScanForDiscriminator); }); +} + +CHIP_ERROR BLEManagerImpl::CancelConnection() +{ + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + mEndpoint.CancelConnect(); + // If in discovery mode, stop scan. + else if (mBLEScanConfig.mBleScanState != BleScanState::kNotScanning) + mDeviceScanner.StopScan(); + return CHIP_NO_ERROR; +} + +void BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralRegisterAppComplete; + event.Platform.BLEPeripheralRegisterAppComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvStartComplete; + event.Platform.BLEPeripheralAdvStartComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvStopComplete; + event.Platform.BLEPeripheralAdvStopComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvReleased() +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvReleased; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::OnDeviceScanned(BluezDevice1 & device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) +{ + const char * address = bluez_device1_get_address(&device); + ChipLogProgress(Ble, "New device scanned: %s", address); + + if (mBLEScanConfig.mBleScanState == BleScanState::kScanForDiscriminator) + { + auto isMatch = mBLEScanConfig.mDiscriminator.MatchesLongDiscriminator(info.GetDeviceDiscriminator()); + VerifyOrReturn( + isMatch, + ChipLogError(Ble, "Skip connection: Device discriminator does not match: %u != %u", info.GetDeviceDiscriminator(), + mBLEScanConfig.mDiscriminator.IsShortDiscriminator() ? mBLEScanConfig.mDiscriminator.GetShortValue() + : mBLEScanConfig.mDiscriminator.GetLongValue())); + ChipLogProgress(Ble, "Device discriminator match. Attempting to connect."); + } + else if (mBLEScanConfig.mBleScanState == BleScanState::kScanForAddress) + { + auto isMatch = strcmp(address, mBLEScanConfig.mAddress.c_str()) == 0; + VerifyOrReturn(isMatch, + ChipLogError(Ble, "Skip connection: Device address does not match: %s != %s", address, + mBLEScanConfig.mAddress.c_str())); + ChipLogProgress(Ble, "Device address match. Attempting to connect."); + } + else + { + // Internal consistency error + ChipLogError(Ble, "Unknown discovery type. Ignoring scanned device."); + return; + } + + mBLEScanConfig.mBleScanState = BleScanState::kConnecting; + + chip::DeviceLayer::PlatformMgr().LockChipStack(); + // We StartScan in the ChipStack thread. + // StopScan should also be performed in the ChipStack thread. + // At the same time, the scan timer also needs to be canceled in the ChipStack thread. + mDeviceScanner.StopScan(); + // Stop scanning and then start connecting timer + DeviceLayer::SystemLayer().StartTimer(kConnectTimeout, HandleConnectTimeout, &mEndpoint); + chip::DeviceLayer::PlatformMgr().UnlockChipStack(); + + CHIP_ERROR err = mEndpoint.ConnectDevice(device); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Ble, "Device connection failed: %" CHIP_ERROR_FORMAT, err.Format())); + + ChipLogProgress(Ble, "New device connected: %s", address); +} + +void BLEManagerImpl::OnScanComplete() +{ + switch (mBLEScanConfig.mBleScanState) + { + case BleScanState::kNotScanning: + ChipLogProgress(Ble, "Scan complete notification without an active scan."); + break; + case BleScanState::kScanForAddress: + case BleScanState::kScanForDiscriminator: + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + ChipLogProgress(Ble, "Scan complete. No matching device found."); + break; + case BleScanState::kConnecting: + break; + } +} + +void BLEManagerImpl::OnScanError(CHIP_ERROR err) +{ + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err); + ChipLogError(Ble, "BLE scan error: %" CHIP_ERROR_FORMAT, err.Format()); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/BLEManagerImpl.h b/src/platform/NuttX/BLEManagerImpl.h new file mode 100644 index 00000000000000..790373c28b3830 --- /dev/null +++ b/src/platform/NuttX/BLEManagerImpl.h @@ -0,0 +1,241 @@ +/* + * + * Copyright (c) 2020-2021 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. + */ + +/** + * @file + * Provides an implementation of the BLEManager singleton object + * for the Linux platforms. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "bluez/BluezAdvertisement.h" +#include "bluez/BluezEndpoint.h" +#include "bluez/ChipDeviceScanner.h" +#include "bluez/Types.h" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +void HandleIncomingBleConnection(Ble::BLEEndPoint * bleEP); + +enum class BleScanState : uint8_t +{ + kNotScanning, + kScanForDiscriminator, + kScanForAddress, + kConnecting, +}; + +struct BLEScanConfig +{ + // If an active scan for connection is being performed + BleScanState mBleScanState = BleScanState::kNotScanning; + + // If scanning by discriminator, what are we scanning for + SetupDiscriminator mDiscriminator; + + // If scanning by address, what address are we searching for + std::string mAddress; + + // Optional argument to be passed to callback functions provided by the BLE scan/connect requestor + void * mAppState = nullptr; +}; + +/** + * Concrete implementation of the BLEManagerImpl singleton object for the Linux platforms. + */ +class BLEManagerImpl final : public BLEManager, + private Ble::BleLayer, + private Ble::BlePlatformDelegate, + private Ble::BleApplicationDelegate, + private Ble::BleConnectionDelegate, + private ChipDeviceScannerDelegate +{ + // Allow the BLEManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend BLEManager; + +public: + CHIP_ERROR ConfigureBle(uint32_t aAdapterId, bool aIsCentral); + void OnScanError(CHIP_ERROR error) override; + + // Driven by BlueZ IO + static void HandleNewConnection(BLE_CONNECTION_OBJECT conId); + static void HandleConnectFailed(CHIP_ERROR error); + static void HandleWriteComplete(BLE_CONNECTION_OBJECT conId); + static void HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed); + static void HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len); + static void HandleRXCharWrite(BLE_CONNECTION_OBJECT user_data, const uint8_t * value, size_t len); + static void CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT user_data); + static void HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT user_data); + static void HandleTXComplete(BLE_CONNECTION_OBJECT user_data); + + static void NotifyBLEPeripheralRegisterAppComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvStartComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvStopComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvReleased(); + +private: + // ===== Members that implement the BLEManager internal interface. + + CHIP_ERROR _Init(); + void _Shutdown(); + bool _IsAdvertisingEnabled(); + CHIP_ERROR _SetAdvertisingEnabled(bool val); + bool _IsAdvertising(); + CHIP_ERROR _SetAdvertisingMode(BLEAdvertisingMode mode); + CHIP_ERROR _GetDeviceName(char * buf, size_t bufSize); + CHIP_ERROR _SetDeviceName(const char * deviceName); + uint16_t _NumConnections(); + + void _OnPlatformEvent(const ChipDeviceEvent * event); + void HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * event); + BleLayer * _GetBleLayer(); + + // ===== Members that implement virtual methods on BlePlatformDelegate. + + bool SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + bool UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + bool CloseConnection(BLE_CONNECTION_OBJECT conId) override; + uint16_t GetMTU(BLE_CONNECTION_OBJECT conId) const override; + bool SendIndication(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendWriteRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendReadRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendReadResponse(BLE_CONNECTION_OBJECT conId, BLE_READ_REQUEST_CONTEXT requestContext, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + + // ===== Members that implement virtual methods on BleApplicationDelegate. + + void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) override; + void CheckNonConcurrentBleClosing() override; + + // ===== Members that implement virtual methods on BleConnectionDelegate. + + void NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator) override; + void NewConnection(BleLayer * bleLayer, void * appState, BLE_CONNECTION_OBJECT connObj) override{}; + CHIP_ERROR CancelConnection() override; + + // ===== Members that implement virtual methods on ChipDeviceScannerDelegate + + void OnDeviceScanned(BluezDevice1 & device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override; + void OnScanComplete() override; + + // ===== Members for internal use by the following friends. + + friend BLEManager & BLEMgr(); + friend BLEManagerImpl & BLEMgrImpl(); + + static BLEManagerImpl sInstance; + + // ===== Private members reserved for use by this class only. + + enum class Flags : uint16_t + { + kAsyncInitCompleted = 0x0001, /**< One-time asynchronous initialization actions have been performed. */ + kBluezBLELayerInitialized = 0x0002, /**< The Bluez layer has been initialized. */ + kAppRegistered = 0x0004, /**< The CHIPoBLE application has been registered with the Bluez layer. */ + kAdvertisingConfigured = 0x0008, /**< CHIPoBLE advertising has been configured in the Bluez layer. */ + kAdvertising = 0x0010, /**< The system is currently CHIPoBLE advertising. */ + kControlOpInProgress = 0x0020, /**< An async control operation has been issued to the ESP BLE layer. */ + kAdvertisingEnabled = 0x0040, /**< The application has enabled CHIPoBLE advertising. */ + kFastAdvertisingEnabled = 0x0080, /**< The application has enabled fast advertising. */ + kUseCustomDeviceName = 0x0100, /**< The application has configured a custom BLE device name. */ + kAdvertisingRefreshNeeded = 0x0200, /**< The advertising configuration/state in BLE layer needs to be updated. */ + kExtAdvertisingEnabled = 0x0400, /**< The application has enabled CHIPoBLE extended advertising. */ + }; + + enum + { + kMaxConnections = 1, // TODO: right max connection + kMaxDeviceNameLength = 20, // TODO: right-size this + kMaxAdvertisementDataSetSize = 31 // TODO: verify this + }; + + void DriveBLEState(); + BluezAdvertisement::AdvertisingIntervals GetAdvertisingIntervals() const; + static void HandleAdvertisingTimer(chip::System::Layer *, void * appState); + void InitiateScan(BleScanState scanType); + void CleanScanConfig(); + + CHIPoBLEServiceMode mServiceMode; + BitFlags mFlags; + + uint32_t mAdapterId = 0; + char mDeviceName[kMaxDeviceNameLength + 1]; + bool mIsCentral = false; + BluezEndpoint mEndpoint; + + BluezAdvertisement mBLEAdvertisement; + const char * mpBLEAdvUUID = nullptr; + + ChipDeviceScanner mDeviceScanner; + BLEScanConfig mBLEScanConfig; +}; + +/** + * Returns a reference to the public interface of the BLEManager singleton object. + * + * Internal components should use this to access features of the BLEManager object + * that are common to all platforms. + */ +inline BLEManager & BLEMgr() +{ + return BLEManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the BLEManager singleton object. + * + * Internal components can use this to gain access to features of the BLEManager + * that are specific to the Linux platforms. + */ +inline BLEManagerImpl & BLEMgrImpl() +{ + return BLEManagerImpl::sInstance; +} + +inline Ble::BleLayer * BLEManagerImpl::_GetBleLayer() +{ + return this; +} + +inline bool BLEManagerImpl::_IsAdvertisingEnabled() +{ + return mFlags.Has(Flags::kAdvertisingEnabled); +} + +inline bool BLEManagerImpl::_IsAdvertising() +{ + return mFlags.Has(Flags::kAdvertising); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/BUILD.gn b/src/platform/NuttX/BUILD.gn new file mode 100644 index 00000000000000..aa63339eaac526 --- /dev/null +++ b/src/platform/NuttX/BUILD.gn @@ -0,0 +1,160 @@ +# Copyright (c) 2021 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${build_root}/config/linux/pkg_config.gni") + +import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") + +assert(chip_device_platform == "linux" || chip_device_platform == "nuttx") + +if (chip_use_pw_logging) { + import("//build_overrides/pigweed.gni") +} + +if (chip_enable_openthread) { + import("//build_overrides/openthread.gni") + import("//build_overrides/ot_br_posix.gni") +} + +if (chip_mdns == "platform") { + pkg_config("avahi_client_config") { + packages = [ "avahi-client" ] + } +} + +static_library("NuttX") { + sources = [ + "../DeviceSafeQueue.cpp", + "../DeviceSafeQueue.h", + "../GLibTypeDeleter.h", + "../SingletonConfigurationManager.cpp", + "CHIPDevicePlatformConfig.h", + "CHIPDevicePlatformEvent.h", + "CHIPLinuxStorage.cpp", + "CHIPLinuxStorage.h", + "CHIPLinuxStorageIni.cpp", + "CHIPLinuxStorageIni.h", + "CHIPPlatformConfig.h", + "ConfigurationManagerImpl.cpp", + "ConfigurationManagerImpl.h", + "ConnectivityManagerImpl.cpp", + "ConnectivityManagerImpl.h", + "ConnectivityUtils.cpp", + "ConnectivityUtils.h", + "DeviceInstanceInfoProviderImpl.cpp", + "DeviceInstanceInfoProviderImpl.h", + "DiagnosticDataProviderImpl.cpp", + "DiagnosticDataProviderImpl.h", + "InetPlatformConfig.h", + "KeyValueStoreManagerImpl.cpp", + "KeyValueStoreManagerImpl.h", + "NetworkCommissioningDriver.h", + "NetworkCommissioningEthernetDriver.cpp", + "PlatformManagerImpl.cpp", + "PlatformManagerImpl.h", + "PosixConfig.cpp", + "PosixConfig.h", + "SystemPlatformConfig.h", + "SystemTimeSupport.cpp", + ] + + deps = [ + "${chip_root}/src/app/icd/server:icd-server-config", + "${chip_root}/src/credentials:credentials_header", + "${chip_root}/src/setup_payload", + ] + + if (!chip_use_external_logging) { + sources += [ "Logging.cpp" ] + deps += [ "${chip_root}/src/platform/logging:headers" ] + } + + if (chip_enable_openthread) { + sources += [ "NetworkCommissioningThreadDriver.cpp" ] + } + + if (chip_enable_ble) { + sources += [ + "BLEManagerImpl.cpp", + "BLEManagerImpl.h", + "BlePlatformConfig.h", + "bluez/AdapterIterator.cpp", + "bluez/AdapterIterator.h", + "bluez/BluezAdvertisement.cpp", + "bluez/BluezAdvertisement.h", + "bluez/BluezConnection.cpp", + "bluez/BluezConnection.h", + "bluez/BluezEndpoint.cpp", + "bluez/BluezEndpoint.h", + "bluez/BluezObjectIterator.h", + "bluez/BluezObjectList.h", + "bluez/ChipDeviceScanner.cpp", + "bluez/ChipDeviceScanner.h", + "bluez/Types.h", + ] + } + + public_deps = [ + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/platform:platform_base", + "${chip_root}/third_party/inipp", + ] + + public_configs = [] + + if (chip_mdns == "platform") { + sources += [ + "DnssdImpl.cpp", + "DnssdImpl.h", + ] + + deps += [ "${chip_root}/src/lib/dnssd:platform_header" ] + + public_configs += [ ":avahi_client_config" ] + } + + if (chip_enable_ota_requestor) { + sources += [ + "OTAImageProcessorImpl.cpp", + "OTAImageProcessorImpl.h", + ] + } + + if (chip_enable_openthread) { + sources += [ + "ThreadStackManagerImpl.cpp", + "ThreadStackManagerImpl.h", + ] + + public_deps += [ "dbus/openthread" ] + } + + if (chip_use_pw_logging) { + deps += [ "$dir_pw_log" ] + } + + if (chip_enable_wifi) { + sources += [ "NetworkCommissioningWiFiDriver.cpp" ] + + public_deps += [ "dbus/wpa" ] + } + + if (chip_enable_ble) { + public_deps += [ "dbus/bluez" ] + } +} diff --git a/src/platform/NuttX/BlePlatformConfig.h b/src/platform/NuttX/BlePlatformConfig.h new file mode 100644 index 00000000000000..54640b707ab720 --- /dev/null +++ b/src/platform/NuttX/BlePlatformConfig.h @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP BLE + * Layer on Linux platforms. + * + */ + +#pragma once + +namespace chip { +namespace DeviceLayer { +namespace Internal { +class BluezConnection; +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +// ==================== Platform Adaptations ==================== +#define BLE_CONNECTION_OBJECT chip::DeviceLayer::Internal::BluezConnection * +#define BLE_CONNECTION_UNINITIALIZED nullptr + +// ========== Platform-specific Configuration Overrides ========= + +/* none so far */ diff --git a/src/platform/NuttX/CHIPDevicePlatformConfig.h b/src/platform/NuttX/CHIPDevicePlatformConfig.h new file mode 100644 index 00000000000000..530e2716308842 --- /dev/null +++ b/src/platform/NuttX/CHIPDevicePlatformConfig.h @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Platform-specific configuration overrides for the chip Device Layer + * on Linux platforms. + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 1 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 +#else +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 0 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 +#endif + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_THREAD +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD CHIP_ENABLE_OPENTHREAD +#endif + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#define CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE 0 +#endif + +// Start GLib main event loop if BLE, Thread or WiFi is enabled. This is needed +// to handle D-Bus communication with BlueZ or wpa_supplicant. +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE || CHIP_DEVICE_CONFIG_ENABLE_THREAD || CHIP_DEVICE_CONFIG_ENABLE_WIFI +#define CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP 1 +#else +#define CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP 0 +#endif + +// ========== Platform-specific Configuration ========= + +// These are configuration options that are unique to Linux platforms. +// These can be overridden by the application as needed. + +// ========== Platform-specific Configuration Overrides ========= + +#ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE 8192 +#endif // CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE + +#ifndef CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE 8192 +#endif // CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE + +#ifndef CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS 1 +#endif // CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY_FULL 0 diff --git a/src/platform/NuttX/CHIPDevicePlatformEvent.h b/src/platform/NuttX/CHIPDevicePlatformEvent.h new file mode 100644 index 00000000000000..019dcb31e3403a --- /dev/null +++ b/src/platform/NuttX/CHIPDevicePlatformEvent.h @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Defines platform-specific event types and data for the chip + * Device Layer on Linux platforms. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace DeviceLayer { + +namespace DeviceEventType { + +/** + * Enumerates Linux platform-specific event types that are visible to the application. + */ +enum PublicPlatformSpecificEventTypes +{ + /* None currently defined */ +}; + +/** + * Enumerates Linux platform-specific event types that are internal to the chip Device Layer. + */ +enum InternalPlatformSpecificEventTypes +{ + kPlatformLinuxEvent = kRange_InternalPlatformSpecific, + kPlatformLinuxBLECentralConnected, + kPlatformLinuxBLECentralConnectFailed, + kPlatformLinuxBLEWriteComplete, + kPlatformLinuxBLESubscribeOpComplete, + kPlatformLinuxBLEIndicationReceived, + kPlatformLinuxBLEC1WriteEvent, + kPlatformLinuxBLEOutOfBuffersEvent, + kPlatformLinuxBLEPeripheralRegisterAppComplete, + kPlatformLinuxBLEPeripheralAdvStartComplete, + kPlatformLinuxBLEPeripheralAdvStopComplete, + kPlatformLinuxBLEPeripheralAdvReleased, +}; + +} // namespace DeviceEventType + +/** + * Represents platform-specific event information for Linux platforms. + */ +struct ChipDevicePlatformEvent +{ + union + { + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLECentralConnected; + struct + { + CHIP_ERROR mError; + } BLECentralConnectFailed; + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLEWriteComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + bool mIsSubscribed; + } BLESubscribeOpComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + chip::System::PacketBuffer * mData; + } BLEIndicationReceived; + struct + { + CHIP_ERROR mError; + } BLEPeripheralRegisterAppComplete; + struct + { + CHIP_ERROR mError; + } BLEPeripheralAdvStartComplete; + struct + { + CHIP_ERROR mError; + } BLEPeripheralAdvStopComplete; + }; +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorage.cpp b/src/platform/NuttX/CHIPLinuxStorage.cpp new file mode 100644 index 00000000000000..866ea1bf675afb --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorage.cpp @@ -0,0 +1,353 @@ +/* + * + * Copyright (c) 2020-2022 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. + */ + +/** + * @file + * This file implements a class for managing client application + * user-editable settings on Linux platform. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +ChipLinuxStorage::ChipLinuxStorage() +{ + mDirty = false; +} + +ChipLinuxStorage::~ChipLinuxStorage() {} + +CHIP_ERROR ChipLinuxStorage::Init(const char * configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile)); + if (mInitialized) + { + ChipLogError(DeviceLayer, "ChipLinuxStorage::Init: Attempt to re-initialize with KVS config file: %s", + StringOrNullMarker(configFile)); + return CHIP_NO_ERROR; + } + + mConfigPath.assign(configFile); + retval = ChipLinuxStorageIni::Init(); + + if (retval == CHIP_NO_ERROR) + { + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + // Create default setting file if not exist. + if (!ifs.good()) + { + mDirty = true; + retval = Commit(); + mDirty = false; + } + } + + if (retval == CHIP_NO_ERROR) + { + retval = ChipLinuxStorageIni::AddConfig(mConfigPath); + } + + mInitialized = true; + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, bool & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + uint32_t result; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, result); + val = (result != 0); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt16Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt64Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetStringValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetBinaryBlobValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, bool val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (val) + { + retval = WriteValue(key, static_cast(1)); + } + else + { + retval = WriteValue(key, static_cast(0)); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint16_t val) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%u", val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint32_t val) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%" PRIu32, val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint64_t val) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%" PRIu64, val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValueStr(const char * key, const char * val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::AddEntry(key, val); + + mDirty = true; + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValueBin(const char * key, const uint8_t * data, size_t dataLen) +{ + static const size_t kMaxBlobSize = 5 * 1024; + + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen = 0; + size_t expectedEncodedLen = ((dataLen + 3) * 4) / 3; + + // We only support encoding blobs up to 5kb + if (dataLen > kMaxBlobSize) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + // Compute our expectedEncodedLen + // Allocate just enough space for the encoded data, and the NULL terminator + if (retval == CHIP_NO_ERROR) + { + if (!encodedData.Alloc(expectedEncodedLen + 1)) + { + retval = CHIP_ERROR_NO_MEMORY; + } + } + + // Encode it + if (retval == CHIP_NO_ERROR) + { + // We tested above that dataLen is no more than kMaxBlobSize. + static_assert(kMaxBlobSize < UINT16_MAX, "dataLen won't fit"); + encodedDataLen = Base64Encode(data, static_cast(dataLen), encodedData.Get()); + encodedData[encodedDataLen] = 0; + } + + // Store it + if (retval == CHIP_NO_ERROR) + { + WriteValueStr(key, encodedData.Get()); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearValue(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveEntry(key); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearAll() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveAll(); + + mLock.unlock(); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + retval = Commit(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +bool ChipLinuxStorage::HasValue(const char * key) +{ + bool retval; + + mLock.lock(); + + retval = ChipLinuxStorageIni::HasValue(key); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::Commit() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (mDirty && !mConfigPath.empty()) + { + mLock.lock(); + + retval = ChipLinuxStorageIni::CommitConfig(mConfigPath); + + mLock.unlock(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorage.h b/src/platform/NuttX/CHIPLinuxStorage.h new file mode 100644 index 00000000000000..57ba0f054e4a89 --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorage.h @@ -0,0 +1,98 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * This file defines a class for managing client application + * user-editable settings. CHIP settings are partitioned into two + * distinct areas: + * + * 1. immutable / durable: factory parameters (CHIP_DEFAULT_FACTORY_PATH) + * 2. mutable / ephemeral: user parameters (CHIP_DEFAULT_CONFIG_PATH/CHIP_DEFAULT_DATA_PATH) + * + * The ephemeral partition should be erased during factory reset. + * + * ChipLinuxStorage wraps the storage class ChipLinuxStorageIni with mutex. + * + */ + +#pragma once + +#include +#include + +#ifndef FATCONFDIR +#define FATCONFDIR "/tmp" +#endif + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/tmp" +#endif + +#ifndef LOCALSTATEDIR +#define LOCALSTATEDIR "/tmp" +#endif + +#define CHIP_DEFAULT_FACTORY_PATH \ + FATCONFDIR "/" \ + "chip_factory.ini" +#define CHIP_DEFAULT_CONFIG_PATH \ + SYSCONFDIR "/" \ + "chip_config.ini" +#define CHIP_DEFAULT_DATA_PATH \ + LOCALSTATEDIR "/" \ + "chip_counters.ini" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage : private ChipLinuxStorageIni +{ +public: + ChipLinuxStorage(); + ~ChipLinuxStorage(); + + CHIP_ERROR Init(const char * configFile); + CHIP_ERROR ReadValue(const char * key, bool & val); + CHIP_ERROR ReadValue(const char * key, uint16_t & val); + CHIP_ERROR ReadValue(const char * key, uint32_t & val); + CHIP_ERROR ReadValue(const char * key, uint64_t & val); + CHIP_ERROR ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR WriteValue(const char * key, bool val); + CHIP_ERROR WriteValue(const char * key, uint16_t val); + CHIP_ERROR WriteValue(const char * key, uint32_t val); + CHIP_ERROR WriteValue(const char * key, uint64_t val); + CHIP_ERROR WriteValueStr(const char * key, const char * val); + CHIP_ERROR WriteValueBin(const char * key, const uint8_t * data, size_t dataLen); + CHIP_ERROR ClearValue(const char * key); + CHIP_ERROR ClearAll(); + CHIP_ERROR Commit(); + bool HasValue(const char * key); + +private: + std::mutex mLock; + bool mDirty; + std::string mConfigPath; + bool mInitialized = false; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorageIni.cpp b/src/platform/NuttX/CHIPLinuxStorageIni.cpp new file mode 100644 index 00000000000000..e9dbe107ffad9e --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorageIni.cpp @@ -0,0 +1,404 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store object + * using IniPP on Linux platform. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace chip::IniEscaping; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +CHIP_ERROR ChipLinuxStorageIni::Init() +{ + return RemoveAll(); +} + +CHIP_ERROR ChipLinuxStorageIni::GetDefaultSection(std::map & section) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + auto it = mConfigStore.sections.find("DEFAULT"); + + if (it != mConfigStore.sections.end()) + { + section = mConfigStore.sections["DEFAULT"]; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + if (ifs.is_open()) + { + mConfigStore.parse(ifs); + ifs.close(); + } + else + { + ChipLogError(DeviceLayer, "Failed to open config file: %s", configFile.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +// Updating a file atomically and durably on Linux requires: +// 1. Writing to a temporary file +// 2. Sync'ing the temp file to commit updated data +// 3. Using rename() to overwrite the existing file +CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::string tmpPath = configFile + "-XXXXXX"; + + int fd = mkstemp(&tmpPath[0]); + if (fd != -1) + { + std::ofstream ofs; + + ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str()); + + ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); + mConfigStore.generate(ofs); + + close(fd); + + if (rename(tmpPath.c_str(), configFile.c_str()) == 0) + { + ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str()); + } + else + { + ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); + retval = CHIP_ERROR_WRITE_FAILED; + } + } + else + { + ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUIntValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt64Value(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + std::string value; + if (inipp::extract(section[escapedKey], value)) + { + size_t len = value.size(); + + if (len > bufSize - 1) + { + outLen = len; + retval = CHIP_ERROR_BUFFER_TOO_SMALL; + } + else + { + outLen = value.copy(buf, len); + buf[outLen] = '\0'; + } + } + else + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobDataAndLengths(const char * key, + chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen) +{ + size_t encodedDataPaddingLen = 0; + std::map section; + CHIP_ERROR err = GetDefaultSection(section); + if (err != CHIP_NO_ERROR) + { + return err; + } + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + if (it == section.end()) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::string value; + + // Compute the expectedDecodedLen + if (!inipp::extract(section[escapedKey], value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + size_t len = value.size(); + if (!encodedData.Alloc(len + 1)) + { + return CHIP_ERROR_NO_MEMORY; + } + encodedDataLen = value.copy(encodedData.Get(), len); + encodedData[encodedDataLen] = '\0'; + + // Check if encoded data was padded. Only "=" or "==" padding combinations are allowed. + if ((encodedDataLen > 0) && (encodedData[encodedDataLen - 1] == '=')) + { + encodedDataPaddingLen++; + if ((encodedDataLen > 1) && (encodedData[encodedDataLen - 2] == '=')) + encodedDataPaddingLen++; + } + + decodedDataLen = ((encodedDataLen - encodedDataPaddingLen) * 3) / 4; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen; + size_t expectedDecodedLen = 0; + + retval = GetBinaryBlobDataAndLengths(key, encodedData, encodedDataLen, expectedDecodedLen); + + // Check the size + if (retval != CHIP_NO_ERROR) + { + return retval; + } + + if (expectedDecodedLen > bufSize) + { + decodedDataLen = expectedDecodedLen; + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + if (encodedDataLen > UINT16_MAX) + { + // We can't even pass this length into Base64Decode. + return CHIP_ERROR_DECODE_FAILED; + } + + // Decode it + // Cast is safe because we checked encodedDataLen above. + decodedDataLen = Base64Decode(encodedData.Get(), static_cast(encodedDataLen), decodedData); + if (decodedDataLen == UINT16_MAX || decodedDataLen > expectedDecodedLen) + { + return CHIP_ERROR_DECODE_FAILED; + } + + return CHIP_NO_ERROR; +} + +bool ChipLinuxStorageIni::HasValue(const char * key) +{ + std::map section; + + if (GetDefaultSection(section) != CHIP_NO_ERROR) + return false; + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + return it != section.end(); +} + +CHIP_ERROR ChipLinuxStorageIni::AddEntry(const char * key, const char * value) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if ((key != nullptr) && (value != nullptr)) + { + std::string escapedKey = EscapeKey(key); + std::map & section = mConfigStore.sections["DEFAULT"]; + section[escapedKey] = std::string(value); + } + else + { + ChipLogError(DeviceLayer, "Invalid input argument, failed to add entry"); + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveEntry(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + std::map & section = mConfigStore.sections["DEFAULT"]; + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + section.erase(it); + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveAll() +{ + mConfigStore.clear(); + + return CHIP_NO_ERROR; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorageIni.h b/src/platform/NuttX/CHIPLinuxStorageIni.h new file mode 100644 index 00000000000000..1c6e564c12bf35 --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorageIni.h @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store interface + * using IniPP. + * + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorageIni +{ +public: + CHIP_ERROR Init(); + CHIP_ERROR AddConfig(const std::string & configFile); + CHIP_ERROR CommitConfig(const std::string & configFile); + CHIP_ERROR GetUInt16Value(const char * key, uint16_t & val); + CHIP_ERROR GetUIntValue(const char * key, uint32_t & val); + CHIP_ERROR GetUInt64Value(const char * key, uint64_t & val); + CHIP_ERROR GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen); + bool HasValue(const char * key); + +protected: + CHIP_ERROR AddEntry(const char * key, const char * value); + CHIP_ERROR RemoveEntry(const char * key); + CHIP_ERROR RemoveAll(); + +private: + CHIP_ERROR GetDefaultSection(std::map & section); + CHIP_ERROR GetBinaryBlobDataAndLengths(const char * key, chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen); + inipp::Ini mConfigStore; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPPlatformConfig.h b/src/platform/NuttX/CHIPPlatformConfig.h new file mode 100644 index 00000000000000..9e2832307f27b2 --- /dev/null +++ b/src/platform/NuttX/CHIPPlatformConfig.h @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file + * Platform-specific configuration overrides for CHIP on + * Linux platforms. + */ + +#pragma once + +// ==================== General Platform Adaptations ==================== + +#define CHIP_CONFIG_ABORT() abort() + +using CHIP_CONFIG_PERSISTED_STORAGE_KEY_TYPE = const char *; +#define CHIP_CONFIG_PERSISTED_STORAGE_MAX_KEY_LENGTH 16 + +#define CHIP_CONFIG_LIFETIIME_PERSISTED_COUNTER_KEY "life-count" + +#define CHIP_CONFIG_ERROR_FORMAT_AS_STRING 1 +#define CHIP_CONFIG_ERROR_SOURCE 1 + +#define CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE 1 + +#define CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT 1 + +// ==================== Security Adaptations ==================== + +// If unspecified, assume crypto is fast on Linux +#ifndef CHIP_CONFIG_SLOW_CRYPTO +#define CHIP_CONFIG_SLOW_CRYPTO 0 +#endif // CHIP_CONFIG_SLOW_CRYPTO + +// ==================== General Configuration Overrides ==================== + +#ifndef CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS +#define CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS 8 +#endif // CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS + +#ifndef CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS +#define CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS 8 +#endif // CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS + +#ifndef CHIP_LOG_FILTERING +#define CHIP_LOG_FILTERING 1 +#endif // CHIP_LOG_FILTERING + +#ifndef CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS +#define CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS 1 +#endif // CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS + +// ==================== Security Configuration Overrides ==================== + +#ifndef CHIP_CONFIG_KVS_PATH +#define CHIP_CONFIG_KVS_PATH "/tmp/chip_kvs" +#endif // CHIP_CONFIG_KVS_PATH diff --git a/src/platform/NuttX/ConfigurationManagerImpl.cpp b/src/platform/NuttX/ConfigurationManagerImpl.cpp new file mode 100644 index 00000000000000..e451832433f2c2 --- /dev/null +++ b/src/platform/NuttX/ConfigurationManagerImpl.cpp @@ -0,0 +1,385 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * 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. + */ + +/** + * @file + * Provides the implementation of the Device Layer ConfigurationManager object + * for Linux platforms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { + +using namespace ::chip::DeviceLayer::Internal; + +ConfigurationManagerImpl & ConfigurationManagerImpl::GetDefaultInstance() +{ + static ConfigurationManagerImpl sInstance; + return sInstance; +} + +CHIP_ERROR ConfigurationManagerImpl::Init() +{ + CHIP_ERROR err; + uint32_t rebootCount; + + // Force initialization of NVS namespaces if they doesn't already exist. + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipFactory); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipConfig); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipCounters); + SuccessOrExit(err); + + // Initialize the generic implementation base class. + err = Internal::GenericConfigurationManagerImpl::Init(); + SuccessOrExit(err); + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_VendorId)) + { + err = StoreVendorId(CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_ProductId)) + { + err = StoreProductId(CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID); + SuccessOrExit(err); + } + + if (PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_RebootCount)) + { + err = GetRebootCount(rebootCount); + SuccessOrExit(err); + + err = StoreRebootCount(rebootCount + 1); + SuccessOrExit(err); + } + else + { + // The first boot after factory reset of the Node. + err = StoreRebootCount(1); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_TotalOperationalHours)) + { + err = StoreTotalOperationalHours(0); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_BootReason)) + { + err = StoreBootReason(to_underlying(BootReasonType::kUnspecified)); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_RegulatoryLocation)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kIndoor); + err = WriteConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, location); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_LocationCapability)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kIndoorOutdoor); + err = WriteConfigValue(PosixConfig::kConfigKey_LocationCapability, location); + SuccessOrExit(err); + } + + err = CHIP_NO_ERROR; + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::GetPrimaryWiFiMACAddress(uint8_t * buf) +{ + struct ifaddrs * addresses = nullptr; + struct sockaddr_ll * mac = nullptr; + CHIP_ERROR error = CHIP_NO_ERROR; + + // TODO: ideally the buffer size should have been passed as a span, however + // for now use the size that is validated in GenericConfigurationManagerImpl.ipp + constexpr size_t kExpectedBufMinSize = ConfigurationManager::kPrimaryMACAddressLength; + memset(buf, 0, kExpectedBufMinSize); + + // Prioritize address for interface matching the WiFi interface name + // specified in the config headers. Otherwise, use the address for the + // first non-loopback interface. + VerifyOrExit(getifaddrs(&addresses) == 0, error = CHIP_ERROR_INTERNAL); + for (auto addr = addresses; addr != nullptr; addr = addr->ifa_next) + { + if ((addr->ifa_addr) && (addr->ifa_addr->sa_family == AF_PACKET)) + { + if (strncmp(addr->ifa_name, CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, IFNAMSIZ) == 0) + { + mac = (struct sockaddr_ll *) addr->ifa_addr; + break; + } + + if (strncmp(addr->ifa_name, "lo", IFNAMSIZ) != 0 && !mac) + { + mac = (struct sockaddr_ll *) addr->ifa_addr; + } + } + } + + if (mac) + { + memcpy(buf, mac->sll_addr, std::min(mac->sll_halen, kExpectedBufMinSize)); + } + else + { + error = CHIP_ERROR_NO_ENDPOINT; + } + + freeifaddrs(addresses); + +exit: + return error; +} + +bool ConfigurationManagerImpl::CanFactoryReset() +{ + // TODO(#742): query the application to determine if factory reset is allowed. + return true; +} + +void ConfigurationManagerImpl::InitiateFactoryReset() +{ + PlatformMgr().ScheduleWork(DoFactoryReset); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + + CHIP_ERROR err = ReadConfigValue(configKey, value); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + return WriteConfigValue(configKey, value); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, bool & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint16_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint32_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint64_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueStr(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueBin(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, bool val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint16_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint32_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint64_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str) +{ + return PosixConfig::WriteConfigValueStr(key, str); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + return PosixConfig::WriteConfigValueStr(key, str, strLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return PosixConfig::WriteConfigValueBin(key, data, dataLen); +} + +void ConfigurationManagerImpl::RunConfigUnitTest() +{ + PosixConfig::RunConfigUnitTest(); +} + +void ConfigurationManagerImpl::DoFactoryReset(intptr_t arg) +{ + CHIP_ERROR err; + + ChipLogProgress(DeviceLayer, "Performing factory reset"); + + err = PosixConfig::FactoryResetConfig(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset configurations: %s", ErrorStr(err)); + } + + err = PosixConfig::FactoryResetCounters(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset counters: %s", ErrorStr(err)); + } + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + + ChipLogProgress(DeviceLayer, "Clearing Thread provision"); + ThreadStackMgr().ErasePersistentInfo(); + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + + // Restart the system. + ChipLogProgress(DeviceLayer, "System restarting (not implemented)"); + // TODO(#742): restart CHIP exe +} + +CHIP_ERROR ConfigurationManagerImpl::StoreVendorId(uint16_t vendorId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreProductId(uint16_t productId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_ProductId, productId); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRebootCount(uint32_t & rebootCount) +{ + return ReadConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreRebootCount(uint32_t rebootCount) +{ + return WriteConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + return ReadConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreTotalOperationalHours(uint32_t totalOperationalHours) +{ + return WriteConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::GetBootReason(uint32_t & bootReason) +{ + return ReadConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreBootReason(uint32_t bootReason) +{ + return WriteConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRegulatoryLocation(uint8_t & location) +{ + uint32_t value; + + if (CHIP_NO_ERROR != ReadConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, value)) + { + ReturnErrorOnFailure(GetLocationCapability(location)); + + if (CHIP_NO_ERROR != StoreRegulatoryLocation(location)) + { + ChipLogError(DeviceLayer, "Failed to store RegulatoryLocation"); + } + } + else + { + location = static_cast(value); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConfigurationManagerImpl::GetLocationCapability(uint8_t & location) +{ + uint32_t value = 0; + + CHIP_ERROR err = ReadConfigValue(PosixConfig::kConfigKey_LocationCapability, value); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(value <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + location = static_cast(value); + } + + return err; +} + +ConfigurationManager & ConfigurationMgrImpl() +{ + return ConfigurationManagerImpl::GetDefaultInstance(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConfigurationManagerImpl.h b/src/platform/NuttX/ConfigurationManagerImpl.h new file mode 100644 index 00000000000000..f995307c1bc937 --- /dev/null +++ b/src/platform/NuttX/ConfigurationManagerImpl.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Provides an implementation of the ConfigurationManager object + * for Linux platforms. + */ + +#pragma once + +#include "platform/internal/DeviceNetworkInfo.h" +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the ConfigurationManager singleton object for the Linux platform. + */ +class ConfigurationManagerImpl : public Internal::GenericConfigurationManagerImpl +{ +public: + CHIP_ERROR StoreVendorId(uint16_t vendorId); + CHIP_ERROR StoreProductId(uint16_t productId); + + CHIP_ERROR GetRebootCount(uint32_t & rebootCount) override; + CHIP_ERROR StoreRebootCount(uint32_t rebootCount) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR StoreTotalOperationalHours(uint32_t totalOperationalHours) override; + CHIP_ERROR GetBootReason(uint32_t & bootReason) override; + CHIP_ERROR StoreBootReason(uint32_t bootReason) override; + CHIP_ERROR GetRegulatoryLocation(uint8_t & location) override; + CHIP_ERROR GetLocationCapability(uint8_t & location) override; + static ConfigurationManagerImpl & GetDefaultInstance(); + +private: + // ===== Members that implement the ConfigurationManager public interface. + + CHIP_ERROR Init() override; + CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) override; + bool CanFactoryReset() override; + void InitiateFactoryReset() override; + CHIP_ERROR ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) override; + CHIP_ERROR WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) override; + + // NOTE: Other public interface methods are implemented by GenericConfigurationManagerImpl<>. + CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + + // ===== Members that implement the GenericConfigurationManagerImpl protected interface. + CHIP_ERROR ReadConfigValue(Key key, bool & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint32_t & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint64_t & val) override; + CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR WriteConfigValue(Key key, bool val) override; + CHIP_ERROR WriteConfigValue(Key key, uint32_t val) override; + CHIP_ERROR WriteConfigValue(Key key, uint64_t val) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen) override; + CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) override; + void RunConfigUnitTest(void) override; + + // ===== Private members reserved for use by this class only. + + static void DoFactoryReset(intptr_t arg); +}; + +/** + * Returns the platform-specific implementation of the ConfigurationManager object. + * + * Applications can use this to gain access to features of the ConfigurationManager + * that are specific to the selected platform. + */ +ConfigurationManager & ConfigurationMgrImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityManagerImpl.cpp b/src/platform/NuttX/ConnectivityManagerImpl.cpp new file mode 100644 index 00000000000000..ea4ca105bbcb45 --- /dev/null +++ b/src/platform/NuttX/ConnectivityManagerImpl.cpp @@ -0,0 +1,1904 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019 Nest Labs, Inc. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#include +#include +#endif + +using namespace ::chip; +using namespace ::chip::TLV; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace ::chip::app::Clusters::GeneralDiagnostics; +using namespace ::chip::app::Clusters::WiFiNetworkDiagnostics; + +using namespace ::chip::DeviceLayer::NetworkCommissioning; + +namespace chip { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +namespace DeviceLayer { + +ConnectivityManagerImpl ConnectivityManagerImpl::sInstance; + +CHIP_ERROR ConnectivityManagerImpl::_Init() +{ +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + mWiFiStationMode = kWiFiStationMode_Disabled; + mWiFiStationReconnectInterval = System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_WIFI_STATION_RECONNECT_INTERVAL); +#endif + mpConnectCallback = nullptr; + mpScanCallback = nullptr; + + if (ConnectivityUtils::GetEthInterfaceName(mEthIfName, IFNAMSIZ) == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Got Ethernet interface: %s", mEthIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get Ethernet interface"); + mEthIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetEthNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset Ethernet statistic counts"); + } + + // Initialize the generic base classes that require it. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_Init(); +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + if (ConnectivityUtils::GetWiFiInterfaceName(sWiFiIfName, IFNAMSIZ) == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Got WiFi interface: %s", sWiFiIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get WiFi interface"); + sWiFiIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetWiFiNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset WiFi statistic counts"); + } +#endif + + return CHIP_NO_ERROR; +} + +void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + // Forward the event to the generic base classes as needed. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_OnPlatformEvent(event); +#endif +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + +ConnectivityManager::WiFiStationMode ConnectivityManagerImpl::_GetWiFiStationMode() +{ + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + std::lock_guard lock(mWpaSupplicantMutex); + mWiFiStationMode = (mWpaSupplicant.iface != nullptr) ? kWiFiStationMode_Enabled : kWiFiStationMode_Disabled; + } + + return mWiFiStationMode; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationMode(ConnectivityManager::WiFiStationMode val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != ConnectivityManager::kWiFiStationMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiStationMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi station mode change: %s -> %s", WiFiStationModeToStr(mWiFiStationMode), + WiFiStationModeToStr(val)); + } + + mWiFiStationMode = val; +exit: + return err; +} + +System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiStationReconnectInterval() +{ + return mWiFiStationReconnectInterval; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationReconnectInterval(System::Clock::Timeout val) +{ + mWiFiStationReconnectInterval = val; + + return CHIP_NO_ERROR; +} + +bool ConnectivityManagerImpl::_IsWiFiStationEnabled() +{ + return GetWiFiStationMode() == kWiFiStationMode_Enabled; +} + +bool ConnectivityManagerImpl::_IsWiFiStationConnected() +{ + bool ret = false; + const gchar * state = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationConnected: interface not connected"); + return false; + } + + state = wpa_fi_w1_wpa_supplicant1_interface_get_state(mWpaSupplicant.iface); + if (g_strcmp0(state, "completed") == 0) + { + mConnectivityFlag.Set(ConnectivityFlags::kHaveIPv4InternetConnectivity) + .Set(ConnectivityFlags::kHaveIPv6InternetConnectivity); + ret = true; + } + + return ret; +} + +bool ConnectivityManagerImpl::_IsWiFiStationApplicationControlled() +{ + return mWiFiStationMode == ConnectivityManager::kWiFiStationMode_ApplicationControlled; +} + +bool ConnectivityManagerImpl::_IsWiFiStationProvisioned() +{ + bool ret = false; + const gchar * bss = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationProvisioned: interface not connected"); + return false; + } + + bss = wpa_fi_w1_wpa_supplicant1_interface_get_current_bss(mWpaSupplicant.iface); + if (g_str_match_string("BSSs", bss, true)) + { + ret = true; + } + + return ret; +} + +void ConnectivityManagerImpl::_ClearWiFiStationProvision() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _ClearWiFiStationProvision: interface not connected"); + return; + } + + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + GAutoPtr err; + wpa_fi_w1_wpa_supplicant1_interface_call_remove_all_networks_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + + if (err != nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to remove all networks with error: %s", + err ? err->message : "unknown error"); + } + } +} + +bool ConnectivityManagerImpl::_CanStartWiFiScan() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED && + mWpaSupplicant.scanState == GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + + return ret; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiAPMode(WiFiAPMode val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != kWiFiAPMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiAPMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi AP mode change: %s -> %s", WiFiAPModeToStr(mWiFiAPMode), WiFiAPModeToStr(val)); + mWiFiAPMode = val; + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + +exit: + return err; +} + +void ConnectivityManagerImpl::_DemandStartWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP"); + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} + +void ConnectivityManagerImpl::_StopOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP"); + mLastAPDemandTime = System::Clock::kZero; + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} + +void ConnectivityManagerImpl::_MaintainOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + if (mWiFiAPState == kWiFiAPState_Active) + { + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + } + } +} + +void ConnectivityManagerImpl::_SetWiFiAPIdleTimeout(System::Clock::Timeout val) +{ + mWiFiAPIdleTimeout = val; + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); +} + +void ConnectivityManagerImpl::UpdateNetworkStatus() +{ + Network configuredNetwork; + + VerifyOrReturn(IsWiFiStationEnabled() && mpStatusChangeCallback != nullptr); + + CHIP_ERROR err = GetConfiguredNetwork(configuredNetwork); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // If we have already connected to the WiFi AP, then return null to indicate a success state. + if (IsWiFiStationConnected()) + { + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kSuccess, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), NullOptional); + return; + } + + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kUnknownError, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), + MakeOptional(GetDisconnectReason())); +} + +void ConnectivityManagerImpl::_OnWpaPropertiesChanged(WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * changedProperties) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (g_variant_n_children(changedProperties) > 0) + { + GAutoPtr iter; + const gchar * key; + GVariant * value; + + WiFiDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetWiFiDiagnosticsDelegate(); + + g_variant_get(changedProperties, "a{sv}", &iter.GetReceiver()); + + while (g_variant_iter_loop(iter.get(), "{&sv}", &key, &value)) + { + GAutoPtr value_str(g_variant_print(value, TRUE)); + ChipLogProgress(DeviceLayer, "wpa_supplicant:PropertiesChanged:key:%s -> %s", StringOrNullMarker(key), + StringOrNullMarker(value_str.get())); + + if (g_strcmp0(key, "State") == 0) + { + if (g_strcmp0(value_str.get(), "\'associating\'") == 0) + { + mAssociationStarted = true; + } + else if (g_strcmp0(value_str.get(), "\'disconnected\'") == 0) + { + gint reason = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnDisconnectionDetected(reason); + delegate->OnConnectionStatusChanged(static_cast(ConnectionStatusEnum::kConnected)); + } + + if (mAssociationStarted) + { + uint8_t associationFailureCause = static_cast(AssociationFailureCauseEnum::kUnknown); + uint16_t status = WLAN_STATUS_UNSPECIFIED_FAILURE; + + switch (abs(reason)) + { + case WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY: + case WLAN_REASON_DISASSOC_AP_BUSY: + case WLAN_REASON_DISASSOC_STA_HAS_LEFT: + case WLAN_REASON_DISASSOC_LOW_ACK: + case WLAN_REASON_BSS_TRANSITION_DISASSOC: + associationFailureCause = static_cast(AssociationFailureCauseEnum::kAssociationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_assoc_status_code(mWpaSupplicant.iface); + break; + case WLAN_REASON_PREV_AUTH_NOT_VALID: + case WLAN_REASON_DEAUTH_LEAVING: + case WLAN_REASON_IEEE_802_1X_AUTH_FAILED: + associationFailureCause = static_cast(AssociationFailureCauseEnum::kAuthenticationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_auth_status_code(mWpaSupplicant.iface); + break; + default: + break; + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, reason]() { + if (mpConnectCallback != nullptr) + { + mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), reason); + mpConnectCallback = nullptr; + } + }); + + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnAssociationFailureDetected(associationFailureCause, status); + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); + + mAssociationStarted = false; + } + else if (g_strcmp0(value_str.get(), "\'associated\'") == 0) + { + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnConnectionStatusChanged(static_cast(ConnectionStatusEnum::kNotConnected)); + } + + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); + } + else if (g_strcmp0(value_str.get(), "\'completed\'") == 0) + { + if (mAssociationStarted) + { + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpConnectCallback != nullptr) + { + mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0); + mpConnectCallback = nullptr; + } + ConnectivityMgrImpl().PostNetworkConnect(); + }); + } + mAssociationStarted = false; + } + } + } + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1Interface * iface = wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus_finish(res, &err.GetReceiver()); + + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } + + if (iface != nullptr && err == nullptr) + { + mWpaSupplicant.iface = iface; + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant interface proxy"); + + g_signal_connect( + mWpaSupplicant.iface, "properties-changed", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * properties, ConnectivityManagerImpl * self) { + return self->_OnWpaPropertiesChanged(proxy, properties); + }), + this); + g_signal_connect(mWpaSupplicant.iface, "scan-done", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceScanDone(proxy, success); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant interface proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } + + // We need to stop auto scan or it will block our network scan. + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + CHIP_ERROR errInner = StopAutoScan(); + if (errInner != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto scan: %s", ErrorStr(errInner)); + } + }); +} + +void ConnectivityManagerImpl::_OnWpaBssProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1BSS * bss = wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_finish(res, &err.GetReceiver()); + + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + if (bss != nullptr && err.get() == nullptr) + { + mWpaSupplicant.bss = bss; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant bss proxy"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant bss proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + gboolean result = wpa_fi_w1_wpa_supplicant1_call_get_interface_finish(mWpaSupplicant.proxy, &mWpaSupplicant.interfacePath, res, + &err.GetReceiver()); + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } + else + { + GAutoPtr error; + GVariant * args = nullptr; + GVariantBuilder builder; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: can't find interface %s: %s", sWiFiIfName, + err ? err->message : "unknown error"); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: try to create interface %s", CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME)); + args = g_variant_builder_end(&builder); + + result = wpa_fi_w1_wpa_supplicant1_call_create_interface_sync(mWpaSupplicant.proxy, args, &mWpaSupplicant.interfacePath, + nullptr, &error.GetReceiver()); + + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + Platform::CopyString(sWiFiIfName, CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create interface %s: %s", + CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, error ? error->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } + } + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceAdded(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath) + { + return; + } + + mWpaSupplicant.interfacePath = const_cast(path); + if (mWpaSupplicant.interfacePath) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface added: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath == nullptr) + { + return; + } + + if (g_strcmp0(mWpaSupplicant.interfacePath, path) == 0) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface removed: %s", StringOrNullMarker(path)); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } + + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } + + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + mWpaSupplicant.scanState = GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + } +} + +void ConnectivityManagerImpl::_OnWpaProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + mWpaSupplicant.proxy = wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus_finish(res, &err.GetReceiver()); + if (mWpaSupplicant.proxy != nullptr && err.get() == nullptr) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_CONNECTED; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant proxy"); + + g_signal_connect( + mWpaSupplicant.proxy, "interface-added", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties, + ConnectivityManagerImpl * self) { return self->_OnWpaInterfaceAdded(proxy, path, properties); }), + this); + g_signal_connect( + mWpaSupplicant.proxy, "interface-removed", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties, + ConnectivityManagerImpl * self) { return self->_OnWpaInterfaceRemoved(proxy, path, properties); }), + this); + + wpa_fi_w1_wpa_supplicant1_call_get_interface( + mWpaSupplicant.proxy, sWiFiIfName, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceReady(sourceObject_, res_); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant proxy %s", + err ? err->message : "unknown error"); + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } +} + +void ConnectivityManagerImpl::StartWiFiManagement() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + mConnectivityFlag.ClearAll(); + mWpaSupplicant = GDBusWpaSupplicant{}; + + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync( + +[](ConnectivityManagerImpl * self) { return self->_StartWiFiManagement(); }, this); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "Failed to start WiFi management")); +} + +bool ConnectivityManagerImpl::IsWiFiManagementStarted() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + + return ret; +} + +void ConnectivityManagerImpl::StartNonConcurrentWiFiManagement() +{ + StartWiFiManagement(); + + for (int cnt = 0; cnt < WIFI_START_CHECK_ATTEMPTS; cnt++) + { + if (IsWiFiManagementStarted()) + { + DeviceControlServer::DeviceControlSvr().PostOperationalNetworkStartedEvent(); + ChipLogProgress(DeviceLayer, "Non-concurrent mode Wi-Fi Management Started."); + return; + } + usleep(WIFI_START_CHECK_TIME_USEC); + } + ChipLogError(Ble, "Non-concurrent mode Wi-Fi Management taking too long to start."); +} + +void ConnectivityManagerImpl::DriveAPState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + WiFiAPState targetState; + + std::lock_guard lock(mWpaSupplicantMutex); + + // If the AP interface is not under application control... + if (mWiFiAPMode != kWiFiAPMode_ApplicationControlled) + { + // Determine the target (desired) state for AP interface... + + // The target state is 'NotActive' if the application has expressly disabled the AP interface. + if (mWiFiAPMode == kWiFiAPMode_Disabled) + { + targetState = kWiFiAPState_NotActive; + } + + // The target state is 'Active' if the application has expressly enabled the AP interface. + else if (mWiFiAPMode == kWiFiAPMode_Enabled) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is 'On demand, when no station is available' + // and the station interface is not provisioned or the application has disabled the station + // interface. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision && + (!IsWiFiStationProvisioned() || GetWiFiStationMode() == kWiFiStationMode_Disabled)) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is one of the 'On demand' modes and there + // has been demand for the AP within the idle timeout period. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp(); + + if (mLastAPDemandTime != System::Clock::kZero && now < (mLastAPDemandTime + mWiFiAPIdleTimeout)) + { + targetState = kWiFiAPState_Active; + + // Compute the amount of idle time before the AP should be deactivated and + // arm a timer to fire at that time. + System::Clock::Timeout apTimeout = (mLastAPDemandTime + mWiFiAPIdleTimeout) - now; + err = DeviceLayer::SystemLayer().StartTimer(apTimeout, DriveAPState, this); + SuccessOrExit(err); + ChipLogProgress(DeviceLayer, "Next WiFi AP timeout in %" PRIu32 " s", + std::chrono::duration_cast(apTimeout).count()); + } + else + { + targetState = kWiFiAPState_NotActive; + } + } + + // Otherwise the target state is 'NotActive'. + else + { + targetState = kWiFiAPState_NotActive; + } + + // If the current AP state does not match the target state... + if (mWiFiAPState != targetState) + { + if (targetState == kWiFiAPState_Active) + { + err = ConfigureWiFiAP(); + SuccessOrExit(err); + + ChangeWiFiAPState(kWiFiAPState_Active); + } + else + { + if (mWpaSupplicant.networkPath) + { + GAutoPtr error(nullptr); + + gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync( + mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr, &error.GetReceiver()); + + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath); + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + ChangeWiFiAPState(kWiFiAPState_NotActive); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s", + error ? error->message : "unknown error"); + err = CHIP_ERROR_INTERNAL; + } + } + } + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + SetWiFiAPMode(kWiFiAPMode_Disabled); + ChipLogError(DeviceLayer, "Drive AP state failed: %s", ErrorStr(err)); + } +} + +CHIP_ERROR ConnectivityManagerImpl::ConfigureWiFiAP() +{ + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + GVariant * args = nullptr; + GVariantBuilder builder; + + uint16_t channel = 1; + uint16_t discriminator = 0; + char ssid[32]; + + std::lock_guard lock(mWpaSupplicantMutex); + + channel = ConnectivityUtils::MapChannelToFrequency(kWiFi_BAND_2_4_GHZ, CHIP_DEVICE_CONFIG_WIFI_AP_CHANNEL); + + if (GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator) != CHIP_NO_ERROR) + discriminator = 0; + + snprintf(ssid, 32, "%s%04u", CHIP_DEVICE_CONFIG_WIFI_AP_SSID_PREFIX, discriminator); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: ConfigureWiFiAP, ssid: %s, channel: %d", ssid, channel); + + // Clean up current network if exists + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssid)); + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("NONE")); + g_variant_builder_add(&builder, "{sv}", "mode", g_variant_new_int32(2)); + g_variant_builder_add(&builder, "{sv}", "frequency", g_variant_new_int32(channel)); + args = g_variant_builder_end(&builder); + + gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync( + mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath, nullptr, &err.GetReceiver()); + + if (result) + { + GAutoPtr error; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: SSID: %s: %s", ssid, mWpaSupplicant.networkPath); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath, + nullptr, &error.GetReceiver()); + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: succeeded to start softAP: SSID: %s", ssid); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start softAP: SSID: %s: %s", ssid, + error ? error->message : "unknown error"); + + ret = CHIP_ERROR_INTERNAL; + } + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s: %s", ssid, err ? err->message : "unknown error"); + + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + ret = CHIP_ERROR_INTERNAL; + } + + return ret; +} + +void ConnectivityManagerImpl::ChangeWiFiAPState(WiFiAPState newState) +{ + if (mWiFiAPState != newState) + { + ChipLogProgress(DeviceLayer, "WiFi AP state change: %s -> %s", WiFiAPStateToStr(mWiFiAPState), WiFiAPStateToStr(newState)); + mWiFiAPState = newState; + } +} + +void ConnectivityManagerImpl::DriveAPState(::chip::System::Layer * aLayer, void * aAppState) +{ + reinterpret_cast(aAppState)->DriveAPState(); +} + +CHIP_ERROR +ConnectivityManagerImpl::_ConnectWiFiNetworkAsync(GVariant * args, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * apCallback) +{ + GAutoPtr argsDeleter(g_variant_ref_sink(args)); // args may be floating, ensure we don't leak it + + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + gboolean result; + + const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface); + + // wpa_supplicant DBus API: if network path of current network is not "/", means we have already selected some network. + if (strcmp(networkPath, "/") != 0) + { + GAutoPtr error; + + result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync(mWpaSupplicant.iface, networkPath, nullptr, + &error.GetReceiver()); + + if (result) + { + if (mWpaSupplicant.networkPath != nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath); + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s", + error ? error->message : "unknown error"); + ret = CHIP_ERROR_INTERNAL; + } + + SuccessOrExit(ret); + } + + result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync(mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath, + nullptr, &err.GetReceiver()); + + if (result) + { + // Note: wpa_supplicant will return immediately if the network is already connected, but it will still try reconnect in the + // background. The client still need to wait for a few seconds for this reconnect operation. So we always disconnect from + // the network we are connected and ignore any errors. + wpa_fi_w1_wpa_supplicant1_interface_call_disconnect_sync(mWpaSupplicant.iface, nullptr, nullptr); + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: %s", mWpaSupplicant.networkPath); + + wpa_fi_w1_wpa_supplicant1_interface_call_select_network( + mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_ConnectWiFiNetworkAsyncCallback(sourceObject_, res_); + }), + this); + + mpConnectCallback = apCallback; + } + else + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to add network: %s", err ? err->message : "unknown error"); + + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + ret = CHIP_ERROR_INTERNAL; + } + +exit: + return ret; +} + +CHIP_ERROR +ConnectivityManagerImpl::ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) +{ + char ssidStr[kMaxWiFiSSIDLength + 1] = { 0 }; + char keyStr[kMaxWiFiKeyLength + 1] = { 0 }; + + VerifyOrReturnError(ssid.size() <= kMaxWiFiSSIDLength, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(credentials.size() <= kMaxWiFiKeyLength, CHIP_ERROR_INVALID_ARGUMENT); + + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // There is another ongoing connect request, reject the new one. + VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + memcpy(ssidStr, ssid.data(), ssid.size()); + memcpy(keyStr, credentials.data(), credentials.size()); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr)); + g_variant_builder_add(&builder, "{sv}", "psk", g_variant_new_string(keyStr)); + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("SAE WPA-PSK")); + GVariant * args = g_variant_builder_end(&builder); + return _ConnectWiFiNetworkAsync(args, connectCallback); +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +static CHIP_ERROR AddOrReplaceBlob(WpaFiW1Wpa_supplicant1Interface * iface, const char * nameOrRef, ByteSpan data) +{ + // Strip the blob:// prefix off the name (if present), so we don't need as many string constants. + constexpr auto refPrefix = "blob://"_span; + const char * name = (strncmp(nameOrRef, refPrefix.data(), refPrefix.size()) == 0) ? nameOrRef + refPrefix.size() : nameOrRef; + + GAutoPtr err; + if (!wpa_fi_w1_wpa_supplicant1_interface_call_remove_blob_sync(iface, name, nullptr, &err.GetReceiver())) + { + GAutoPtr remoteError(g_dbus_error_get_remote_error(err.get())); + if (!(remoteError && strcmp(remoteError.get(), kWpaSupplicantBlobUnknown) == 0)) + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to remove blob: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + err.reset(); + } + if (!wpa_fi_w1_wpa_supplicant1_interface_call_add_blob_sync( + iface, name, g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, data.data(), data.size(), 1), nullptr, &err.GetReceiver())) + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to add blob: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; +} + +// Note: Static blob names assume we're only supporting a single network configuration. +static constexpr char kNetworkIdentityBlobRef[] = "blob://pdc-ni"; +static constexpr char kClientIdentityBlobRef[] = "blob://pdc-ci"; +static constexpr char kClientIdentityKeyBlobRef[] = "blob://pdc-cik"; + +CHIP_ERROR ConnectivityManagerImpl::ConnectWiFiNetworkWithPDCAsync( + ByteSpan ssid, ByteSpan networkIdentity, ByteSpan clientIdentity, const Crypto::P256Keypair & clientIdentityKeypair, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) +{ + VerifyOrReturnError(ssid.size() <= kMaxWiFiSSIDLength, CHIP_ERROR_INVALID_ARGUMENT); + + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // There is another ongoing connect request, reject the new one. + VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + + // Convert identities and our key pair to DER and add them to wpa_supplicant as blobs + { + constexpr size_t bufferSize = std::max(kMaxDERCertLength, kP256ECPrivateKeyDERLength); + Platform::ScopedMemoryBuffer buffer; + VerifyOrReturnError(buffer.Alloc(bufferSize), CHIP_ERROR_NO_MEMORY); + + MutableByteSpan networkIdentityDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(ConvertChipCertToX509Cert(networkIdentity, networkIdentityDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kNetworkIdentityBlobRef, networkIdentityDER)); + + MutableByteSpan clientIdentityDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(ConvertChipCertToX509Cert(clientIdentity, clientIdentityDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kClientIdentityBlobRef, clientIdentityDER)); + + Crypto::P256SerializedKeypair serializedKeypair; + MutableByteSpan clientIdentityKeypairDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(clientIdentityKeypair.Serialize(serializedKeypair)); + ReturnErrorOnFailure(ConvertECDSAKeypairRawToDER(serializedKeypair, clientIdentityKeypairDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kClientIdentityKeyBlobRef, clientIdentityKeypairDER)); + } + + // Build the network configuration + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + { + char ssidStr[kMaxWiFiSSIDLength + 1] = { 0 }; + memcpy(ssidStr, ssid.data(), ssid.size()); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr)); + } + + { + CertificateKeyIdStorage keyId; + ReturnErrorOnFailure(ExtractIdentifierFromChipNetworkIdentity(networkIdentity, keyId)); + + static constexpr char kNAIDomain[] = ".pdc.csa-iot.org"; + static constexpr auto keyIdHexSize = keyId.size() * 2; + char identityStr[1 + keyIdHexSize + sizeof(kNAIDomain)]; // sizeof(kNAIDomain) includes null terminator + + identityStr[0] = '@'; + ReturnErrorOnFailure(Encoding::BytesToUppercaseHexBuffer(keyId.data(), keyId.size(), &identityStr[1], keyIdHexSize)); + strcpy(&identityStr[1 + keyIdHexSize], kNAIDomain); + g_variant_builder_add(&builder, "{sv}", "identity", g_variant_new_string(identityStr)); + } + + // The configuration will become simpler once we add explicit Matter support to wpa_supplicant + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("WPA-EAP-SHA256")); + g_variant_builder_add(&builder, "{sv}", "fallback_key_mgmt", g_variant_new_string("WPA-EAP-SHA256")); + g_variant_builder_add(&builder, "{sv}", "pairwise", g_variant_new_string("CCMP")); + g_variant_builder_add(&builder, "{sv}", "group", g_variant_new_string("CCMP")); + g_variant_builder_add(&builder, "{sv}", "ieee80211w", g_variant_new_int32(2)); + g_variant_builder_add(&builder, "{sv}", "eap", g_variant_new_string("TLS")); + g_variant_builder_add(&builder, "{sv}", "eap_workaround", g_variant_new_int32(0)); + + g_variant_builder_add( + &builder, "{sv}", "phase1", + g_variant_new_string("tls_disable_tlsv1_0=1,tls_disable_tlsv1_1=1,tls_disable_tlsv1_2=1,tls_disable_tlsv1_3=0")); + g_variant_builder_add(&builder, "{sv}", "openssl_ciphers", g_variant_new_string("TLS_AES_128_CCM_SHA256")); + g_variant_builder_add(&builder, "{sv}", "openssl_ecdh_curves", g_variant_new_string("P-256")); + + g_variant_builder_add(&builder, "{sv}", "ca_cert", g_variant_new_string(kNetworkIdentityBlobRef)); + g_variant_builder_add(&builder, "{sv}", "client_cert", g_variant_new_string(kClientIdentityBlobRef)); + g_variant_builder_add(&builder, "{sv}", "private_key", g_variant_new_string(kClientIdentityKeyBlobRef)); + GVariant * args = g_variant_builder_end(&builder); + return _ConnectWiFiNetworkAsync(args, connectCallback); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * sourceObject, GAsyncResult * res) +{ + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + { + gboolean result = + wpa_fi_w1_wpa_supplicant1_interface_call_select_network_finish(mWpaSupplicant.iface, res, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform connect network: %s", err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpConnectCallback != nullptr) + { + // TODO(#14175): Replace this with actual thread attach result. + mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0); + mpConnectCallback = nullptr; + } + mpConnectCallback = nullptr; + }); + + return; + } + + result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error"); + } + } +} + +void ConnectivityManagerImpl::PostNetworkConnect() +{ + // Iterate on the network interface to see if we already have beed assigned addresses. + // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session. + // This should be removed or find a better place once we depercate the rendezvous session. + for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next()) + { + char ifName[chip::Inet::InterfaceId::kMaxIfNameLength]; + if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) && + strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0) + { + chip::Inet::IPAddress addr; + if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4()) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kInternetConnectivityChange; + event.InternetConnectivityChange.IPv4 = kConnectivity_Established; + event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange; + event.InternetConnectivityChange.ipAddress = addr; + + char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; + addr.ToString(ipStrBuf); + + ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName, ipStrBuf); + + PlatformMgr().PostEventOrDie(&event); + } + } + } + +#if defined(CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD) + // CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD can be defined to a command pattern + // to run once the network has been connected, with a %s placeholder for the + // interface name. E.g. "dhclient -nw %s" + // Run dhclient for IP on WiFi. + // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this. + char cmdBuffer[128]; + sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName); + int dhclientSystemRet = system(cmdBuffer); + if (dhclientSystemRet != 0) + { + ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet); + } + else + { + ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName); + } +#endif // defined(CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD) +} + +CHIP_ERROR ConnectivityManagerImpl::CommitConfig() +{ + gboolean result; + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogError(DeviceLayer, "wpa_supplicant: CommitConfig: interface proxy not connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config"); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + + if (!result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!"); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiBssId(MutableByteSpan & value) +{ + constexpr size_t bssIdSize = 6; + static_assert(kMaxHardwareAddrSize >= bssIdSize, "We are assuming we can fit a BSSID in a buffer of size kMaxHardwareAddrSize"); + VerifyOrReturnError(value.size() >= bssIdSize, CHIP_ERROR_BUFFER_TOO_SMALL); + + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + // On Linux simulation, we don't have the DBus API to get the BSSID of connected AP. Use mac address + // of local WiFi network card instead. + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, value.data(), kMaxHardwareAddrSize) != + CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get WiFi network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + value.reduce_size(bssIdSize); + err = CHIP_NO_ERROR; + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiSecurityType(SecurityTypeEnum & securityType) +{ + const gchar * mode = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogError(DeviceLayer, "wpa_supplicant: GetWiFiSecurityType: interface proxy not connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + mode = wpa_fi_w1_wpa_supplicant1_interface_get_current_auth_mode(mWpaSupplicant.iface); + ChipLogProgress(DeviceLayer, "wpa_supplicant: current Wi-Fi security type: %s", StringOrNullMarker(mode)); + + if (strncmp(mode, "WPA-PSK", 7) == 0) + { + securityType = SecurityTypeEnum::kWpa; + } + else if (strncmp(mode, "WPA2-PSK", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa2; + } + else if (strncmp(mode, "WPA2-EAP", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa2; + } + else if (strncmp(mode, "WPA3-PSK", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa3; + } + else if (strncmp(mode, "WEP", 3) == 0) + { + securityType = SecurityTypeEnum::kWep; + } + else if (strncmp(mode, "NONE", 4) == 0) + { + securityType = SecurityTypeEnum::kNone; + } + else if (strncmp(mode, "WPA-NONE", 8) == 0) + { + securityType = SecurityTypeEnum::kNone; + } + else + { + securityType = SecurityTypeEnum::kUnspecified; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiVersion(WiFiVersionEnum & wiFiVersion) +{ + // We don't have direct API to get the WiFi version yet, return 802.11n on Linux simulation. + wiFiVersion = WiFiVersionEnum::kN; + + return CHIP_NO_ERROR; +} + +int32_t ConnectivityManagerImpl::GetDisconnectReason() +{ + std::lock_guard lock(mWpaSupplicantMutex); + GAutoPtr err; + + gint errorValue = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + // wpa_supplicant DBus API: DisconnectReason: The most recent IEEE 802.11 reason code for disconnect. Negative value + // indicates locally generated disconnection. + return errorValue; +} + +CHIP_ERROR ConnectivityManagerImpl::GetConfiguredNetwork(NetworkCommissioning::Network & network) +{ + // This function can be called without g_main_context_get_thread_default() being set. + // The network proxy object is created in a synchronous manner, so the D-Bus call will + // be completed before this function returns. Also, no external callbacks are registered + // with the proxy object. + + std::lock_guard lock(mWpaSupplicantMutex); + GAutoPtr err; + + if (mWpaSupplicant.iface == nullptr) + { + ChipLogDetail(DeviceLayer, "Wifi network not currently connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface); + + // wpa_supplicant DBus API: if network path of current network is "/", means no networks is currently selected. + if (strcmp(networkPath, "/") == 0) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr networkInfo(wpa_fi_w1_wpa_supplicant1_network_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, networkPath, nullptr, &err.GetReceiver())); + if (networkInfo == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + network.connected = wpa_fi_w1_wpa_supplicant1_network_get_enabled(networkInfo.get()); + GVariant * properties = wpa_fi_w1_wpa_supplicant1_network_get_properties(networkInfo.get()); + GAutoPtr ssid(g_variant_lookup_value(properties, "ssid", nullptr)); + gsize length; + const gchar * ssidStr = g_variant_get_string(ssid.get(), &length); + // TODO: wpa_supplicant will return ssid with quotes! We should have a better way to get the actual ssid in bytes. + gsize length_actual = length - 2; + VerifyOrReturnError(length_actual <= sizeof(network.networkID), CHIP_ERROR_INTERNAL); + ChipLogDetail(DeviceLayer, "Current connected network: %s", StringOrNullMarker(ssidStr)); + memcpy(network.networkID, ssidStr + 1, length_actual); + network.networkIDLen = length_actual; + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::StopAutoScan() +{ + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + GAutoPtr err; + gboolean result; + + ChipLogDetail(DeviceLayer, "wpa_supplicant: disabling auto scan"); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_auto_scan_sync( + mWpaSupplicant.iface, "" /* empty string means disabling auto scan */, nullptr, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto network scan: %s", err ? err->message : "unknown"); + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::StartWiFiScan(ByteSpan ssid, WiFiDriver::ScanCallback * callback) +{ + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + // There is another ongoing scan request, reject the new one. + VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(ssid.size() <= sizeof(sInterestedSSID), CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + GVariant * args = nullptr; + GVariantBuilder builder; + gboolean result; + + memcpy(sInterestedSSID, ssid.data(), ssid.size()); + sInterestedSSIDLen = ssid.size(); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active")); + args = g_variant_builder_end(&builder); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_scan_sync(mWpaSupplicant.iface, args, nullptr, &err.GetReceiver()); + + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: initialized network scan."); + mpScanCallback = callback; + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start network scan: %s", err ? err->message : "unknown error"); + ret = CHIP_ERROR_INTERNAL; + } + + return ret; +} + +namespace { + +// wpa_supplicant's scan results don't contains the channel infomation, so we need this lookup table for resolving the band and +// channel infomation. +std::pair GetBandAndChannelFromFrequency(uint32_t freq) +{ + std::pair ret = std::make_pair(WiFiBand::k2g4, 0); + if (freq <= 931) + { + ret.first = WiFiBand::k1g; + if (freq >= 916) + { + ret.second = ((freq - 916) * 2) - 1; + } + else if (freq >= 902) + { + ret.second = (freq - 902) * 2; + } + else if (freq >= 863) + { + ret.second = (freq - 863) * 2; + } + else + { + ret.second = 1; + } + } + else if (freq <= 2472) + { + ret.second = static_cast((freq - 2412) / 5 + 1); + } + else if (freq == 2484) + { + ret.second = 14; + } + else if (freq >= 3600 && freq <= 3700) + { + // Note: There are not many devices supports this band, and this band contains rational frequency in MHz, need to figure out + // the behavior of wpa_supplicant in this case. + ret.first = WiFiBand::k3g65; + } + else if (freq >= 5035 && freq <= 5945) + { + ret.first = WiFiBand::k5g; + ret.second = static_cast((freq - 5000) / 5); + } + else if (freq == 5960 || freq == 5980) + { + ret.first = WiFiBand::k5g; + ret.second = static_cast((freq - 5000) / 5); + } + else if (freq >= 5955) + { + ret.first = WiFiBand::k6g; + ret.second = static_cast((freq - 5950) / 5); + } + else if (freq >= 58000) + { + ret.first = WiFiBand::k60g; + // Note: Some channel has the same center frequency but different bandwidth. Should figure out wpa_supplicant's behavior in + // this case. Also, wpa_supplicant's frequency property is uint16 infact. + switch (freq) + { + case 58'320: + ret.second = 1; + break; + case 60'480: + ret.second = 2; + break; + case 62'640: + ret.second = 3; + break; + case 64'800: + ret.second = 4; + break; + case 66'960: + ret.second = 5; + break; + case 69'120: + ret.second = 6; + break; + case 59'400: + ret.second = 9; + break; + case 61'560: + ret.second = 10; + break; + case 63'720: + ret.second = 11; + break; + case 65'880: + ret.second = 12; + break; + case 68'040: + ret.second = 13; + break; + } + } + return ret; +} + +} // namespace + +bool ConnectivityManagerImpl::_GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result) +{ + // This function can be called without g_main_context_get_thread_default() being set. + // The BSS proxy object is created in a synchronous manner, so the D-Bus call will be + // completed before this function returns. Also, no external callbacks are registered + // with the proxy object. + + GAutoPtr err; + GAutoPtr bss(wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, bssPath, nullptr, &err.GetReceiver())); + + if (bss == nullptr) + { + return false; + } + + WpaFiW1Wpa_supplicant1BSSProxy * bssProxy = WPA_FI_W1_WPA_SUPPLICANT1_BSS_PROXY(bss.get()); + + GAutoPtr ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID")); + GAutoPtr bssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "BSSID")); + + // Network scan is performed in the background, so the BSS + // may be gone when we try to get the properties. + if (ssid == nullptr || bssid == nullptr) + { + ChipLogDetail(DeviceLayer, "wpa_supplicant: BSS not found: %s", StringOrNullMarker(bssPath)); + return false; + } + + const guchar * ssidStr = nullptr; + const guchar * bssidBuf = nullptr; + char bssidStr[2 * 6 + 5 + 1] = { 0 }; + gsize ssidLen = 0; + gsize bssidLen = 0; + gint16 signal = wpa_fi_w1_wpa_supplicant1_bss_get_signal(bss.get()); + guint16 frequency = wpa_fi_w1_wpa_supplicant1_bss_get_frequency(bss.get()); + + ssidStr = reinterpret_cast(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(guchar))); + bssidBuf = reinterpret_cast(g_variant_get_fixed_array(bssid.get(), &bssidLen, sizeof(guchar))); + + if (bssidLen == 6) + { + snprintf(bssidStr, sizeof(bssidStr), "%02x:%02x:%02x:%02x:%02x:%02x", bssidBuf[0], bssidBuf[1], bssidBuf[2], bssidBuf[3], + bssidBuf[4], bssidBuf[5]); + } + else + { + bssidLen = 0; + ChipLogError(DeviceLayer, "Got a network with bssid not equals to 6"); + } + ChipLogDetail(DeviceLayer, "Network Found: %.*s (%s) Signal:%d", int(ssidLen), StringOrNullMarker((const gchar *) ssidStr), + bssidStr, signal); + + // A flag for enterprise encryption option to avoid returning open for these networks by mistake + // TODO: The following code will mistakenly recognize WEP encryption as OPEN network, this should be fixed by reading + // IEs (information elements) field instead of reading cooked data. + + static constexpr uint8_t kEAP = (1 << 7); + + auto IsNetworkWPAPSK = [](GVariant * wpa) -> uint8_t { + if (wpa == nullptr) + { + return 0; + } + + GAutoPtr keyMgmt(g_variant_lookup_value(wpa, "KeyMgmt", nullptr)); + if (keyMgmt == nullptr) + { + return 0; + } + GAutoPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr)); + const gchar ** keyMgmtsHendle = keyMgmts.get(); + uint8_t res = 0; + + VerifyOrReturnError(keyMgmtsHendle != nullptr, res); + + for (auto keyMgmtVal = *keyMgmtsHendle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHendle)) + { + if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-none") == 0) + { + res |= (1 << 2); // SecurityType::WPA_PERSONAL + } + else if (g_strcasecmp(keyMgmtVal, "wpa-eap")) + { + res |= (kEAP); + } + } + + return res; + }; + auto IsNetworkWPA2PSK = [](GVariant * rsn) -> uint8_t { + if (rsn == nullptr) + { + return 0; + } + GAutoPtr keyMgmt(g_variant_lookup_value(rsn, "KeyMgmt", nullptr)); + if (keyMgmt == nullptr) + { + return 0; + } + GAutoPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr)); + const gchar ** keyMgmtsHendle = keyMgmts.get(); + uint8_t res = 0; + + VerifyOrReturnError(keyMgmtsHendle != nullptr, res); + + for (auto keyMgmtVal = *keyMgmtsHendle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHendle)) + { + if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-psk-sha256") == 0 || + g_strcasecmp(keyMgmtVal, "wpa-ft-psk") == 0) + { + res |= (1 << 3); // SecurityType::WPA2_PERSONAL + } + else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0 || g_strcasecmp(keyMgmtVal, "wpa-eap-sha256") == 0 || + g_strcasecmp(keyMgmtVal, "wpa-ft-eap") == 0) + { + res |= kEAP; + } + else if (g_strcasecmp(keyMgmtVal, "sae") == 0) + { + // wpa_supplicant will include "sae" in KeyMgmt field for WPA3 WiFi, this is not included in the wpa_supplicant + // document. + res |= (1 << 4); // SecurityType::WPA3_PERSONAL + } + } + + return res; + }; + auto GetNetworkSecurityType = [IsNetworkWPAPSK, IsNetworkWPA2PSK](WpaFiW1Wpa_supplicant1BSSProxy * proxy) -> uint8_t { + GAutoPtr wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA")); + GAutoPtr rsn(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "RSN")); + + uint8_t res = IsNetworkWPAPSK(wpa.get()) | IsNetworkWPA2PSK(rsn.get()); + if (res == 0) + { + res = 1; // Open + } + return res & (0x7F); + }; + + // Drop the network if its SSID or BSSID is illegal. + VerifyOrReturnError(ssidLen <= kMaxWiFiSSIDLength, false); + VerifyOrReturnError(bssidLen == kWiFiBSSIDLength, false); + memcpy(result.ssid, ssidStr, ssidLen); + memcpy(result.bssid, bssidBuf, bssidLen); + result.ssidLen = ssidLen; + if (signal < INT8_MIN) + { + result.rssi = INT8_MIN; + } + else if (signal > INT8_MAX) + { + result.rssi = INT8_MAX; + } + else + { + result.rssi = static_cast(signal); + } + + auto bandInfo = GetBandAndChannelFromFrequency(frequency); + result.wiFiBand = bandInfo.first; + result.channel = bandInfo.second; + result.security.SetRaw(GetNetworkSecurityType(bssProxy)); + + return true; +} + +void ConnectivityManagerImpl::_OnWpaInterfaceScanDone(WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: network scan done"); + gchar ** bsss = wpa_fi_w1_wpa_supplicant1_interface_dup_bsss(mWpaSupplicant.iface); + gchar ** oldBsss = bsss; + if (bsss == nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: no network found"); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpScanCallback != nullptr) + { + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), nullptr); + mpScanCallback = nullptr; + } + }); + return; + } + + std::vector * networkScanned = new std::vector(); + for (const gchar * bssPath = (bsss != nullptr ? *bsss : nullptr); bssPath != nullptr; bssPath = *(++bsss)) + { + WiFiScanResponse network; + if (_GetBssInfo(bssPath, network)) + { + if (sInterestedSSIDLen == 0) + { + networkScanned->push_back(network); + } + else if (network.ssidLen == sInterestedSSIDLen && memcmp(network.ssid, sInterestedSSID, sInterestedSSIDLen) == 0) + { + networkScanned->push_back(network); + } + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, networkScanned]() { + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of + // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this + // lambda. + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter(const_cast *>(networkScanned)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + + delete const_cast *>(networkScanned); + }); + + g_strfreev(oldBsss); +} + +CHIP_ERROR ConnectivityManagerImpl::_StartWiFiManagement() +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: Start WiFi management"); + wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, kWpaSupplicantObjectPath, nullptr, + reinterpret_cast(+[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaProxyReady(sourceObject_, res_); + }), + this); + + return CHIP_NO_ERROR; +} + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityManagerImpl.h b/src/platform/NuttX/ConnectivityManagerImpl.h new file mode 100644 index 00000000000000..0d96adcac6e6d8 --- /dev/null +++ b/src/platform/NuttX/ConnectivityManagerImpl.h @@ -0,0 +1,320 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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 +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#else +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#else +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#else +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#include +#include +#include +#include + +#include +#endif + +#include +#include +#include + +namespace chip { +namespace Inet { +class IPAddress; +} // namespace Inet +} // namespace chip + +namespace chip { +namespace DeviceLayer { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +struct GDBusWpaSupplicant +{ + enum WpaState + { + INIT, + WPA_CONNECTING, + WPA_CONNECTED, + WPA_NOT_CONNECTED, + WPA_NO_INTERFACE_PATH, + WPA_GOT_INTERFACE_PATH, + WPA_INTERFACE_CONNECTED, + }; + + enum WpaScanning + { + WIFI_SCANNING_IDLE, + WIFI_SCANNING, + }; + + WpaState state = INIT; + WpaScanning scanState = WIFI_SCANNING_IDLE; + WpaFiW1Wpa_supplicant1 * proxy = nullptr; + WpaFiW1Wpa_supplicant1Interface * iface = nullptr; + WpaFiW1Wpa_supplicant1BSS * bss = nullptr; + gchar * interfacePath = nullptr; + gchar * networkPath = nullptr; +}; +#endif + +/** + * Concrete implementation of the ConnectivityManager singleton object for Linux platforms. + */ +class ConnectivityManagerImpl final : public ConnectivityManager, +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + public Internal::GenericConnectivityManagerImpl_BLE, +#else + public Internal::GenericConnectivityManagerImpl_NoBLE, +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + public Internal::GenericConnectivityManagerImpl_Thread, +#else + public Internal::GenericConnectivityManagerImpl_NoThread, +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + public Internal::GenericConnectivityManagerImpl_WiFi, +#else + public Internal::GenericConnectivityManagerImpl_NoWiFi, +#endif + public Internal::GenericConnectivityManagerImpl_UDP, +#if INET_CONFIG_ENABLE_TCP_ENDPOINT + public Internal::GenericConnectivityManagerImpl_TCP, +#endif + public Internal::GenericConnectivityManagerImpl +{ + // Allow the ConnectivityManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class ConnectivityManager; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +public: + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } + + CHIP_ERROR ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + CHIP_ERROR ConnectWiFiNetworkWithPDCAsync(ByteSpan ssid, ByteSpan networkIdentity, ByteSpan clientIdentity, + const Crypto::P256Keypair & clientIdentityKeypair, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback); +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + + void PostNetworkConnect(); + CHIP_ERROR CommitConfig(); + + void StartWiFiManagement(); + bool IsWiFiManagementStarted(); + void StartNonConcurrentWiFiManagement(); + int32_t GetDisconnectReason(); + CHIP_ERROR GetWiFiBssId(MutableByteSpan & value); + CHIP_ERROR GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType); + CHIP_ERROR GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion); + CHIP_ERROR GetConfiguredNetwork(NetworkCommissioning::Network & network); + CHIP_ERROR StartWiFiScan(ByteSpan ssid, NetworkCommissioning::WiFiDriver::ScanCallback * callback); + +private: + CHIP_ERROR _ConnectWiFiNetworkAsync(GVariant * networkArgs, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) + CHIP_REQUIRES(mWpaSupplicantMutex); + void _ConnectWiFiNetworkAsyncCallback(GObject * sourceObject, GAsyncResult * res); +#endif + +public: + const char * GetEthernetIfName() { return (mEthIfName[0] == '\0') ? nullptr : mEthIfName; } + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + const char * GetWiFiIfName() { return (sWiFiIfName[0] == '\0') ? nullptr : sWiFiIfName; } +#endif + +private: + // ===== Members that implement the ConnectivityManager abstract interface. + + struct WiFiNetworkScanned + { + // The fields matches WiFiInterfaceScanResult::Type. + uint8_t ssid[Internal::kMaxWiFiSSIDLength]; + uint8_t ssidLen; + uint8_t bssid[6]; + int8_t rssi; + uint16_t frequencyBand; + uint8_t channel; + uint8_t security; + }; + + CHIP_ERROR _Init(); + void _OnPlatformEvent(const ChipDeviceEvent * event); + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + WiFiStationMode _GetWiFiStationMode(); + CHIP_ERROR _SetWiFiStationMode(ConnectivityManager::WiFiStationMode val); + System::Clock::Timeout _GetWiFiStationReconnectInterval(); + CHIP_ERROR _SetWiFiStationReconnectInterval(System::Clock::Timeout val); + bool _IsWiFiStationEnabled(); + bool _IsWiFiStationConnected(); + bool _IsWiFiStationApplicationControlled(); + bool _IsWiFiStationProvisioned(); + void _ClearWiFiStationProvision(); + bool _CanStartWiFiScan(); + + WiFiAPMode _GetWiFiAPMode(); + CHIP_ERROR _SetWiFiAPMode(WiFiAPMode val); + bool _IsWiFiAPActive(); + bool _IsWiFiAPApplicationControlled(); + void _DemandStartWiFiAP(); + void _StopOnDemandWiFiAP(); + void _MaintainOnDemandWiFiAP(); + System::Clock::Timeout _GetWiFiAPIdleTimeout(); + void _SetWiFiAPIdleTimeout(System::Clock::Timeout val); + void UpdateNetworkStatus(); + CHIP_ERROR StopAutoScan(); + + void _OnWpaProxyReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties); + void _OnWpaInterfaceAdded(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties); + void _OnWpaPropertiesChanged(WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * properties); + void _OnWpaInterfaceScanDone(WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success); + void _OnWpaInterfaceReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaInterfaceProxyReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaBssProxyReady(GObject * sourceObject, GAsyncResult * res); + + bool _GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result); + + CHIP_ERROR _StartWiFiManagement(); + + bool mAssociationStarted = false; + BitFlags mConnectivityFlag; + GDBusWpaSupplicant mWpaSupplicant CHIP_GUARDED_BY(mWpaSupplicantMutex); + // Access to mWpaSupplicant has to be protected by a mutex because it is accessed from + // the CHIP event loop thread and dedicated D-Bus thread started by platform manager. + std::mutex mWpaSupplicantMutex; + + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; +#endif + + // ==================== ConnectivityManager Private Methods ==================== + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + void DriveAPState(); + CHIP_ERROR ConfigureWiFiAP(); + void ChangeWiFiAPState(WiFiAPState newState); + static void DriveAPState(::chip::System::Layer * aLayer, void * aAppState); +#endif + + // ===== Members for internal use by the following friends. + + friend ConnectivityManager & ConnectivityMgr(); + friend ConnectivityManagerImpl & ConnectivityMgrImpl(); + + static ConnectivityManagerImpl sInstance; + + // ===== Private members reserved for use by this class only. + + char mEthIfName[IFNAMSIZ]; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + ConnectivityManager::WiFiStationMode mWiFiStationMode; + ConnectivityManager::WiFiAPMode mWiFiAPMode; + WiFiAPState mWiFiAPState; + System::Clock::Timestamp mLastAPDemandTime; + System::Clock::Timeout mWiFiStationReconnectInterval; + System::Clock::Timeout mWiFiAPIdleTimeout; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + char sWiFiIfName[IFNAMSIZ]; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + uint8_t sInterestedSSID[Internal::kMaxWiFiSSIDLength]; + uint8_t sInterestedSSIDLen; +#endif + NetworkCommissioning::WiFiDriver::ScanCallback * mpScanCallback; + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback; +}; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +inline ConnectivityManager::WiFiAPMode ConnectivityManagerImpl::_GetWiFiAPMode() +{ + return mWiFiAPMode; +} + +inline bool ConnectivityManagerImpl::_IsWiFiAPActive() +{ + return mWiFiAPState == kWiFiAPState_Active; +} + +inline bool ConnectivityManagerImpl::_IsWiFiAPApplicationControlled() +{ + return mWiFiAPMode == kWiFiAPMode_ApplicationControlled; +} + +inline System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiAPIdleTimeout() +{ + return mWiFiAPIdleTimeout; +} + +#endif + +/** + * Returns the public interface of the ConnectivityManager singleton object. + * + * chip applications should use this to access features of the ConnectivityManager object + * that are common to all platforms. + */ +inline ConnectivityManager & ConnectivityMgr() +{ + return ConnectivityManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the ConnectivityManager singleton object. + * + * chip applications can use this to gain access to features of the ConnectivityManager + * that are specific to the ESP32 platform. + */ +inline ConnectivityManagerImpl & ConnectivityMgrImpl() +{ + return ConnectivityManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityUtils.cpp b/src/platform/NuttX/ConnectivityUtils.cpp new file mode 100644 index 00000000000000..47f6253b2b1b0e --- /dev/null +++ b/src/platform/NuttX/ConnectivityUtils.cpp @@ -0,0 +1,734 @@ +/* + * + * Copyright (c) 2021-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. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on Linux platforms. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +uint16_t ConnectivityUtils::Map2400MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inChannel >= 1 && inChannel <= 13) + { + frequency = static_cast(2412 + ((inChannel - 1) * 5)); + } + else if (inChannel == 14) + { + frequency = 2484; + } + + return frequency; +} + +uint16_t ConnectivityUtils::Map5000MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + switch (inChannel) + { + case 183: + frequency = 4915; + break; + case 184: + frequency = 4920; + break; + case 185: + frequency = 4925; + break; + case 187: + frequency = 4935; + break; + case 188: + frequency = 4940; + break; + case 189: + frequency = 4945; + break; + case 192: + frequency = 4960; + break; + case 196: + frequency = 4980; + break; + case 7: + frequency = 5035; + break; + case 8: + frequency = 5040; + break; + case 9: + frequency = 5045; + break; + case 11: + frequency = 5055; + break; + case 12: + frequency = 5060; + break; + case 16: + frequency = 5080; + break; + case 34: + frequency = 5170; + break; + case 36: + frequency = 5180; + break; + case 38: + frequency = 5190; + break; + case 40: + frequency = 5200; + break; + case 42: + frequency = 5210; + break; + case 44: + frequency = 5220; + break; + case 46: + frequency = 5230; + break; + case 48: + frequency = 5240; + break; + case 52: + frequency = 5260; + break; + case 56: + frequency = 5280; + break; + case 60: + frequency = 5300; + break; + case 64: + frequency = 5320; + break; + case 100: + frequency = 5500; + break; + case 104: + frequency = 5520; + break; + case 108: + frequency = 5540; + break; + case 112: + frequency = 5560; + break; + case 116: + frequency = 5580; + break; + case 120: + frequency = 5600; + break; + case 124: + frequency = 5620; + break; + case 128: + frequency = 5640; + break; + case 132: + frequency = 5660; + break; + case 136: + frequency = 5680; + break; + case 140: + frequency = 5700; + break; + case 149: + frequency = 5745; + break; + case 153: + frequency = 5765; + break; + case 157: + frequency = 5785; + break; + case 161: + frequency = 5805; + break; + case 165: + frequency = 5825; + break; + } + + return frequency; +} + +uint16_t ConnectivityUtils::MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inBand == kWiFi_BAND_2_4_GHZ) + { + frequency = Map2400MHz(inChannel); + } + else if (inBand == kWiFi_BAND_5_0_GHZ) + { + frequency = Map5000MHz(inChannel); + } + + return frequency; +} + +uint8_t ConnectivityUtils::MapFrequencyToChannel(const uint16_t frequency) +{ + if (frequency < 2412) + return 0; + + if (frequency < 2484) + return (frequency - 2407) / 5; + + if (frequency == 2484) + return 14; + + return frequency / 5 - 1000; +} + +double ConnectivityUtils::ConvertFrequenceToFloat(const iw_freq * in) +{ + double result = (double) in->m; + + for (int i = 0; i < in->e; i++) + result *= 10; + + return result; +} + +InterfaceTypeEnum ConnectivityUtils::GetInterfaceConnectionType(const char * ifname) +{ + InterfaceTypeEnum ret = InterfaceTypeEnum::kUnspecified; + int sock = -1; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + ChipLogError(DeviceLayer, "Failed to open socket"); + return InterfaceTypeEnum::kUnspecified; + } + + // Test wireless extensions for CONNECTION_WIFI + struct iwreq pwrq = {}; + Platform::CopyString(pwrq.ifr_name, ifname); + + if (ioctl(sock, SIOCGIWNAME, &pwrq) != -1) + { + ret = InterfaceTypeEnum::kWiFi; + } + else if ((strncmp(ifname, "en", 2) == 0) || (strncmp(ifname, "eth", 3) == 0)) + { + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if (ioctl(sock, SIOCETHTOOL, &ifr) != -1) + ret = InterfaceTypeEnum::kEthernet; + } + + close(sock); + + return ret; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ifname[0] != '\0') + { + struct ifreq req; + + strcpy(req.ifr_name, ifname); + if (ioctl(skfd, SIOCGIFHWADDR, &req) != -1) + { + // Copy 48-bit IEEE MAC Address + VerifyOrReturnError(bufSize >= 6, CHIP_ERROR_BUFFER_TOO_SMALL); + + memset(buf, 0, bufSize); + memcpy(buf, req.ifr_ifru.ifru_hwaddr.sa_data, 6); + err = CHIP_NO_ERROR; + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceIPv4Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp) +{ + CHIP_ERROR err; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + uint8_t index = 0; + + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) + { + if (strcmp(ifname, ifa->ifa_name) == 0) + { + void * addPtr = &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; + + memcpy(ifp->Ipv4AddressesBuffer[index], addPtr, kMaxIPv4AddrSize); + ifp->Ipv4AddressSpans[index] = ByteSpan(ifp->Ipv4AddressesBuffer[index], kMaxIPv4AddrSize); + index++; + + if (index >= kMaxIPv4AddrCount) + { + break; + } + } + } + } + + if (index > 0) + { + err = CHIP_NO_ERROR; + size = index; + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceIPv6Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp) +{ + CHIP_ERROR err; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + uint8_t index = 0; + + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) + { + if (strcmp(ifname, ifa->ifa_name) == 0) + { + void * addPtr = &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr; + + memcpy(ifp->Ipv6AddressesBuffer[index], addPtr, kMaxIPv6AddrSize); + ifp->Ipv6AddressSpans[index] = ByteSpan(ifp->Ipv6AddressesBuffer[index], kMaxIPv6AddrSize); + index++; + + if (index >= kMaxIPv6AddrCount) + { + break; + } + } + } + } + + if (index > 0) + { + err = CHIP_NO_ERROR; + size = index; + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + Platform::CopyString(ifname, bufSize, ifa->ifa_name); + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiParameter(int skfd, /* Socket to the kernel */ + const char * ifname, /* Device name */ + int request, /* WE ID */ + struct iwreq * pwrq) /* Fixed part of the request */ +{ + /* Set device name */ + Platform::CopyString(pwrq->ifr_name, ifname); + + /* Do the request */ + if (ioctl(skfd, request, pwrq) < 0) + { + return CHIP_ERROR_BAD_REQUEST; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats) +{ + struct iwreq wrq; + + wrq.u.data.pointer = (caddr_t) stats; + wrq.u.data.length = sizeof(struct iw_statistics); + wrq.u.data.flags = 1; /*Clear updated flag */ + Platform::CopyString(wrq.ifr_name, ifname); + + return GetWiFiParameter(skfd, ifname, SIOCGIWSTATS, &wrq); +} + +CHIP_ERROR ConnectivityUtils::GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWFREQ, &wrq) == CHIP_NO_ERROR) + { + double freq = ConvertFrequenceToFloat(&(wrq.u.freq)); + VerifyOrReturnError((freq / 1000000) <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + channelNumber = MapFrequencyToChannel(static_cast(freq / 1000000)); + + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiRssi(const char * ifname, int8_t & rssi) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + struct iw_quality * qual = &stats.qual; + + /* Check if the statistics are in RCPI (IEEE 802.11k) */ + if (qual->updated & IW_QUAL_RCPI) + { /* Deal with signal level in RCPI */ + /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + double rcpilevel = (qual->level / 2.0) - 110.0; + VerifyOrReturnError(rcpilevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(rcpilevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Check if the statistics are in dBm */ + if (qual->updated & IW_QUAL_DBM) + { /* Deal with signal level in dBm (absolute power measurement) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + int dblevel = qual->level; + /* Implement a range for dBm[-192; 63] */ + if (qual->level >= 64) + dblevel -= 0x100; + + VerifyOrReturnError(dblevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(dblevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Deal with signal level as relative value (0 -> max) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + VerifyOrReturnError(qual->level <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(qual->level); + err = CHIP_NO_ERROR; + } + } + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + beaconLostCount = stats.miss.beacon; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWRATE, &wrq) == CHIP_NO_ERROR) + { + currentMaxRate = wrq.u.bitrate.value; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + Platform::CopyString(ifname, bufSize, ifa->ifa_name); + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + int skfd; + uint32_t speed = 0; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + close(skfd); + return CHIP_ERROR_READ_FAILED; + } + + speed = (ecmd.speed_hi << 16) | ecmd.speed; + switch (speed) + { + case 10: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate10M; + break; + case 100: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate100M; + break; + case 1000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate1G; + break; + case 25000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate25g; + break; + case 5000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate5G; + break; + case 10000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate10G; + break; + case 40000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate40G; + break; + case 100000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate100G; + break; + case 200000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate200G; + break; + case 400000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate400G; + break; + default: + ChipLogError(DeviceLayer, "Undefined speed! (%" PRIu32 ")\n", speed); + err = CHIP_ERROR_READ_FAILED; + break; + }; + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthFullDuplex(const char * ifname, bool & fullDuplex) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + int skfd; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + fullDuplex = ecmd.duplex == DUPLEX_FULL; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityUtils.h b/src/platform/NuttX/ConnectivityUtils.h new file mode 100644 index 00000000000000..0cfabeca000477 --- /dev/null +++ b/src/platform/NuttX/ConnectivityUtils.h @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on Linux platforms. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static constexpr uint16_t kWiFi_BAND_2_4_GHZ = 2400; +static constexpr uint16_t kWiFi_BAND_5_0_GHZ = 5000; +static constexpr char kWpaSupplicantServiceName[] = "fi.w1.wpa_supplicant1"; +static constexpr char kWpaSupplicantObjectPath[] = "/fi/w1/wpa_supplicant1"; +static constexpr char kWpaSupplicantBlobUnknown[] = "fi.w1.wpa_supplicant1.BlobUnknown"; + +class ConnectivityUtils +{ +public: + static uint16_t MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel); + static uint8_t MapFrequencyToChannel(const uint16_t frequency); + static app::Clusters::GeneralDiagnostics::InterfaceTypeEnum GetInterfaceConnectionType(const char * ifname); + static CHIP_ERROR GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize); + static CHIP_ERROR GetInterfaceIPv4Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp); + static CHIP_ERROR GetInterfaceIPv6Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp); + static CHIP_ERROR GetWiFiInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber); + static CHIP_ERROR GetWiFiRssi(const char * ifname, int8_t & rssi); + static CHIP_ERROR GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount); + static CHIP_ERROR GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate); + static CHIP_ERROR GetEthInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate); + static CHIP_ERROR GetEthFullDuplex(const char * ifname, bool & fullDuplex); + +private: + static uint16_t Map2400MHz(const uint8_t inChannel); + static uint16_t Map5000MHz(const uint8_t inChannel); + static double ConvertFrequenceToFloat(const iw_freq * in); + static CHIP_ERROR GetWiFiParameter(int skfd, const char * ifname, int request, struct iwreq * pwrq); + static CHIP_ERROR GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats); +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp new file mode 100644 index 00000000000000..9e148e77b4484d --- /dev/null +++ b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2022 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 "DeviceInstanceInfoProviderImpl.h" + +#include + +namespace chip { +namespace DeviceLayer { + +CHIP_ERROR DeviceInstanceInfoProviderImpl::GetVendorId(uint16_t & vendorId) +{ + return Internal::PosixConfig::ReadConfigValue(Internal::PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR DeviceInstanceInfoProviderImpl::GetProductId(uint16_t & productId) +{ + return Internal::PosixConfig::ReadConfigValue(Internal::PosixConfig::kConfigKey_ProductId, productId); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h new file mode 100644 index 00000000000000..72679be226b619 --- /dev/null +++ b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2022 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 + +namespace chip { +namespace DeviceLayer { + +class DeviceInstanceInfoProviderImpl : public Internal::GenericDeviceInstanceInfoProvider +{ +public: + CHIP_ERROR GetVendorId(uint16_t & vendorId) override; + CHIP_ERROR GetProductId(uint16_t & productId) override; + + DeviceInstanceInfoProviderImpl(ConfigurationManagerImpl & configManager) : + Internal::GenericDeviceInstanceInfoProvider(configManager) + {} +}; + +inline DeviceInstanceInfoProviderImpl & DeviceInstanceInfoProviderMgrImpl() +{ + static DeviceInstanceInfoProviderImpl sInstance(ConfigurationManagerImpl::GetDefaultInstance()); + return sInstance; +} +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DiagnosticDataProviderImpl.cpp b/src/platform/NuttX/DiagnosticDataProviderImpl.cpp new file mode 100644 index 00000000000000..b47e5c431e41b4 --- /dev/null +++ b/src/platform/NuttX/DiagnosticDataProviderImpl.cpp @@ -0,0 +1,830 @@ +/* + * + * Copyright (c) 2021-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. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object + * for Linux platform. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::TLV; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace { + +enum class EthernetStatsCountType +{ + kEthPacketRxCount, + kEthPacketTxCount, + kEthTxErrCount, + kEthCollisionCount, + kEthOverrunCount +}; + +enum class WiFiStatsCountType +{ + kWiFiUnicastPacketRxCount, + kWiFiUnicastPacketTxCount, + kWiFiMulticastPacketRxCount, + kWiFiMulticastPacketTxCount, + kWiFiOverrunCount +}; + +CHIP_ERROR GetEthernetStatsCount(EthernetStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case EthernetStatsCountType::kEthPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthTxErrCount: + count = stats->tx_errors; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthCollisionCount: + count = stats->collisions; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown Ethernet statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR GetWiFiStatsCount(WiFiStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + // The usecase of this function is embedded devices,on which we can interact with the WiFi + // driver to get the accurate number of muticast and unicast packets accurately. + // On Linux simulation, we can only get the total packets received, the total bytes transmitted, + // the multicast packets received and receiver ring buff overflow. + + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case WiFiStatsCountType::kWiFiUnicastPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiUnicastPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketRxCount: + count = stats->multicast; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketTxCount: + count = 0; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown WiFi statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +} // namespace + +namespace chip { +namespace DeviceLayer { + +DiagnosticDataProviderImpl & DiagnosticDataProviderImpl::GetDefaultInstance() +{ + static DiagnosticDataProviderImpl sInstance; + return sInstance; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are not being utilized + // by the current running program. + currentHeapFree = mallocInfo.fordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are being used by + // the current running program. + currentHeapUsed = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // The usecase of this function is embedded devices,on which we would need to intercept + // malloc/calloc/free and then record the maximum amount of heap memory,in bytes, that + // has been used by the Node. + // On Linux, since it uses virtual memory, whereby a page of memory could be copied to + // the hard disk, called swap space, and free up that page of memory. So it is impossible + // to know accurately peak physical memory it use. We just return the current heap memory + // being used by the current running program. + currentHeapHighWatermark = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetWatermarks() +{ + // If implemented, the server SHALL set the value of the CurrentHeapHighWatermark attribute to the + // value of the CurrentHeapUsed. + + // On Linux, the write operation is non-op since we always rely on the mallinfo system + // function to get the current heap memory. + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetThreadMetrics(ThreadMetrics ** threadMetricsOut) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + DIR * proc_dir = opendir("/proc/self/task"); + + if (proc_dir == nullptr) + { + ChipLogError(DeviceLayer, "Failed to open current process task directory"); + } + else + { + ThreadMetrics * head = nullptr; + struct dirent * entry; + + /* proc available, iterate through tasks... */ + while ((entry = readdir(proc_dir)) != nullptr) + { + if (entry->d_name[0] == '.') + continue; + + ThreadMetrics * thread = new ThreadMetrics(); + + Platform::CopyString(thread->NameBuf, entry->d_name); + thread->name.Emplace(CharSpan::fromCharString(thread->NameBuf)); + thread->id = atoi(entry->d_name); + + // TODO: Get stack info of each thread: thread->stackFreeCurrent, + // thread->stackFreeMinimum, thread->stackSize. + + thread->Next = head; + head = thread; + } + + closedir(proc_dir); + + *threadMetricsOut = head; + err = CHIP_NO_ERROR; + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseThreadMetrics(ThreadMetrics * threadMetrics) +{ + while (threadMetrics) + { + ThreadMetrics * del = threadMetrics; + threadMetrics = threadMetrics->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetRebootCount(uint16_t & rebootCount) +{ + uint32_t count = 0; + + CHIP_ERROR err = ConfigurationMgr().GetRebootCount(count); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(count <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rebootCount = static_cast(count); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetUpTime(uint64_t & upTime) +{ + System::Clock::Timestamp currentTime = System::SystemClock().GetMonotonicTimestamp(); + System::Clock::Timestamp startTime = PlatformMgrImpl().GetStartTime(); + + if (currentTime >= startTime) + { + upTime = std::chrono::duration_cast(currentTime - startTime).count(); + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + uint64_t upTime = 0; + + if (GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalHours = 0; + if (ConfigurationMgr().GetTotalOperationalHours(totalHours) == CHIP_NO_ERROR) + { + VerifyOrReturnError(upTime / 3600 <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + totalOperationalHours = totalHours + static_cast(upTime / 3600); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetBootReason(BootReasonType & bootReason) +{ + uint32_t reason = 0; + + CHIP_ERROR err = ConfigurationMgr().GetBootReason(reason); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(reason <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + bootReason = static_cast(reason); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveHardwareFaults(GeneralFaults & hardwareFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following hardware faults statically. + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kRadio))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kSensor))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kPowerSource))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kUserInterfaceFault))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveRadioFaults(GeneralFaults & radioFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kWiFiFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kCellularFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kThreadFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kNFCFault))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveNetworkFaults(GeneralFaults & networkFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kHardwareFailure))); + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kNetworkJammed))); + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kConnectionFailed))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** netifpp) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + NetworkInterface * head = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_PACKET) + { + uint8_t size = 0; + NetworkInterface * ifp = new NetworkInterface(); + + Platform::CopyString(ifp->Name, ifa->ifa_name); + + ifp->name = CharSpan::fromCharString(ifp->Name); + ifp->isOperational = ifa->ifa_flags & IFF_RUNNING; + ifp->type = ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name); + ifp->offPremiseServicesReachableIPv4.SetNull(); + ifp->offPremiseServicesReachableIPv6.SetNull(); + + if (ConnectivityUtils::GetInterfaceIPv4Addrs(ifa->ifa_name, size, ifp) == CHIP_NO_ERROR) + { + if (size > 0) + { + ifp->IPv4Addresses = DataModel::List(ifp->Ipv4AddressSpans, size); + } + } + + if (ConnectivityUtils::GetInterfaceIPv6Addrs(ifa->ifa_name, size, ifp) == CHIP_NO_ERROR) + { + if (size > 0) + { + ifp->IPv6Addresses = DataModel::List(ifp->Ipv6AddressSpans, size); + } + } + + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, ifp->MacAddress, kMaxHardwareAddrSize) != + CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + ifp->hardwareAddress = ByteSpan(ifp->MacAddress, 6); + } + + ifp->Next = head; + head = ifp; + } + } + + *netifpp = head; + err = CHIP_NO_ERROR; + + freeifaddrs(ifaddr); + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseNetworkInterfaces(NetworkInterface * netifp) +{ + while (netifp) + { + NetworkInterface * del = netifp; + netifp = netifp->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthPHYRate(ConnectivityMgrImpl().GetEthernetIfName(), pHYRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthFullDuplex(bool & fullDuplex) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthFullDuplex(ConnectivityMgrImpl().GetEthernetIfName(), fullDuplex); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTimeSinceReset(uint64_t & timeSinceReset) +{ + return GetDiagnosticDataProvider().GetUpTime(timeSinceReset); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketRxCount(uint64_t & packetRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketRxCount, count)); + VerifyOrReturnError(count >= mEthPacketRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetRxCount = count - mEthPacketRxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketTxCount(uint64_t & packetTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketTxCount, count)); + VerifyOrReturnError(count >= mEthPacketTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetTxCount = count - mEthPacketTxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTxErrCount(uint64_t & txErrCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthTxErrCount, count)); + VerifyOrReturnError(count >= mEthTxErrCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + txErrCount = count - mEthTxErrCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthCollisionCount(uint64_t & collisionCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthCollisionCount, count)); + VerifyOrReturnError(count >= mEthCollisionCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + collisionCount = count - mEthCollisionCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthOverrunCount, count)); + VerifyOrReturnError(count >= mEthOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mEthOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetEthNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mEthPacketRxCount = stats->rx_packets; + mEthPacketTxCount = stats->tx_packets; + mEthTxErrCount = stats->tx_errors; + mEthCollisionCount = stats->collisions; + mEthOverrunCount = stats->rx_over_errors; + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiChannelNumber(uint16_t & channelNumber) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiChannelNumber(ConnectivityMgrImpl().GetWiFiIfName(), channelNumber); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiRssi(int8_t & rssi) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiRssi(ConnectivityMgrImpl().GetWiFiIfName(), rssi); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBeaconLostCount(uint32_t & beaconLostCount) +{ + uint32_t count; + + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + ReturnErrorOnFailure(ConnectivityUtils::GetWiFiBeaconLostCount(ConnectivityMgrImpl().GetWiFiIfName(), count)); + VerifyOrReturnError(count >= mBeaconLostCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + beaconLostCount = count - mBeaconLostCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiCurrentMaxRate(ConnectivityMgrImpl().GetWiFiIfName(), currentMaxRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiOverrunCount, count)); + VerifyOrReturnError(count >= mOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetWiFiNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + ReturnErrorOnFailure(GetWiFiBeaconLostCount(mBeaconLostCount)); + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mPacketMulticastRxCount = stats->multicast; + mPacketMulticastTxCount = 0; + mPacketUnicastRxCount = stats->rx_packets; + mPacketUnicastTxCount = stats->tx_packets; + mOverrunCount = stats->rx_over_errors; + + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion) +{ + return ConnectivityMgrImpl().GetWiFiVersion(wiFiVersion); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBssId(MutableByteSpan & value) +{ + return ConnectivityMgrImpl().GetWiFiBssId(value); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType) +{ + return ConnectivityMgrImpl().GetWiFiSecurityType(securityType); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +DiagnosticDataProvider & GetDiagnosticDataProviderImpl() +{ + return DiagnosticDataProviderImpl::GetDefaultInstance(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DiagnosticDataProviderImpl.h b/src/platform/NuttX/DiagnosticDataProviderImpl.h new file mode 100644 index 00000000000000..427a1d1c8d8b3d --- /dev/null +++ b/src/platform/NuttX/DiagnosticDataProviderImpl.h @@ -0,0 +1,117 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object. + */ + +#pragma once + +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the DiagnosticDataProvider singleton object for Linux platforms. + */ +class DiagnosticDataProviderImpl : public DiagnosticDataProvider +{ +public: + static DiagnosticDataProviderImpl & GetDefaultInstance(); + + // ===== Methods that implement the DiagnosticDataProvider abstract interface. + + bool SupportsWatermarks() override { return true; } + CHIP_ERROR GetCurrentHeapFree(uint64_t & currentHeapFree) override; + CHIP_ERROR GetCurrentHeapUsed(uint64_t & currentHeapUsed) override; + CHIP_ERROR GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) override; + CHIP_ERROR GetThreadMetrics(ThreadMetrics ** threadMetricsOut) override; + CHIP_ERROR ResetWatermarks() override; + void ReleaseThreadMetrics(ThreadMetrics * threadMetrics) override; + + CHIP_ERROR GetRebootCount(uint16_t & rebootCount) override; + CHIP_ERROR GetUpTime(uint64_t & upTime) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR GetBootReason(BootReasonType & bootReason) override; + + CHIP_ERROR GetActiveHardwareFaults(GeneralFaults & hardwareFaults) override; + CHIP_ERROR GetActiveRadioFaults(GeneralFaults & radioFaults) override; + CHIP_ERROR GetActiveNetworkFaults(GeneralFaults & networkFaults) override; + + CHIP_ERROR GetNetworkInterfaces(NetworkInterface ** netifpp) override; + void ReleaseNetworkInterfaces(NetworkInterface * netifp) override; + + CHIP_ERROR GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) override; + CHIP_ERROR GetEthFullDuplex(bool & fullDuplex) override; + CHIP_ERROR GetEthTimeSinceReset(uint64_t & timeSinceReset) override; + CHIP_ERROR GetEthPacketRxCount(uint64_t & packetRxCount) override; + CHIP_ERROR GetEthPacketTxCount(uint64_t & packetTxCount) override; + CHIP_ERROR GetEthTxErrCount(uint64_t & txErrCount) override; + CHIP_ERROR GetEthCollisionCount(uint64_t & collisionCount) override; + CHIP_ERROR GetEthOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetEthNetworkDiagnosticsCounts() override; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + CHIP_ERROR GetWiFiChannelNumber(uint16_t & channelNumber) override; + CHIP_ERROR GetWiFiRssi(int8_t & rssi) override; + CHIP_ERROR GetWiFiBeaconLostCount(uint32_t & beaconLostCount) override; + CHIP_ERROR GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) override; + CHIP_ERROR GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) override; + CHIP_ERROR GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) override; + CHIP_ERROR GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) override; + CHIP_ERROR GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) override; + CHIP_ERROR GetWiFiOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetWiFiNetworkDiagnosticsCounts() override; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + CHIP_ERROR GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion) override; + CHIP_ERROR GetWiFiBssId(MutableByteSpan & value) override; + CHIP_ERROR GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType) override; +#endif + +private: + uint64_t mEthPacketRxCount = 0; + uint64_t mEthPacketTxCount = 0; + uint64_t mEthTxErrCount = 0; + uint64_t mEthCollisionCount = 0; + uint64_t mEthOverrunCount = 0; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + uint32_t mBeaconLostCount = 0; + uint32_t mPacketMulticastRxCount = 0; + uint32_t mPacketMulticastTxCount = 0; + uint32_t mPacketUnicastRxCount = 0; + uint32_t mPacketUnicastTxCount = 0; + uint64_t mOverrunCount = 0; +#endif +}; + +/** + * Returns the platform-specific implementation of the DiagnosticDataProvider singleton object. + * + * Applications can use this to gain access to features of the DiagnosticDataProvider + * that are specific to the selected platform. + */ +DiagnosticDataProvider & GetDiagnosticDataProviderImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DnssdImpl.cpp b/src/platform/NuttX/DnssdImpl.cpp new file mode 100644 index 00000000000000..2a1873072fbb9e --- /dev/null +++ b/src/platform/NuttX/DnssdImpl.cpp @@ -0,0 +1,1061 @@ +/* + * + * Copyright (c) 2020-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. + */ + +#include "DnssdImpl.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using chip::Dnssd::DnssdServiceProtocol; +using chip::Dnssd::kDnssdTypeMaxSize; +using chip::Dnssd::TextEntry; +using chip::System::SocketEvents; +using std::chrono::duration_cast; +using std::chrono::microseconds; +using std::chrono::seconds; +using std::chrono::steady_clock; + +namespace { + +AvahiProtocol ToAvahiProtocol(chip::Inet::IPAddressType addressType) +{ +#if INET_CONFIG_ENABLE_IPV4 + AvahiProtocol protocol; + + switch (addressType) + { + case chip::Inet::IPAddressType::kIPv4: + protocol = AVAHI_PROTO_INET; + break; + case chip::Inet::IPAddressType::kIPv6: + protocol = AVAHI_PROTO_INET6; + break; + default: + protocol = AVAHI_PROTO_UNSPEC; + break; + } + + return protocol; +#else + // We only support IPV6, never tell AVAHI about INET4 or UNSPEC because + // UNSPEC may actually return IPv4 data. + return AVAHI_PROTO_INET6; +#endif +} + +chip::Inet::IPAddressType ToAddressType(AvahiProtocol protocol) +{ + chip::Inet::IPAddressType type; + + switch (protocol) + { +#if INET_CONFIG_ENABLE_IPV4 + case AVAHI_PROTO_INET: + type = chip::Inet::IPAddressType::kIPv4; + break; +#endif + case AVAHI_PROTO_INET6: + type = chip::Inet::IPAddressType::kIPv6; + break; + default: + type = chip::Inet::IPAddressType::kUnknown; + break; + } + + return type; +} + +AvahiWatchEvent ToAvahiWatchEvent(SocketEvents events) +{ + return static_cast((events.Has(chip::System::SocketEventFlags::kRead) ? AVAHI_WATCH_IN : 0) | + (events.Has(chip::System::SocketEventFlags::kWrite) ? AVAHI_WATCH_OUT : 0) | + (events.Has(chip::System::SocketEventFlags::kError) ? AVAHI_WATCH_ERR : 0)); +} + +void AvahiWatchCallbackTrampoline(chip::System::SocketEvents events, intptr_t data) +{ + AvahiWatch * const watch = reinterpret_cast(data); + watch->mPendingIO = ToAvahiWatchEvent(events); + watch->mCallback(watch, watch->mSocket, watch->mPendingIO, watch->mContext); +} + +CHIP_ERROR MakeAvahiStringListFromTextEntries(TextEntry * entries, size_t size, AvahiStringList ** strListOut) +{ + *strListOut = avahi_string_list_new(nullptr, nullptr); + + for (size_t i = 0; i < size; i++) + { + uint8_t buf[chip::Dnssd::kDnssdTextMaxSize]; + size_t offset = static_cast(snprintf(reinterpret_cast(buf), sizeof(buf), "%s=", entries[i].mKey)); + + if (offset + entries[i].mDataSize > sizeof(buf)) + { + avahi_string_list_free(*strListOut); + *strListOut = nullptr; + return CHIP_ERROR_INVALID_ARGUMENT; + } + + memcpy(&buf[offset], entries[i].mData, entries[i].mDataSize); + *strListOut = avahi_string_list_add_arbitrary(*strListOut, buf, offset + entries[i].mDataSize); + } + return CHIP_NO_ERROR; +} + +const char * GetProtocolString(DnssdServiceProtocol protocol) +{ + return protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? "_udp" : "_tcp"; +} + +std::string GetFullType(const char * type, DnssdServiceProtocol protocol) +{ + std::ostringstream typeBuilder; + typeBuilder << type << "." << GetProtocolString(protocol); + return typeBuilder.str(); +} + +} // namespace + +namespace chip { +namespace Dnssd { + +MdnsAvahi MdnsAvahi::sInstance; + +Poller::Poller() +{ + mAvahiPoller.userdata = this; + mAvahiPoller.watch_new = WatchNew; + mAvahiPoller.watch_update = WatchUpdate; + mAvahiPoller.watch_get_events = WatchGetEvents; + mAvahiPoller.watch_free = WatchFree; + + mAvahiPoller.timeout_new = TimeoutNew; + mAvahiPoller.timeout_update = TimeoutUpdate; + mAvahiPoller.timeout_free = TimeoutFree; + + mEarliestTimeout = std::chrono::steady_clock::time_point(); +} + +AvahiWatch * Poller::WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context) +{ + return reinterpret_cast(poller->userdata)->WatchNew(fd, event, callback, context); +} + +AvahiWatch * Poller::WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context) +{ + VerifyOrDie(callback != nullptr && fd >= 0); + + auto watch = std::make_unique(); + watch->mSocket = fd; + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().StartWatchingSocket(fd, &watch->mSocketWatch)); + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().SetCallback(watch->mSocketWatch, AvahiWatchCallbackTrampoline, + reinterpret_cast(watch.get()))); + WatchUpdate(watch.get(), event); + watch->mCallback = callback; + watch->mContext = context; + watch->mPoller = this; + mWatches.emplace_back(std::move(watch)); + + return mWatches.back().get(); +} + +void Poller::WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event) +{ + if (event & AVAHI_WATCH_IN) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingRead(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingRead(watch->mSocketWatch)); + } + if (event & AVAHI_WATCH_OUT) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingWrite(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingWrite(watch->mSocketWatch)); + } +} + +AvahiWatchEvent Poller::WatchGetEvents(AvahiWatch * watch) +{ + return watch->mPendingIO; +} + +void Poller::WatchFree(AvahiWatch * watch) +{ + reinterpret_cast(watch->mPoller)->WatchFree(*watch); +} + +void Poller::WatchFree(AvahiWatch & watch) +{ + DeviceLayer::SystemLayerSockets().StopWatchingSocket(&watch.mSocketWatch); + mWatches.erase(std::remove_if(mWatches.begin(), mWatches.end(), + [&watch](const std::unique_ptr & aValue) { return aValue.get() == &watch; }), + mWatches.end()); +} + +AvahiTimeout * Poller::TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context) +{ + VerifyOrDie(poller != nullptr && callback != nullptr); + + return static_cast(poller->userdata)->TimeoutNew(timeout, callback, context); +} + +steady_clock::time_point GetAbsTimeout(const struct timeval * timeout) +{ + steady_clock::time_point now = steady_clock::now(); + steady_clock::time_point absTimeout = now; + + if (timeout != nullptr) + { + absTimeout += seconds(timeout->tv_sec); + absTimeout += microseconds(timeout->tv_usec); + } + + return absTimeout; +} + +AvahiTimeout * Poller::TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context) +{ + mTimers.emplace_back(new AvahiTimeout{ GetAbsTimeout(timeout), callback, timeout != nullptr, context, this }); + AvahiTimeout * timer = mTimers.back().get(); + SystemTimerUpdate(timer); + return timer; +} + +void Poller::TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout) +{ + if (timeout) + { + timer->mAbsTimeout = GetAbsTimeout(timeout); + timer->mEnabled = true; + static_cast(timer->mPoller)->SystemTimerUpdate(timer); + } + else + { + timer->mEnabled = false; + } +} + +void Poller::TimeoutFree(AvahiTimeout * timer) +{ + static_cast(timer->mPoller)->TimeoutFree(*timer); +} + +void Poller::TimeoutFree(AvahiTimeout & timer) +{ + mTimers.erase(std::remove_if(mTimers.begin(), mTimers.end(), + [&timer](const std::unique_ptr & aValue) { return aValue.get() == &timer; }), + mTimers.end()); +} + +void Poller::SystemTimerCallback(System::Layer * layer, void * data) +{ + static_cast(data)->HandleTimeout(); +} + +void Poller::HandleTimeout() +{ + mEarliestTimeout = std::chrono::steady_clock::time_point(); + steady_clock::time_point now = steady_clock::now(); + + AvahiTimeout * earliest = nullptr; + for (auto && timer : mTimers) + { + if (!timer->mEnabled) + { + continue; + } + if (timer->mAbsTimeout <= now) + { + timer->mCallback(timer.get(), timer->mContext); + } + else + { + if ((earliest == nullptr) || (timer->mAbsTimeout < earliest->mAbsTimeout)) + { + earliest = timer.get(); + } + } + } + if (earliest) + { + SystemTimerUpdate(earliest); + } +} + +void Poller::SystemTimerUpdate(AvahiTimeout * timer) +{ + if ((mEarliestTimeout == std::chrono::steady_clock::time_point()) || (timer->mAbsTimeout < mEarliestTimeout)) + { + mEarliestTimeout = timer->mAbsTimeout; + auto delay = std::chrono::duration_cast(steady_clock::now() - mEarliestTimeout); + DeviceLayer::SystemLayer().StartTimer(delay, SystemTimerCallback, this); + } +} + +CHIP_ERROR MdnsAvahi::Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int avahiError = 0; + + Shutdown(); + + VerifyOrExit(initCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(errorCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(mClient == nullptr && mPublishedGroups.empty(), error = CHIP_ERROR_INCORRECT_STATE); + mInitCallback = initCallback; + mErrorCallback = errorCallback; + mAsyncReturnContext = context; + mClient = avahi_client_new(mPoller.GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError); + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_OPEN_FAILED); + VerifyOrExit(avahiError == 0, error = CHIP_ERROR_OPEN_FAILED); + +exit: + return error; +} + +void MdnsAvahi::Shutdown() +{ + StopPublish(); + if (mClient) + { + avahi_client_free(mClient); + mClient = nullptr; + } +} + +CHIP_ERROR MdnsAvahi::SetHostname(const char * hostname) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_INCORRECT_STATE); + // Note: we do no longer set the primary hostname here, as other services + // on the platform might not be happy with the matter mandated hostname. + // Instead, we'll establish our own hostname when needed (see PublishService()) +exit: + return error; +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state, void * context) +{ + static_cast(context)->HandleClientState(client, state); +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state) +{ + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: + ChipLogProgress(DeviceLayer, "Avahi client registered"); + mClient = client; + // no longer create groups here, but on a by-service basis in PublishService() + mInitCallback(mAsyncReturnContext, CHIP_NO_ERROR); + break; + case AVAHI_CLIENT_FAILURE: + ChipLogError(DeviceLayer, "Avahi client failure"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + ChipLogProgress(DeviceLayer, "Avahi re-register required"); + StopPublish(); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_FORCED_RESET); + break; + case AVAHI_CLIENT_CONNECTING: + ChipLogProgress(DeviceLayer, "Avahi connecting"); + break; + } +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context) +{ + static_cast(context)->HandleGroupState(group, state); +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state) +{ + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + ChipLogProgress(DeviceLayer, "Avahi group established"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + ChipLogError(DeviceLayer, "Avahi group collision"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_MDNS_COLLISION); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + ChipLogError(DeviceLayer, "Avahi group internal failure %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(group)))); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +CHIP_ERROR MdnsAvahi::PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context) +{ + std::ostringstream keyBuilder; + std::string key; + std::string type = GetFullType(service.mType, service.mProtocol); + std::string matterHostname; + CHIP_ERROR error = CHIP_NO_ERROR; + AvahiStringList * text = nullptr; + AvahiEntryGroup * group = nullptr; + const char * mainHostname = nullptr; + AvahiIfIndex interface = + service.mInterface.IsPresent() ? static_cast(service.mInterface.GetPlatformInterface()) : AVAHI_IF_UNSPEC; + AvahiProtocol protocol = ToAvahiProtocol(service.mAddressType); + + keyBuilder << service.mName << "." << type << service.mPort << "." << interface; + key = keyBuilder.str(); + ChipLogProgress(DeviceLayer, "PublishService %s", key.c_str()); + auto publishedgroups_it = mPublishedGroups.find(key); + if (publishedgroups_it != mPublishedGroups.end()) + { + // same service was already published, we need to de-publish it first + int avahiRet = avahi_entry_group_free(publishedgroups_it->second); + if (avahiRet != AVAHI_OK) + { + ChipLogError(DeviceLayer, "Cannot remove avahi group: %s", avahi_strerror(avahiRet)); + ExitNow(error = CHIP_ERROR_INTERNAL); + } + mPublishedGroups.erase(publishedgroups_it); + } + + // create fresh group + group = avahi_entry_group_new(mClient, HandleGroupState, this); + VerifyOrExit(group != nullptr, error = CHIP_ERROR_INTERNAL); + + // establish the host name (separately from avahi's default host name that the platform might have, + // unless it matches the matter hostname) + mainHostname = avahi_client_get_host_name(mClient); + if (strcmp(mainHostname, service.mHostName) == 0) + { + // main host name is correct, we can use it + matterHostname = std::string(mainHostname) + ".local"; + } + else + { + // we need to establish a matter hostname separately from the platform's default hostname + char b[chip::Inet::IPAddress::kMaxStringLength]; + SuccessOrExit(error = service.mInterface.GetInterfaceName(b, chip::Inet::IPAddress::kMaxStringLength)); + ChipLogDetail(DeviceLayer, "Using addresses from interface id=%d name=%s", service.mInterface.GetPlatformInterface(), b); + matterHostname = std::string(service.mHostName) + ".local"; + // find addresses to publish + for (chip::Inet::InterfaceAddressIterator addr_it; addr_it.HasCurrent(); addr_it.Next()) + { + // only specific interface? + if (service.mInterface.IsPresent() && addr_it.GetInterfaceId() != service.mInterface) + { + continue; + } + if (addr_it.IsUp()) + { + if (addr_it.IsLoopback()) + { + // do not advertise loopback interface addresses + continue; + } + chip::Inet::IPAddress addr; + if ((addr_it.GetAddress(addr) == CHIP_NO_ERROR) && + ((service.mAddressType == chip::Inet::IPAddressType::kAny) || + (addr.IsIPv6() && service.mAddressType == chip::Inet::IPAddressType::kIPv6) +#if INET_CONFIG_ENABLE_IPV4 + || (addr.IsIPv4() && service.mAddressType == chip::Inet::IPAddressType::kIPv4) +#endif + )) + { + VerifyOrExit(addr.ToString(b) != nullptr, error = CHIP_ERROR_INTERNAL); + AvahiAddress a; + VerifyOrExit(avahi_address_parse(b, AVAHI_PROTO_UNSPEC, &a) != nullptr, error = CHIP_ERROR_INTERNAL); + AvahiIfIndex thisinterface = static_cast(addr_it.GetInterfaceId().GetPlatformInterface()); + // Note: NO_REVERSE publish flag is needed because otherwise we can't have more than one hostname + // for reverse resolving IP addresses back to hostnames + VerifyOrExit(avahi_entry_group_add_address(group, // group + thisinterface, // interface + ToAvahiProtocol(addr.Type()), // protocol + AVAHI_PUBLISH_NO_REVERSE, // publish flags + matterHostname.c_str(), // hostname + &a // address + ) == 0, + error = CHIP_ERROR_INTERNAL); + } + } + } + } + + // create the service + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); + + VerifyOrExit(avahi_entry_group_add_service_strlst(group, interface, protocol, // group, interface, protocol + static_cast(0), // publish flags + service.mName, // service name + type.c_str(), // type + nullptr, // domain + matterHostname.c_str(), // host + service.mPort, // port + text) == 0, // TXT records StringList + error = CHIP_ERROR_INTERNAL); + + // add the subtypes + for (size_t i = 0; i < service.mSubTypeSize; i++) + { + std::ostringstream sstream; + + sstream << service.mSubTypes[i] << "._sub." << type; + + VerifyOrExit(avahi_entry_group_add_service_subtype(group, interface, protocol, static_cast(0), + service.mName, type.c_str(), nullptr, sstream.str().c_str()) == 0, + error = CHIP_ERROR_INTERNAL); + } + VerifyOrExit(avahi_entry_group_commit(group) == 0, error = CHIP_ERROR_INTERNAL); + + // group is now published, pass it to the service map + mPublishedGroups[key] = group; + group = nullptr; + +exit: + if (group != nullptr) + { + avahi_entry_group_free(group); + } + + if (text != nullptr) + { + avahi_string_list_free(text); + } + + // Ideally the callback would be called from `HandleGroupState` when the `AVAHI_ENTRY_GROUP_ESTABLISHED` state + // is received. But the current code use the `userdata` field to pass a pointer to the current MdnsAvahi instance + // and this is all comes from MdnsAvahi::Init that does not have any clue about the `type` that *will* be published. + // The code needs to be updated to support that callback properly. + if (CHIP_NO_ERROR == error) + { + callback(context, type.c_str(), service.mName, CHIP_NO_ERROR); + } + else + { + ChipLogError(DeviceLayer, "PublishService failed: %s", + mClient ? avahi_strerror(avahi_client_errno(mClient)) : "no mClient"); + callback(context, nullptr, nullptr, error); + } + + return error; +} + +CHIP_ERROR MdnsAvahi::StopPublish() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + for (const auto & group : mPublishedGroups) + { + if (group.second) + { + int avahiRet = avahi_entry_group_free(group.second); + if (avahiRet != AVAHI_OK) + { + ChipLogError(DeviceLayer, "Error freeing avahi group: %s", avahi_strerror(avahiRet)); + error = CHIP_ERROR_INTERNAL; + } + } + } + mPublishedGroups.clear(); + return error; +} + +CHIP_ERROR MdnsAvahi::Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, + intptr_t * browseIdentifier) +{ + AvahiServiceBrowser * browser; + BrowseContext * browseContext = chip::Platform::New(); + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + + browseContext->mInstance = this; + browseContext->mContext = context; + browseContext->mCallback = callback; + browseContext->mAddressType = addressType; + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + browseContext->mInterface = avahiInterface; + browseContext->mProtocol = GetFullType(type, protocol); + browseContext->mBrowseRetries = 0; + browseContext->mStopped.store(false); + + browser = avahi_service_browser_new(mClient, avahiInterface, AVAHI_PROTO_UNSPEC, browseContext->mProtocol.c_str(), nullptr, + static_cast(0), HandleBrowse, browseContext); + // Otherwise the browser will be freed in the callback + if (browser == nullptr) + { + chip::Platform::Delete(browseContext); + *browseIdentifier = reinterpret_cast(nullptr); + } + else + { + *browseIdentifier = reinterpret_cast(browseContext); + } + + return browser == nullptr ? CHIP_ERROR_INTERNAL : CHIP_NO_ERROR; +} + +CHIP_ERROR MdnsAvahi::StopBrowse(intptr_t browseIdentifier) +{ + BrowseContext * browseContext = reinterpret_cast(browseIdentifier); + if (browseContext == nullptr) + { + return CHIP_ERROR_NOT_FOUND; + } + // Any running timers here will check mStopped before rescheduling. Leave the timer running + // so we don't race on deletion of the browse context. + browseContext->mStopped.store(true); + return CHIP_NO_ERROR; +} + +DnssdServiceProtocol GetProtocolInType(const char * type) +{ + const char * deliminator = strrchr(type, '.'); + + if (deliminator == nullptr) + { + ChipLogError(Discovery, "Failed to find protocol in type: %s", StringOrNullMarker(type)); + return DnssdServiceProtocol::kDnssdProtocolUnknown; + } + + if (strcmp("._tcp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolTcp; + } + if (strcmp("._udp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolUdp; + } + + ChipLogError(Discovery, "Unknown protocol in type: %s", StringOrNullMarker(type)); + return DnssdServiceProtocol::kDnssdProtocolUnknown; +} + +/// Copies the type from a string containing both type and protocol +/// +/// e.g. if input is "foo.bar", output is "foo", input is 'a.b._tcp", output is "a.b" +template +void CopyTypeWithoutProtocol(char (&dest)[N], const char * typeAndProtocol) +{ + const char * dotPos = strrchr(typeAndProtocol, '.'); + size_t lengthWithoutProtocol = (dotPos != nullptr) ? static_cast(dotPos - typeAndProtocol) : N; + + Platform::CopyString(dest, typeAndProtocol); + + /// above copied everything including the protocol. Truncate the protocol away. + if (lengthWithoutProtocol < N) + { + dest[lengthWithoutProtocol] = 0; + } +} + +void MdnsAvahi::BrowseRetryCallback(chip::System::Layer * aLayer, void * appState) +{ + BrowseContext * context = static_cast(appState); + // Don't schedule anything new if we've stopped. + if (context->mStopped.load()) + { + chip::Platform::Delete(context); + return; + } + AvahiServiceBrowser * newBrowser = + avahi_service_browser_new(context->mInstance->mClient, context->mInterface, AVAHI_PROTO_UNSPEC, context->mProtocol.c_str(), + nullptr, static_cast(0), HandleBrowse, context); + if (newBrowser == nullptr) + { + // If we failed to create the browser, this browse context is effectively done. We need to call the final callback and + // delete the context. + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), true, CHIP_NO_ERROR); + chip::Platform::Delete(context); + } +} + +void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags /*flags*/, + void * userdata) +{ + BrowseContext * context = static_cast(userdata); + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + context->mCallback(context->mContext, nullptr, 0, true, CHIP_ERROR_INTERNAL); + avahi_service_browser_free(browser); + chip::Platform::Delete(context); + break; + case AVAHI_BROWSER_NEW: + ChipLogProgress(DeviceLayer, "Avahi browse: cache new"); + if (strcmp("local", domain) == 0) + { + DnssdService service = {}; + + Platform::CopyString(service.mName, name); + CopyTypeWithoutProtocol(service.mType, type); + service.mProtocol = GetProtocolInType(type); + service.mAddressType = context->mAddressType; + service.mTransportType = ToAddressType(protocol); + service.mInterface = Inet::InterfaceId::Null(); + if (interface != AVAHI_IF_UNSPEC) + { + service.mInterface = static_cast(interface); + } + service.mType[kDnssdTypeMaxSize] = 0; + context->mServices.push_back(service); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: { + ChipLogProgress(DeviceLayer, "Avahi browse: all for now"); + bool needRetries = context->mBrowseRetries++ < kMaxBrowseRetries && !context->mStopped.load(); + // If we were already asked to stop, no need to send a callback - no one is listening. + if (!context->mStopped.load()) + { + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), !needRetries, + CHIP_NO_ERROR); + } + avahi_service_browser_free(browser); + if (needRetries) + { + context->mNextRetryDelay *= 2; + // Hand the ownership of the context over to the timer. It will either schedule a new browse on the context, + // triggering this function, or it will delete and not reschedule (if stopped). + DeviceLayer::SystemLayer().StartTimer(context->mNextRetryDelay / 2, BrowseRetryCallback, context); + } + else + { + // We didn't schedule a timer, so we're responsible for deleting the context + chip::Platform::Delete(context); + } + break; + } + case AVAHI_BROWSER_REMOVE: + ChipLogProgress(DeviceLayer, "Avahi browse: remove"); + if (strcmp("local", domain) == 0) + { + context->mServices.erase( + std::remove_if(context->mServices.begin(), context->mServices.end(), [name, type](const DnssdService & service) { + return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol); + })); + } + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + ChipLogProgress(DeviceLayer, "Avahi browse: cache exhausted"); + break; + } +} + +MdnsAvahi::ResolveContext * MdnsAvahi::AllocateResolveContext() +{ + ResolveContext * context = chip::Platform::New(); + if (context == nullptr) + { + return nullptr; + } + + context->mNumber = mResolveCount++; + mAllocatedResolves.push_back(context); + + return context; +} + +MdnsAvahi::ResolveContext * MdnsAvahi::ResolveContextForHandle(size_t handle) +{ + for (auto it : mAllocatedResolves) + { + if (it->mNumber == handle) + { + return it; + } + } + return nullptr; +} + +void MdnsAvahi::FreeResolveContext(size_t handle) +{ + for (auto it = mAllocatedResolves.begin(); it != mAllocatedResolves.end(); it++) + { + if ((*it)->mNumber == handle) + { + chip::Platform::Delete(*it); + mAllocatedResolves.erase(it); + return; + } + } +} + +void MdnsAvahi::StopResolve(const char * name) +{ + auto truncate_end = std::remove_if(mAllocatedResolves.begin(), mAllocatedResolves.end(), + [name](ResolveContext * ctx) { return strcmp(ctx->mName, name) == 0; }); + + for (auto it = truncate_end; it != mAllocatedResolves.end(); it++) + { + (*it)->mCallback((*it)->mContext, nullptr, Span(), CHIP_ERROR_CANCELLED); + chip::Platform::Delete(*it); + } + + mAllocatedResolves.erase(truncate_end, mAllocatedResolves.end()); +} + +CHIP_ERROR MdnsAvahi::Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, Inet::IPAddressType addressType, + Inet::IPAddressType transportType, Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) +{ + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + ResolveContext * resolveContext = AllocateResolveContext(); + CHIP_ERROR error = CHIP_NO_ERROR; + resolveContext->mInstance = this; + resolveContext->mCallback = callback; + resolveContext->mContext = context; + + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + + Platform::CopyString(resolveContext->mName, name); + resolveContext->mInterface = avahiInterface; + resolveContext->mTransport = ToAvahiProtocol(transportType); + resolveContext->mAddressType = ToAvahiProtocol(addressType); + resolveContext->mFullType = GetFullType(type, protocol); + + AvahiServiceResolver * resolver = + avahi_service_resolver_new(mClient, avahiInterface, resolveContext->mTransport, name, resolveContext->mFullType.c_str(), + nullptr, resolveContext->mAddressType, static_cast(0), HandleResolve, + reinterpret_cast(resolveContext->mNumber)); + // Otherwise the resolver will be freed in the callback + if (resolver == nullptr) + { + error = CHIP_ERROR_INTERNAL; + chip::Platform::Delete(resolveContext); + } + resolveContext->mResolver = resolver; + + return error; +} + +void MdnsAvahi::HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * /*domain*/, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata) +{ + size_t handle = reinterpret_cast(userdata); + ResolveContext * context = sInstance.ResolveContextForHandle(handle); + std::vector textEntries; + + if (context == nullptr) + { + ChipLogError(Discovery, "Invalid context for handling resolves: %ld", static_cast(handle)); + return; + } + + switch (event) + { + case AVAHI_RESOLVER_FAILURE: + if (context->mAttempts++ < 3) + { + ChipLogProgress(DeviceLayer, "Re-trying resolve"); + avahi_service_resolver_free(resolver); + context->mResolver = avahi_service_resolver_new( + context->mInstance->mClient, context->mInterface, context->mTransport, context->mName, context->mFullType.c_str(), + nullptr, context->mAddressType, static_cast(0), HandleResolve, userdata); + if (context->mResolver == nullptr) + { + ChipLogError(DeviceLayer, "Avahi resolve failed on retry"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + sInstance.FreeResolveContext(handle); + } + return; + } + ChipLogError(DeviceLayer, "Avahi resolve failed"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + break; + case AVAHI_RESOLVER_FOUND: + DnssdService result = {}; + + ChipLogProgress(DeviceLayer, "Avahi resolve found"); + + Platform::CopyString(result.mName, name); + CopyTypeWithoutProtocol(result.mType, type); + result.mProtocol = GetProtocolInType(type); + result.mPort = port; + result.mAddressType = ToAddressType(protocol); + result.mInterface = Inet::InterfaceId::Null(); + // It's not clear if we can get the actual value from avahi, so just assume default. + result.mTtlSeconds = AVAHI_DEFAULT_TTL_HOST_NAME; + if (interface != AVAHI_IF_UNSPEC) + { + result.mInterface = static_cast(interface); + } + Platform::CopyString(result.mHostName, host_name); + // Returned value is full QName, want only host part. + char * dot = strchr(result.mHostName, '.'); + if (dot != nullptr) + { + *dot = '\0'; + } + + CHIP_ERROR result_err = CHIP_ERROR_INVALID_ADDRESS; + chip::Inet::IPAddress ipAddress; // Will be set of result_err is set to CHIP_NO_ERROR + if (address) + { + switch (address->proto) + { + case AVAHI_PROTO_INET: +#if INET_CONFIG_ENABLE_IPV4 + struct in_addr addr4; + + memcpy(&addr4, &(address->data.ipv4), sizeof(addr4)); + ipAddress = chip::Inet::IPAddress(addr4); + result_err = CHIP_NO_ERROR; +#else + ChipLogError(Discovery, "Ignoring IPv4 mDNS address."); +#endif + break; + case AVAHI_PROTO_INET6: + struct in6_addr addr6; + + memcpy(&addr6, &(address->data.ipv6), sizeof(addr6)); + ipAddress = chip::Inet::IPAddress(addr6); + result_err = CHIP_NO_ERROR; + break; + default: + break; + } + } + + while (txt != nullptr) + { + for (size_t i = 0; i < txt->size; i++) + { + if (txt->text[i] == '=') + { + txt->text[i] = '\0'; + textEntries.push_back(TextEntry{ reinterpret_cast(txt->text), &txt->text[i + 1], txt->size - i - 1 }); + break; + } + } + txt = txt->next; + } + + if (!textEntries.empty()) + { + result.mTextEntries = textEntries.data(); + } + result.mTextEntrySize = textEntries.size(); + + if (result_err == CHIP_NO_ERROR) + { + context->mCallback(context->mContext, &result, Span(&ipAddress, 1), CHIP_NO_ERROR); + } + else + { + context->mCallback(context->mContext, nullptr, Span(), result_err); + } + break; + } + + sInstance.FreeResolveContext(handle); +} + +CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + return MdnsAvahi::GetInstance().Init(initCallback, errorCallback, context); +} + +void ChipDnssdShutdown() +{ + MdnsAvahi::GetInstance().Shutdown(); +} + +CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context) +{ + VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + if (strcmp(service->mHostName, "") != 0) + { + ReturnErrorOnFailure(MdnsAvahi::GetInstance().SetHostname(service->mHostName)); + } + + return MdnsAvahi::GetInstance().PublishService(*service, callback, context); +} + +CHIP_ERROR ChipDnssdRemoveServices() +{ + return MdnsAvahi::GetInstance().StopPublish(); +} + +CHIP_ERROR ChipDnssdFinalizeServiceUpdate() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, + intptr_t * browseIdentifier) +{ + return MdnsAvahi::GetInstance().Browse(type, protocol, addressType, interface, callback, context, browseIdentifier); +} + +CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier) +{ + return MdnsAvahi::GetInstance().StopBrowse(browseIdentifier); +} + +CHIP_ERROR ChipDnssdResolve(DnssdService * browseResult, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) + +{ + VerifyOrReturnError(browseResult != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + return MdnsAvahi::GetInstance().Resolve(browseResult->mName, browseResult->mType, browseResult->mProtocol, + browseResult->mAddressType, Inet::IPAddressType::kAny, interface, callback, context); +} + +void ChipDnssdResolveNoLongerNeeded(const char * instanceName) +{ + MdnsAvahi::GetInstance().StopResolve(instanceName); +} + +CHIP_ERROR ChipDnssdReconfirmRecord(const char * hostname, chip::Inet::IPAddress address, chip::Inet::InterfaceId interface) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/NuttX/DnssdImpl.h b/src/platform/NuttX/DnssdImpl.h new file mode 100644 index 00000000000000..c66a8c23f700b6 --- /dev/null +++ b/src/platform/NuttX/DnssdImpl.h @@ -0,0 +1,205 @@ +/* + * + * Copyright (c) 2020 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct AvahiWatch +{ + int mSocket; + chip::System::SocketWatchToken mSocketWatch; + AvahiWatchCallback mCallback; ///< The function to be called when interested events happened on mFd. + AvahiWatchEvent mPendingIO; ///< The pending events from the currently active or most recent callback. + void * mContext; ///< A pointer to application-specific context. + void * mPoller; ///< The poller created this watch. +}; + +struct AvahiTimeout +{ + std::chrono::steady_clock::time_point mAbsTimeout; ///< Absolute time when this timer timeout. + AvahiTimeoutCallback mCallback; ///< The function to be called when timeout. + bool mEnabled; ///< Whether the timeout is enabled. + void * mContext; ///< The pointer to application-specific context. + void * mPoller; ///< The poller created this timer. +}; + +namespace chip { +namespace Dnssd { + +class Poller +{ +public: + Poller(void); + + void HandleTimeout(); + + const AvahiPoll * GetAvahiPoll(void) const { return &mAvahiPoller; } + +private: + static AvahiWatch * WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context); + AvahiWatch * WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context); + + static void WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event); + + static AvahiWatchEvent WatchGetEvents(AvahiWatch * watch); + + static void WatchFree(AvahiWatch * watch); + void WatchFree(AvahiWatch & watch); + + static AvahiTimeout * TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context); + AvahiTimeout * TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context); + + static void TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout); + + static void TimeoutFree(AvahiTimeout * timer); + void TimeoutFree(AvahiTimeout & timer); + + void SystemTimerUpdate(AvahiTimeout * timer); + static void SystemTimerCallback(System::Layer * layer, void * data); + + std::vector> mWatches; + std::vector> mTimers; + std::chrono::steady_clock::time_point mEarliestTimeout; + + AvahiPoll mAvahiPoller; +}; + +class MdnsAvahi +{ +public: + MdnsAvahi(const MdnsAvahi &) = delete; + MdnsAvahi & operator=(const MdnsAvahi &) = delete; + + CHIP_ERROR Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context); + void Shutdown(); + CHIP_ERROR SetHostname(const char * hostname); + CHIP_ERROR PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context); + CHIP_ERROR StopPublish(); + CHIP_ERROR Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, intptr_t * browseIdentifier); + CHIP_ERROR StopBrowse(intptr_t browseIdentifier); + CHIP_ERROR Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::IPAddressType transportType, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context); + void StopResolve(const char * name); + + Poller & GetPoller() { return mPoller; } + + static MdnsAvahi & GetInstance() { return sInstance; } + +private: + struct BrowseContext + { + MdnsAvahi * mInstance; + DnssdBrowseCallback mCallback; + void * mContext; + Inet::IPAddressType mAddressType; + std::vector mServices; + size_t mBrowseRetries; + AvahiIfIndex mInterface; + std::string mProtocol; + chip::System::Clock::Timeout mNextRetryDelay = chip::System::Clock::Seconds16(1); + std::atomic_bool mStopped{ false }; + }; + + struct ResolveContext + { + size_t mNumber; // unique number for this context + MdnsAvahi * mInstance; + DnssdResolveCallback mCallback; + void * mContext; + char mName[Common::kInstanceNameMaxLength + 1]; + AvahiIfIndex mInterface; + AvahiProtocol mTransport; + AvahiProtocol mAddressType; + std::string mFullType; + uint8_t mAttempts = 0; + AvahiServiceResolver * mResolver = nullptr; + + ~ResolveContext() + { + if (mResolver != nullptr) + { + avahi_service_resolver_free(mResolver); + mResolver = nullptr; + } + } + }; + + MdnsAvahi() : mClient(nullptr) {} + static MdnsAvahi sInstance; + + /// Allocates a new resolve context with a unique `mNumber` + ResolveContext * AllocateResolveContext(); + + ResolveContext * ResolveContextForHandle(size_t handle); + void FreeResolveContext(size_t handle); + void FreeResolveContext(const char * name); + + static void HandleClientState(AvahiClient * client, AvahiClientState state, void * context); + void HandleClientState(AvahiClient * client, AvahiClientState state); + + static void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context); + void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state); + + static void HandleBrowse(AvahiServiceBrowser * broswer, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags flags, + void * userdata); + static void BrowseRetryCallback(chip::System::Layer * aLayer, void * appState); + static void HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * domain, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata); + + DnssdAsyncReturnCallback mInitCallback; + DnssdAsyncReturnCallback mErrorCallback; + void * mAsyncReturnContext; + + AvahiClient * mClient; + std::map mPublishedGroups; + Poller mPoller; + static constexpr size_t kMaxBrowseRetries = 4; + + // Handling of allocated resolves + size_t mResolveCount = 0; + std::list mAllocatedResolves; +}; + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/NuttX/InetPlatformConfig.h b/src/platform/NuttX/InetPlatformConfig.h new file mode 100644 index 00000000000000..3aab9a7b9b9cd9 --- /dev/null +++ b/src/platform/NuttX/InetPlatformConfig.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP Inet + * Layer on Linux platforms. + * + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#ifndef INET_CONFIG_ENABLE_IPV4 +#error Inet IPv4 configuration should be configured at build generation time +#endif + +// ========== Platform-specific Configuration Overrides ========= + +#ifndef INET_CONFIG_NUM_TCP_ENDPOINTS +#define INET_CONFIG_NUM_TCP_ENDPOINTS 32 +#endif // INET_CONFIG_NUM_TCP_ENDPOINTS + +#ifndef IPV6_MULTICAST_IMPLEMENTED +#define IPV6_MULTICAST_IMPLEMENTED +#endif + +#ifndef INET_CONFIG_NUM_UDP_ENDPOINTS +#define INET_CONFIG_NUM_UDP_ENDPOINTS 32 +#endif // INET_CONFIG_NUM_UDP_ENDPOINTS + +// On linux platform, we have sys/socket.h, so HAVE_SO_BINDTODEVICE should be set to 1 +#define HAVE_SO_BINDTODEVICE 1 diff --git a/src/platform/NuttX/KeyValueStoreManagerImpl.cpp b/src/platform/NuttX/KeyValueStoreManagerImpl.cpp new file mode 100644 index 00000000000000..2f4e2f19663c2c --- /dev/null +++ b/src/platform/NuttX/KeyValueStoreManagerImpl.cpp @@ -0,0 +1,115 @@ +/* + * + * 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. + */ + +/** + * @file + * Platform-specific implementatiuon of KVS for linux. + */ + +#include + +#include +#include + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; + +CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size, + size_t offset_bytes) +{ + size_t read_size; + + // Copy data into value buffer + VerifyOrReturnError(value != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + // On linux read first without a buffer which returns the size, and then + // use a local buffer to read the entire object, which allows partial and + // offset reads. + CHIP_ERROR err = mStorage.ReadValueBin(key, nullptr, 0, read_size); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_BUFFER_TOO_SMALL)) + { + return err; + } + if (offset_bytes > read_size) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + Platform::ScopedMemoryBuffer buf; + VerifyOrReturnError(buf.Alloc(read_size), CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(mStorage.ReadValueBin(key, buf.Get(), read_size, read_size)); + + size_t total_size_to_read = read_size - offset_bytes; + size_t copy_size = std::min(value_size, total_size_to_read); + if (read_bytes_size != nullptr) + { + *read_bytes_size = copy_size; + } + ::memcpy(value, buf.Get() + offset_bytes, copy_size); + + return (value_size < total_size_to_read) ? CHIP_ERROR_BUFFER_TOO_SMALL : CHIP_NO_ERROR; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + err = mStorage.WriteValueBin(key, reinterpret_cast(value), value_size); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + err = mStorage.ClearValue(key); + + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/KeyValueStoreManagerImpl.h b/src/platform/NuttX/KeyValueStoreManagerImpl.h new file mode 100644 index 00000000000000..206a3dc1276128 --- /dev/null +++ b/src/platform/NuttX/KeyValueStoreManagerImpl.h @@ -0,0 +1,79 @@ +/* + * + * 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. + */ + +/** + * @file + * Platform-specific implementation of KVS for linux. + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +class KeyValueStoreManagerImpl : public KeyValueStoreManager +{ +public: + /** + * @brief + * Initalize the KVS, must be called before using. + */ + CHIP_ERROR Init(const char * file) { return mStorage.Init(file); } + + CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size = nullptr, size_t offset = 0); + CHIP_ERROR _Delete(const char * key); + CHIP_ERROR _Put(const char * key, const void * value, size_t value_size); + +private: + DeviceLayer::Internal::ChipLinuxStorage mStorage; + + // ===== Members for internal use by the following friends. + friend KeyValueStoreManager & KeyValueStoreMgr(); + friend KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(); + + static KeyValueStoreManagerImpl sInstance; +}; + +/** + * Returns the public interface of the KeyValueStoreManager singleton object. + * + * Chip applications should use this to access features of the KeyValueStoreManager object + * that are common to all platforms. + */ +inline KeyValueStoreManager & KeyValueStoreMgr(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the KeyValueStoreManager singleton object. + * + * Chip applications can use this to gain access to features of the KeyValueStoreManager + * that are specific to the ESP32 platform. + */ +inline KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/Logging.cpp b/src/platform/NuttX/Logging.cpp new file mode 100644 index 00000000000000..e10ec76d0a9c12 --- /dev/null +++ b/src/platform/NuttX/Logging.cpp @@ -0,0 +1,89 @@ +/* See Project CHIP LICENSE file for licensing information. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if CHIP_USE_PW_LOGGING +#include +#endif // CHIP_USE_PW_LOGGING + +namespace chip { +namespace DeviceLayer { + +/** + * Called whenever a log message is emitted by chip or LwIP. + * + * This function is intended be overridden by the application to, e.g., + * schedule output of queued log entries. + */ +void __attribute__((weak)) OnLogOutput() {} + +} // namespace DeviceLayer + +namespace Logging { +namespace Platform { + +/** + * CHIP log output functions. + */ +void ENFORCE_FORMAT(3, 0) LogV(const char * module, uint8_t category, const char * msg, va_list v) +{ + struct timeval tv; + + // Should not fail per man page of gettimeofday(), but failed to get time is not a fatal error in log. The bad time value will + // indicate the error occurred during getting time. + gettimeofday(&tv, nullptr); + +#if !CHIP_USE_PW_LOGGING + // Lock standard output, so a single log line will not be corrupted in case + // where multiple threads are using logging subsystem at the same time. + flockfile(stdout); + + printf("[%" PRIu64 ".%06" PRIu64 "][%lld:%lld] CHIP:%s: ", static_cast(tv.tv_sec), static_cast(tv.tv_usec), + static_cast(getpid()), static_cast(gettid()), module); + vprintf(msg, v); + printf("\n"); + fflush(stdout); + + funlockfile(stdout); +#else // !CHIP_USE_PW_LOGGING + char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + snprintf(formattedMsg, sizeof(formattedMsg), + "[%" PRIu64 ".%06" PRIu64 "][%lld:%lld] CHIP:%s: ", static_cast(tv.tv_sec), + static_cast(tv.tv_usec), static_cast(syscall(SYS_getpid)), + static_cast(syscall(SYS_gettid)), module); + size_t len = strnlen(formattedMsg, sizeof(formattedMsg)); + vsnprintf(formattedMsg + len, sizeof(formattedMsg) - len, msg, v); + + switch (static_cast(category)) + { + case kLogCategory_Error: + PW_LOG_ERROR("%s", formattedMsg); + break; + case kLogCategory_Progress: + PW_LOG_INFO("%s", formattedMsg); + break; + case kLogCategory_Detail: + case kLogCategory_None: + case kLogCategory_Automation: + PW_LOG_DEBUG("%s", formattedMsg); + break; + } +#endif // !CHIP_USE_PW_LOGGING + + // Let the application know that a log message has been emitted. + DeviceLayer::OnLogOutput(); +} + +} // namespace Platform +} // namespace Logging +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningDriver.h b/src/platform/NuttX/NetworkCommissioningDriver.h new file mode 100644 index 00000000000000..96a647c2d22cbc --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningDriver.h @@ -0,0 +1,241 @@ +/* + * + * Copyright (c) 2021 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +template +class LinuxScanResponseIterator : public Iterator +{ +public: + LinuxScanResponseIterator(std::vector * apScanResponse) : mpScanResponse(apScanResponse) {} + size_t Count() override { return mpScanResponse != nullptr ? mpScanResponse->size() : 0; } + bool Next(T & item) override + { + if (mpScanResponse == nullptr || currentIterating >= mpScanResponse->size()) + { + return false; + } + item = (*mpScanResponse)[currentIterating]; + currentIterating++; + return true; + } + void Release() override + { /* nothing to do, we don't hold the ownership of the vector, and users is not expected to hold the ownership in OnFinished for + scan. */ + } + +private: + size_t currentIterating = 0; + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copyable. + std::vector * mpScanResponse; +}; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +class LinuxWiFiDriver final : public WiFiDriver +{ +public: + class WiFiNetworkIterator final : public NetworkIterator + { + public: + WiFiNetworkIterator(LinuxWiFiDriver * aDriver) : driver(aDriver) {} + size_t Count() override; + bool Next(Network & item) override; + void Release() override { delete this; } + ~WiFiNetworkIterator() override = default; + + private: + LinuxWiFiDriver * driver; + bool exhausted = false; + }; + + void Set5gSupport(bool is5gSupported) { mIs5gSupported = is5gSupported; } + + // BaseDriver + NetworkIterator * GetNetworks() override { return new WiFiNetworkIterator(this); } + CHIP_ERROR Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + void Shutdown() override; + + // WirelessDriver + uint8_t GetMaxNetworks() override { return 1; } + uint8_t GetScanNetworkTimeoutSeconds() override { return 10; } + uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; } + + CHIP_ERROR CommitConfiguration() override; + CHIP_ERROR RevertConfiguration() override; + + Status RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // WiFiDriver + Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) override; + void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override; + + uint32_t GetSupportedWiFiBandsMask() const override + { + uint32_t supportedBands = static_cast(1UL << chip::to_underlying(WiFiBandEnum::k2g4)); + if (mIs5gSupported) + { + supportedBands |= static_cast(1UL << chip::to_underlying(WiFiBandEnum::k5g)); + } + return supportedBands; + } + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + bool SupportsPerDeviceCredentials() override { return true; }; + CHIP_ERROR AddOrUpdateNetworkWithPDC(ByteSpan ssid, ByteSpan networkIdentity, Optional clientIdentityNetworkIndex, + Status & outStatus, MutableCharSpan & outDebugText, MutableByteSpan & outClientIdentity, + uint8_t & outNetworkIndex) override; + CHIP_ERROR GetNetworkIdentity(uint8_t networkIndex, MutableByteSpan & outNetworkIdentity) override; + CHIP_ERROR GetClientIdentity(uint8_t networkIndex, MutableByteSpan & outClientIdentity) override; + CHIP_ERROR SignWithClientIdentity(uint8_t networkIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) override; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +private: + struct WiFiNetwork + { + bool Empty() const { return ssidLen == 0; } + bool Matches(ByteSpan aSsid) const { return !Empty() && ByteSpan(ssid, ssidLen).data_equal(aSsid); } + + uint8_t ssid[DeviceLayer::Internal::kMaxWiFiSSIDLength]; + uint8_t ssidLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(ssid)); + + uint8_t credentials[DeviceLayer::Internal::kMaxWiFiKeyLength]; + uint8_t credentialsLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(credentials)); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + bool UsingPDC() const { return networkIdentityLen != 0; } + + uint8_t networkIdentity[Credentials::kMaxCHIPCompactNetworkIdentityLength]; + uint8_t networkIdentityLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(networkIdentity)); + + uint8_t clientIdentity[Credentials::kMaxCHIPCompactNetworkIdentityLength]; + uint8_t clientIdentityLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(clientIdentity)); + + Platform::SharedPtr clientIdentityKeypair; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + }; + + WiFiNetwork mSavedNetwork; + WiFiNetwork mStagingNetwork; + // Whether 5GHz band is supported, as claimed by callers (`Set5gSupport()`) rather than syscalls. + bool mIs5gSupported = false; +}; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +class LinuxThreadDriver final : public ThreadDriver +{ +public: + class ThreadNetworkIterator final : public NetworkIterator + { + public: + ThreadNetworkIterator(LinuxThreadDriver * aDriver) : driver(aDriver) {} + size_t Count() override; + bool Next(Network & item) override; + void Release() override { delete this; } + ~ThreadNetworkIterator() override = default; + + private: + LinuxThreadDriver * driver; + bool exhausted = false; + }; + + // BaseDriver + NetworkIterator * GetNetworks() override { return new ThreadNetworkIterator(this); } + CHIP_ERROR Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + void Shutdown() override; + + // WirelessDriver + uint8_t GetMaxNetworks() override { return 1; } + uint8_t GetScanNetworkTimeoutSeconds() override { return 10; } + uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; } + + CHIP_ERROR CommitConfiguration() override; + CHIP_ERROR RevertConfiguration() override; + + Status RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // ThreadDriver + Status AddOrUpdateNetwork(ByteSpan operationalDataset, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + void ScanNetworks(ThreadDriver::ScanCallback * callback) override; + ThreadCapabilities GetSupportedThreadFeatures() override; + uint16_t GetThreadVersion() override; + +private: + ThreadNetworkIterator mThreadIterator = ThreadNetworkIterator(this); + Thread::OperationalDataset mSavedNetwork; + Thread::OperationalDataset mStagingNetwork; +}; + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + +class LinuxEthernetDriver final : public EthernetDriver +{ +public: + struct EthernetNetworkIterator final : public NetworkIterator + { + EthernetNetworkIterator() = default; + size_t Count() override { return interfaceNameLen > 0 ? 1 : 0; } + bool Next(Network & item) override + { + if (exhausted) + { + return false; + } + exhausted = true; + memcpy(item.networkID, interfaceName, interfaceNameLen); + item.networkIDLen = interfaceNameLen; + item.connected = true; + return true; + } + void Release() override { delete this; } + ~EthernetNetworkIterator() override = default; + + // Public, but cannot be accessed via NetworkIterator interface. + uint8_t interfaceName[kMaxNetworkIDLen]; + uint8_t interfaceNameLen = 0; + bool exhausted = false; + }; + + uint8_t GetMaxNetworks() override { return 1; }; + NetworkIterator * GetNetworks() override; +}; + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp b/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp new file mode 100644 index 00000000000000..c1792a78fe437c --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2021 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 +#include + +using namespace chip::DeviceLayer::Internal; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +NetworkIterator * LinuxEthernetDriver::GetNetworks() +{ + auto ret = new EthernetNetworkIterator(); + ConnectivityUtils::GetEthInterfaceName(SafePointerCast(ret->interfaceName), sizeof(ret->interfaceName)); + ret->interfaceNameLen = static_cast(strnlen(SafePointerCast(ret->interfaceName), sizeof(ret->interfaceName))); + return ret; +} + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp b/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp new file mode 100644 index 00000000000000..79da48d49c0419 --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp @@ -0,0 +1,223 @@ +/* + * + * Copyright (c) 2021 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 + +#include +#include +#include + +using namespace chip; +using namespace chip::Thread; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + +// NOTE: For ThreadDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will +// load the network config from otbr-agent, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks, +// all changed are made on the staging network. +// TODO: The otbr-posix does not actually maintains its own networking states, it will always persist the last network connected. +// This should not be an issue for most cases, but we should implement the code for maintaining the states by ourselves. + +CHIP_ERROR LinuxThreadDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), CHIP_NO_ERROR); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(mStagingNetwork) == CHIP_NO_ERROR, CHIP_NO_ERROR); + + mSavedNetwork.Init(mStagingNetwork.AsByteSpan()); + + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + + return CHIP_NO_ERROR; +} + +void LinuxThreadDriver::Shutdown() +{ + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(nullptr); +} + +CHIP_ERROR LinuxThreadDriver::CommitConfiguration() +{ + // Note: otbr-agent will persist the networks by their own, we don't have much to do for saving the networks (see Init() above, + // we just loads the saved dataset from otbr-agent.) + mSavedNetwork = mStagingNetwork; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxThreadDriver::RevertConfiguration() +{ + mStagingNetwork = mSavedNetwork; + return CHIP_NO_ERROR; +} + +Status LinuxThreadDriver::AddOrUpdateNetwork(ByteSpan operationalDataset, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + uint8_t extpanid[kSizeExtendedPanId]; + uint8_t newExtpanid[kSizeExtendedPanId]; + Thread::OperationalDataset newDataset; + outDebugText.reduce_size(0); + outNetworkIndex = 0; + newDataset.Init(operationalDataset); + VerifyOrReturnError(newDataset.IsCommissioned(), Status::kOutOfRange); + + VerifyOrReturnError(!mStagingNetwork.IsCommissioned() || memcmp(extpanid, newExtpanid, kSizeExtendedPanId) == 0, + Status::kBoundsExceeded); + + mStagingNetwork = newDataset; + return Status::kSuccess; +} + +Status LinuxThreadDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + return Status::kUnknownError; + } + + VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0, + Status::kNetworkNotFound); + mStagingNetwork.Clear(); + return Status::kSuccess; +} + +Status LinuxThreadDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + return Status::kUnknownError; + } + + VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0, + Status::kNetworkNotFound); + + return Status::kSuccess; +} + +void LinuxThreadDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) +{ + NetworkCommissioning::Status status = Status::kSuccess; + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + ExitNow(status = Status::kNetworkNotFound); + } + else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + ExitNow(status = Status::kUnknownError); + } + + VerifyOrExit((networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0), + status = Status::kNetworkNotFound); + + VerifyOrExit(DeviceLayer::ThreadStackMgrImpl().AttachToThreadNetwork(mStagingNetwork, callback) == CHIP_NO_ERROR, + status = Status::kUnknownError); + +exit: + if (status != Status::kSuccess) + { + callback->OnResult(status, CharSpan(), 0); + } +} + +void LinuxThreadDriver::ScanNetworks(ThreadDriver::ScanCallback * callback) +{ + CHIP_ERROR err = DeviceLayer::ThreadStackMgrImpl().StartThreadScan(callback); + // The ThreadScan callback will always be invoked in CHIP mainloop, which is strictly after this function + if (err != CHIP_NO_ERROR) + { + callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); + } +} + +size_t LinuxThreadDriver::ThreadNetworkIterator::Count() +{ + return driver->mStagingNetwork.IsCommissioned() ? 1 : 0; +} + +bool LinuxThreadDriver::ThreadNetworkIterator::Next(Network & item) +{ + if (exhausted || !driver->mStagingNetwork.IsCommissioned()) + { + return false; + } + uint8_t extpanid[kSizeExtendedPanId]; + VerifyOrReturnError(driver->mStagingNetwork.GetExtendedPanId(extpanid) == CHIP_NO_ERROR, false); + memcpy(item.networkID, extpanid, kSizeExtendedPanId); + item.networkIDLen = kSizeExtendedPanId; + item.connected = false; + exhausted = true; + + Thread::OperationalDataset currentDataset; + uint8_t enabledExtPanId[Thread::kSizeExtendedPanId]; + + // The Thread network is not actually enabled. + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), true); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentDataset) == CHIP_NO_ERROR, true); + // The Thread network is not enabled, but has a different extended pan id. + VerifyOrReturnError(currentDataset.GetExtendedPanId(enabledExtPanId) == CHIP_NO_ERROR, true); + VerifyOrReturnError(memcmp(extpanid, enabledExtPanId, kSizeExtendedPanId) == 0, true); + // The Thread network is enabled and has the same extended pan id as the one in our record. + item.connected = true; + + return true; +} + +ThreadCapabilities LinuxThreadDriver::GetSupportedThreadFeatures() +{ + BitMask capabilites = 0; + capabilites.SetField(ThreadCapabilities::kIsBorderRouterCapable, CHIP_DEVICE_CONFIG_THREAD_BORDER_ROUTER); + capabilites.SetField(ThreadCapabilities::kIsRouterCapable, CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsSleepyEndDeviceCapable, !CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsFullThreadDevice, CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsSynchronizedSleepyEndDeviceCapable, + (!CHIP_DEVICE_CONFIG_THREAD_FTD && CHIP_DEVICE_CONFIG_THREAD_SSED)); + return capabilites; +} + +uint16_t LinuxThreadDriver::GetThreadVersion() +{ + // TODO https://github.com/project-chip/connectedhomeip/issues/30602 + // Needs to be implemented with DBUS io.openthread.BorderRouter Thread API + return 0; +} + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp b/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp new file mode 100644 index 00000000000000..e2de245cbb418e --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp @@ -0,0 +1,361 @@ +/* + * + * Copyright (c) 2021-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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::Crypto; +using namespace chip::Credentials; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +// TODO(#14172): Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides +// some bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related +// code in ConnectivityManagerImpl). +namespace { +constexpr char kWiFiSSIDKeyName[] = "wifi-ssid"; +constexpr char kWiFiCredentialsKeyName[] = "wifi-pass"; +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +constexpr char kWifiNetworkIdentityKeyName[] = "wifi-ni"; +constexpr char kWifiClientIdentityKeyName[] = "wifi-ci"; +constexpr char kWifiClientIdentityKeypairKeyName[] = "wifi-cik"; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +inline CHIP_ERROR IgnoreNotFound(CHIP_ERROR err) +{ + return (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : err; +} +} // namespace + +// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will +// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks, +// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to +// mStagingNetwork and persist the changes. + +// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config. + +// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then +// wpa_supplicant will manage the networks for us.) + +CHIP_ERROR LinuxWiFiDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + CHIP_ERROR err; + WiFiNetwork network; + size_t valueLen = 0; + + auto & kvs = PersistedStorage::KeyValueStoreMgr(); + + SuccessOrExit(err = IgnoreNotFound(kvs.Get(kWiFiSSIDKeyName, network.ssid, sizeof(network.ssid), &valueLen))); + if (valueLen != 0) + { + network.ssidLen = valueLen; + + err = kvs.Get(kWiFiCredentialsKeyName, network.credentials, sizeof(network.credentials), &valueLen); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + SuccessOrExit( + err = kvs.Get(kWifiNetworkIdentityKeyName, network.networkIdentity, sizeof(network.networkIdentity), &valueLen)); + VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED); + network.networkIdentityLen = valueLen; + + SuccessOrExit( + err = kvs.Get(kWifiClientIdentityKeyName, network.clientIdentity, sizeof(network.clientIdentity), &valueLen)); + VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED); + network.clientIdentityLen = valueLen; + + P256SerializedKeypair serializedKeypair; + SuccessOrExit(err = kvs.Get(kWifiClientIdentityKeypairKeyName, serializedKeypair.Bytes(), serializedKeypair.Capacity(), + &valueLen)); + serializedKeypair.SetLength(valueLen); + network.clientIdentityKeypair = Platform::MakeShared(); + SuccessOrExit(err = network.clientIdentityKeypair->Deserialize(serializedKeypair)); + } + else +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { + SuccessOrExit(err); + network.credentialsLen = valueLen; + } + + mStagingNetwork = mSavedNetwork = network; + } + + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + return CHIP_NO_ERROR; + +exit: + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: Failed to load network configuration: %" CHIP_ERROR_FORMAT, + err.Format()); + return err; +} + +void LinuxWiFiDriver::Shutdown() +{ + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(nullptr); +} + +CHIP_ERROR LinuxWiFiDriver::CommitConfiguration() +{ + auto & kvs = PersistedStorage::KeyValueStoreMgr(); + ReturnErrorOnFailure(kvs.Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen)); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (mStagingNetwork.UsingPDC()) + { + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWiFiCredentialsKeyName))); + ReturnErrorOnFailure( + kvs.Put(kWifiNetworkIdentityKeyName, mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen)); + ReturnErrorOnFailure( + kvs.Put(kWifiClientIdentityKeyName, mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen)); + + P256SerializedKeypair serializedKeypair; + ReturnErrorOnFailure(mStagingNetwork.clientIdentityKeypair->Serialize(serializedKeypair)); + ReturnErrorOnFailure( + kvs.Put(kWifiClientIdentityKeypairKeyName, serializedKeypair.ConstBytes(), serializedKeypair.Length())); + } + else + { + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiNetworkIdentityKeyName))); + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeyName))); + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeypairKeyName))); +#else // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + ReturnErrorOnFailure(kvs.Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials, mStagingNetwork.credentialsLen)); + } + + ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig()); + mSavedNetwork = mStagingNetwork; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxWiFiDriver::RevertConfiguration() +{ + mStagingNetwork = mSavedNetwork; + return CHIP_NO_ERROR; +} + +Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + VerifyOrReturnError(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), Status::kBoundsExceeded); + + // Do the check before setting the values, so the data is not updated on error. + VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange); + VerifyOrReturnError(!ssid.empty() && ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange); + + memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size()); + mStagingNetwork.credentialsLen = static_cast(credentials.size()); + + memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size()); + mStagingNetwork.ssidLen = static_cast(ssid.size()); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + mStagingNetwork.networkIdentityLen = 0; + mStagingNetwork.clientIdentityLen = 0; + mStagingNetwork.clientIdentityKeypair.reset(); +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + + return Status::kSuccess; +} + +Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound); + + // Use empty ssid for representing invalid network + mStagingNetwork.ssidLen = 0; + return Status::kSuccess; +} + +Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound); + VerifyOrReturnError(index == 0, Status::kOutOfRange); + // We only support one network, so reorder is actually no-op. + return Status::kSuccess; +} + +void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + Status networkingStatus = Status::kSuccess; + + const auto & network = mStagingNetwork; + VerifyOrExit(network.Matches(networkId), networkingStatus = Status::kNetworkIDNotFound); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (network.UsingPDC()) + { + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork (PDC) '%.*s'", network.ssidLen, network.ssid); + err = ConnectivityMgrImpl().ConnectWiFiNetworkWithPDCAsync( + ByteSpan(network.ssid, network.ssidLen), ByteSpan(network.networkIdentity, network.networkIdentityLen), + ByteSpan(network.clientIdentity, network.clientIdentityLen), *network.clientIdentityKeypair, callback); + } + else +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork '%.*s'", network.ssidLen, network.ssid); + + err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(network.ssid, network.ssidLen), + ByteSpan(network.credentials, network.credentialsLen), callback); + } + +exit: + if (err != CHIP_NO_ERROR) + { + networkingStatus = Status::kUnknownError; + } + + if (networkingStatus != Status::kSuccess) + { + ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %" CHIP_ERROR_FORMAT, err.Format()); + callback->OnResult(networkingStatus, CharSpan(), 0); + } +} + +void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback) +{ + CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback); + if (err != CHIP_NO_ERROR) + { + callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); + } +} + +size_t LinuxWiFiDriver::WiFiNetworkIterator::Count() +{ + return driver->mStagingNetwork.Empty() ? 0 : 1; +} + +bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item) +{ + if (exhausted || driver->mStagingNetwork.Empty()) + { + return false; + } + memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen); + item.networkIDLen = driver->mStagingNetwork.ssidLen; + item.connected = false; + exhausted = true; + + Network configuredNetwork; + CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConfiguredNetwork(configuredNetwork); + if (err == CHIP_NO_ERROR) + { + if (DeviceLayer::ConnectivityMgrImpl().IsWiFiStationConnected() && configuredNetwork.networkIDLen == item.networkIDLen && + memcmp(configuredNetwork.networkID, item.networkID, item.networkIDLen) == 0) + { + item.connected = true; + } + } + + return true; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +CHIP_ERROR LinuxWiFiDriver::AddOrUpdateNetworkWithPDC(ByteSpan ssid, ByteSpan networkIdentity, + Optional clientIdentityNetworkIndex, Status & outStatus, + MutableCharSpan & outDebugText, MutableByteSpan & outClientIdentity, + uint8_t & outNetworkIndex) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + outStatus = Status::kUnknownError; + VerifyOrExit(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), outStatus = Status::kBoundsExceeded); + + VerifyOrExit(!ssid.empty() && ssid.size() <= sizeof(WiFiNetwork::ssid), outStatus = Status::kOutOfRange); + VerifyOrExit(!networkIdentity.empty() && networkIdentity.size() <= sizeof(WiFiNetwork::networkIdentity), + outStatus = Status::kOutOfRange); + VerifyOrExit(!clientIdentityNetworkIndex.HasValue() || (clientIdentityNetworkIndex.Value() == 0 && mStagingNetwork.UsingPDC()), + outStatus = Status::kOutOfRange); + + { + WiFiNetwork network = mStagingNetwork; // update a copy first in case of errors + + memcpy(network.ssid, ssid.data(), network.ssidLen = ssid.size()); + memcpy(network.networkIdentity, networkIdentity.data(), network.networkIdentityLen = networkIdentity.size()); + + // If an existing client identity is being reused, we would need to copy it here, + // but since we're only supporting a single network we simply don't overwrite it. + if (!clientIdentityNetworkIndex.HasValue()) + { + network.clientIdentityKeypair = Platform::MakeShared(); + SuccessOrExit(err = network.clientIdentityKeypair->Initialize(ECPKeyTarget::ECDSA)); + + MutableByteSpan clientIdentity(network.clientIdentity); + SuccessOrExit(err = NewChipNetworkIdentity(*network.clientIdentityKeypair, clientIdentity)); + network.clientIdentityLen = clientIdentity.size(); + } + + network.credentialsLen = 0; + + SuccessOrExit(err = CopySpanToMutableSpan(ByteSpan(network.clientIdentity, network.clientIdentityLen), outClientIdentity)); + + mStagingNetwork = network; + outNetworkIndex = 0; + outStatus = Status::kSuccess; + } + +exit: + outDebugText.reduce_size(0); + return err; +} + +CHIP_ERROR LinuxWiFiDriver::GetNetworkIdentity(uint8_t networkIndex, MutableByteSpan & outNetworkIdentity) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen), outNetworkIdentity); +} + +CHIP_ERROR LinuxWiFiDriver::GetClientIdentity(uint8_t networkIndex, MutableByteSpan & outClientIdentity) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen), outClientIdentity); +} + +CHIP_ERROR LinuxWiFiDriver::SignWithClientIdentity(uint8_t networkIndex, const ByteSpan & message, + P256ECDSASignature & outSignature) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return mStagingNetwork.clientIdentityKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/OTAImageProcessorImpl.cpp b/src/platform/NuttX/OTAImageProcessorImpl.cpp new file mode 100644 index 00000000000000..b6fe393e01e19d --- /dev/null +++ b/src/platform/NuttX/OTAImageProcessorImpl.cpp @@ -0,0 +1,279 @@ +/* + * + * 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 "OTAImageProcessorImpl.h" + +#include + +namespace chip { + +CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() +{ + if (mImageFile == nullptr) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Finalize() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Apply() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Abort() +{ + if (mImageFile == nullptr) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) +{ + if (!mOfs.is_open() || !mOfs.good()) + { + return CHIP_ERROR_INTERNAL; + } + + // Store block data for HandleProcessBlock to access + CHIP_ERROR err = SetBlock(block); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format()); + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +bool OTAImageProcessorImpl::IsFirstImageRun() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return false; + } + + return requestor->GetCurrentUpdateState() == OTARequestorInterface::OTAUpdateStateEnum::kApplying; +} + +CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + uint32_t currentVersion; + uint32_t targetVersion = requestor->GetTargetVersion(); + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); + if (currentVersion != targetVersion) + { + ChipLogError(SoftwareUpdate, "Current software version = %" PRIu32 ", expected software version = %" PRIu32, currentVersion, + targetVersion); + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + unlink(imageProcessor->mImageFile); + + imageProcessor->mParams.downloadedBytes = 0; + imageProcessor->mParams.totalFileBytes = 0; + imageProcessor->mHeaderParser.Init(); + imageProcessor->mOfs.open(imageProcessor->mImageFile, std::ofstream::out | std::ofstream::ate | std::ofstream::app); + if (!imageProcessor->mOfs.good()) + { + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_OPEN_FAILED); + return; + } + + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); +} + +void OTAImageProcessorImpl::HandleFinalize(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + imageProcessor->ReleaseBlock(); + + ChipLogProgress(SoftwareUpdate, "OTA image downloaded to %s", imageProcessor->mImageFile); +} + +void OTAImageProcessorImpl::HandleApply(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + VerifyOrReturn(imageProcessor != nullptr); + + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + VerifyOrReturn(requestor != nullptr); + + // Move the downloaded image to the location where the new image is to be executed from + unlink(kImageExecPath); + rename(imageProcessor->mImageFile, kImageExecPath); + chmod(kImageExecPath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + + // Shutdown the stack and expect to boot into the new image once the event loop is stopped + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { DeviceLayer::PlatformMgr().HandleServerShuttingDown(); }); + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { DeviceLayer::PlatformMgr().StopEventLoopTask(); }); +} + +void OTAImageProcessorImpl::HandleAbort(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + unlink(imageProcessor->mImageFile); + imageProcessor->ReleaseBlock(); +} + +void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + ByteSpan block = imageProcessor->mBlock; + CHIP_ERROR error = imageProcessor->ProcessHeader(block); + if (error != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Image does not contain a valid header"); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INVALID_FILE_IDENTIFIER); + return; + } + + if (!imageProcessor->mOfs.write(reinterpret_cast(block.data()), static_cast(block.size()))) + { + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED); + return; + } + + imageProcessor->mParams.downloadedBytes += block.size(); + imageProcessor->mDownloader->FetchNextData(); +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessHeader(ByteSpan & block) +{ + if (mHeaderParser.IsInitialized()) + { + OTAImageHeader header; + CHIP_ERROR error = mHeaderParser.AccumulateAndDecode(block, header); + + // Needs more data to decode the header + ReturnErrorCodeIf(error == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_NO_ERROR); + ReturnErrorOnFailure(error); + + mParams.totalFileBytes = header.mPayloadSize; + mHeaderParser.Clear(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::SetBlock(ByteSpan & block) +{ + if (block.empty()) + { + ReleaseBlock(); + return CHIP_NO_ERROR; + } + if (mBlock.size() < block.size()) + { + if (!mBlock.empty()) + { + ReleaseBlock(); + } + uint8_t * mBlock_ptr = static_cast(chip::Platform::MemoryAlloc(block.size())); + if (mBlock_ptr == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + mBlock = MutableByteSpan(mBlock_ptr, block.size()); + } + CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock() +{ + if (mBlock.data() != nullptr) + { + chip::Platform::MemoryFree(mBlock.data()); + } + + mBlock = MutableByteSpan(); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/platform/NuttX/OTAImageProcessorImpl.h b/src/platform/NuttX/OTAImageProcessorImpl.h new file mode 100644 index 00000000000000..a8939512e113dd --- /dev/null +++ b/src/platform/NuttX/OTAImageProcessorImpl.h @@ -0,0 +1,75 @@ +/* + * + * 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 + +#include + +namespace chip { + +// Full file path to where the new image will be executed from post-download +static char kImageExecPath[] = "/tmp/ota.update"; + +class OTAImageProcessorImpl : public OTAImageProcessorInterface +{ +public: + //////////// OTAImageProcessorInterface Implementation /////////////// + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Apply() override; + CHIP_ERROR Abort() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + bool IsFirstImageRun() override; + CHIP_ERROR ConfirmCurrentImage() override; + + void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } + void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; } + +private: + //////////// Actual handlers for the OTAImageProcessorInterface /////////////// + static void HandlePrepareDownload(intptr_t context); + static void HandleFinalize(intptr_t context); + static void HandleApply(intptr_t context); + static void HandleAbort(intptr_t context); + static void HandleProcessBlock(intptr_t context); + + CHIP_ERROR ProcessHeader(ByteSpan & block); + + /** + * Called to allocate memory for mBlock if necessary and set it to block + */ + CHIP_ERROR SetBlock(ByteSpan & block); + + /** + * Called to release allocated memory for mBlock + */ + CHIP_ERROR ReleaseBlock(); + + std::ofstream mOfs; + MutableByteSpan mBlock; + OTADownloader * mDownloader; + OTAImageHeaderParser mHeaderParser; + const char * mImageFile = nullptr; +}; + +} // namespace chip diff --git a/src/platform/NuttX/PlatformManagerImpl.cpp b/src/platform/NuttX/PlatformManagerImpl.cpp new file mode 100644 index 00000000000000..d590f58c343232 --- /dev/null +++ b/src/platform/NuttX/PlatformManagerImpl.cpp @@ -0,0 +1,327 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object + * for Linux platforms. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip::app::Clusters; + +namespace chip { +namespace DeviceLayer { + +PlatformManagerImpl PlatformManagerImpl::sInstance; + +namespace { + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +void * GLibMainLoopThread(void * userData) +{ + GMainLoop * loop = static_cast(userData); + GMainContext * context = g_main_loop_get_context(loop); + + g_main_context_push_thread_default(context); + g_main_loop_run(loop); + + return nullptr; +} +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +gboolean WiFiIPChangeListener(GIOChannel * ch, GIOCondition /* condition */, void * /* userData */) +{ + + char buffer[4096]; + auto * header = reinterpret_cast(buffer); + ssize_t len; + + if ((len = recv(g_io_channel_unix_get_fd(ch), buffer, sizeof(buffer), 0)) == -1) + { + if (errno == EINTR || errno == EAGAIN) + return G_SOURCE_CONTINUE; + ChipLogError(DeviceLayer, "Error reading from netlink socket: %d", errno); + return G_SOURCE_CONTINUE; + } + + if (len > 0) + { + for (struct nlmsghdr * messageHeader = header; + (NLMSG_OK(messageHeader, static_cast(len))) && (messageHeader->nlmsg_type != NLMSG_DONE); + messageHeader = NLMSG_NEXT(messageHeader, len)) + { + if (header->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg * addressMessage = (struct ifaddrmsg *) NLMSG_DATA(header); + struct rtattr * routeInfo = IFA_RTA(addressMessage); + size_t rtl = IFA_PAYLOAD(header); + + for (; rtl && RTA_OK(routeInfo, rtl); routeInfo = RTA_NEXT(routeInfo, rtl)) + { + if (routeInfo->rta_type == IFA_LOCAL) + { + char name[IFNAMSIZ]; + if (if_indextoname(addressMessage->ifa_index, name) == nullptr) + { + ChipLogError(DeviceLayer, "Error %d when getting the interface name at index: %d", errno, + addressMessage->ifa_index); + continue; + } + + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + ChipLogDetail(DeviceLayer, "No wifi interface name. Ignoring IP update event."); + continue; + } + + if (strcmp(name, ConnectivityMgrImpl().GetWiFiIfName()) != 0) + { + continue; + } + + char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; + inet_ntop(AF_INET, RTA_DATA(routeInfo), ipStrBuf, sizeof(ipStrBuf)); + ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", name, ipStrBuf); + + ChipDeviceEvent event; + event.Type = DeviceEventType::kInternetConnectivityChange; + event.InternetConnectivityChange.IPv4 = kConnectivity_Established; + event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange; + + if (!chip::Inet::IPAddress::FromString(ipStrBuf, event.InternetConnectivityChange.ipAddress)) + { + ChipLogDetail(DeviceLayer, "Failed to report IP address - ip address parsing failed"); + continue; + } + + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogDetail(DeviceLayer, "Failed to report IP address: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + } + } + } + } + else + { + ChipLogError(DeviceLayer, "EOF on netlink socket"); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +// The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session. +// This should be removed or find a better place once we deprecate the rendezvous session. +CHIP_ERROR RunWiFiIPChangeListener() +{ + int sock; + if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + { + ChipLogError(DeviceLayer, "Failed to init netlink socket for IP addresses: %d", errno); + return CHIP_ERROR_INTERNAL; + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_IPV4_IFADDR; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + { + ChipLogError(DeviceLayer, "Failed to bind netlink socket for IP addresses: %d", errno); + close(sock); + return CHIP_ERROR_INTERNAL; + } + + GIOChannel * ch = g_io_channel_unix_new(sock); + GSource * watchSource = g_io_create_watch(ch, G_IO_IN); + g_source_set_callback(watchSource, G_SOURCE_FUNC(WiFiIPChangeListener), nullptr, nullptr); + g_io_channel_set_close_on_unref(ch, TRUE); + g_io_channel_set_encoding(ch, nullptr, nullptr); + + PlatformMgrImpl().GLibMatterContextAttachSource(watchSource); + + g_source_unref(watchSource); + g_io_channel_unref(ch); + + return CHIP_NO_ERROR; +} + +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +} // namespace + +CHIP_ERROR PlatformManagerImpl::_InitChipStack() +{ +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + auto * context = g_main_context_new(); + mGLibMainLoop = g_main_loop_new(context, FALSE); + mGLibMainLoopThread = g_thread_new("gmain-matter", GLibMainLoopThread, mGLibMainLoop); + g_main_context_unref(context); + + { + // Wait for the GLib main loop to start. It is required that the context used + // by the main loop is acquired before any other GLib functions are called. Otherwise, + // the GLibMatterContextInvokeSync() might run functions on the wrong thread. + + std::unique_lock lock(mGLibMainLoopCallbackIndirectionMutex); + GLibMatterContextInvokeData invokeData{}; + + auto * idleSource = g_idle_source_new(); + g_source_set_callback( + idleSource, + [](void * userData_) { + auto * data = reinterpret_cast(userData_); + std::unique_lock lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); + data->mDone = true; + data->mDoneCond.notify_one(); + return G_SOURCE_REMOVE; + }, + &invokeData, nullptr); + GLibMatterContextAttachSource(idleSource); + g_source_unref(idleSource); + + invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); + } + +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + ReturnErrorOnFailure(RunWiFiIPChangeListener()); +#endif + + // Initialize the configuration system. + ReturnErrorOnFailure(Internal::PosixConfig::Init()); + + // Call _InitChipStack() on the generic implementation base class + // to finish the initialization process. + ReturnErrorOnFailure(Internal::GenericPlatformManagerImpl_POSIX::_InitChipStack()); + + // Now set up our device instance info provider. We couldn't do that + // earlier, because the generic implementation sets a generic one. + SetDeviceInstanceInfoProvider(&DeviceInstanceInfoProviderMgrImpl()); + + mStartTime = System::SystemClock().GetMonotonicTimestamp(); + + return CHIP_NO_ERROR; +} + +void PlatformManagerImpl::_Shutdown() +{ + uint64_t upTime = 0; + + if (GetDiagnosticDataProvider().GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalOperationalHours = 0; + + if (ConfigurationMgr().GetTotalOperationalHours(totalOperationalHours) == CHIP_NO_ERROR) + { + ConfigurationMgr().StoreTotalOperationalHours(totalOperationalHours + static_cast(upTime / 3600)); + } + else + { + ChipLogError(DeviceLayer, "Failed to get total operational hours of the Node"); + } + } + else + { + ChipLogError(DeviceLayer, "Failed to get current uptime since the Node’s last reboot"); + } + + Internal::GenericPlatformManagerImpl_POSIX::_Shutdown(); + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + g_main_loop_quit(mGLibMainLoop); + g_thread_join(mGLibMainLoopThread); + g_main_loop_unref(mGLibMainLoop); +#endif +} + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +CHIP_ERROR PlatformManagerImpl::_GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData) +{ + // Because of TSAN false positives, we need to use a mutex to synchronize access to all members of + // the GLibMatterContextInvokeData object (including constructor and destructor). This is a temporary + // workaround until TSAN-enabled GLib will be used in our CI. + std::unique_lock lock(mGLibMainLoopCallbackIndirectionMutex); + + GLibMatterContextInvokeData invokeData{ func, userData }; + + lock.unlock(); + + g_main_context_invoke_full( + g_main_loop_get_context(mGLibMainLoop), G_PRIORITY_HIGH_IDLE, + [](void * userData_) { + auto * data = reinterpret_cast(userData_); + + // XXX: Temporary workaround for TSAN false positives. + std::unique_lock lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); + + auto mFunc = data->mFunc; + auto mUserData = data->mFuncUserData; + + lock_.unlock(); + auto result = mFunc(mUserData); + lock_.lock(); + + data->mDone = true; + data->mFuncResult = result; + data->mDoneCond.notify_one(); + + return G_SOURCE_REMOVE; + }, + &invokeData, nullptr); + + lock.lock(); + + invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); + + return invokeData.mFuncResult; +} +#endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PlatformManagerImpl.h b/src/platform/NuttX/PlatformManagerImpl.h new file mode 100644 index 00000000000000..5669ce5dd28d62 --- /dev/null +++ b/src/platform/NuttX/PlatformManagerImpl.h @@ -0,0 +1,159 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object. + */ + +#pragma once + +#include +#include + +#include +#include + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +#include +#endif + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the PlatformManager singleton object for Linux platforms. + */ +class PlatformManagerImpl final : public PlatformManager, public Internal::GenericPlatformManagerImpl_POSIX +{ + // Allow the PlatformManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend PlatformManager; + + // Allow the generic implementation base class to call helper methods on + // this class. +#ifndef DOXYGEN_SHOULD_SKIP_THIS + friend Internal::GenericPlatformManagerImpl_POSIX; +#endif + +public: + // ===== Platform-specific members that may be accessed directly by the application. + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + /** + * @brief Invoke a function on the Matter GLib context. + * + * If execution of the function will have to be scheduled on other thread, + * this call will block the current thread until the function is executed. + * + * @param[in] function The function to call. + * @param[in] userData User data to pass to the function. + * @returns The result of the function. + */ + template + CHIP_ERROR GLibMatterContextInvokeSync(CHIP_ERROR (*func)(T *), T * userData) + { + return _GLibMatterContextInvokeSync((CHIP_ERROR(*)(void *)) func, (void *) userData); + } + + unsigned int GLibMatterContextAttachSource(GSource * source) + { + VerifyOrDie(mGLibMainLoop != nullptr); + return g_source_attach(source, g_main_loop_get_context(mGLibMainLoop)); + } + +#endif + + System::Clock::Timestamp GetStartTime() { return mStartTime; } + +private: + // ===== Methods that implement the PlatformManager abstract interface. + + CHIP_ERROR _InitChipStack(); + void _Shutdown(); + + // ===== Members for internal use by the following friends. + + friend PlatformManager & PlatformMgr(); + friend PlatformManagerImpl & PlatformMgrImpl(); + friend class Internal::BLEManagerImpl; + + System::Clock::Timestamp mStartTime = System::Clock::kZero; + + static PlatformManagerImpl sInstance; + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + struct GLibMatterContextInvokeData + { + CHIP_ERROR (*mFunc)(void *); + void * mFuncUserData; + CHIP_ERROR mFuncResult; + // Sync primitives to wait for the function to be executed + std::condition_variable mDoneCond; + bool mDone = false; + }; + + /** + * @brief Invoke a function on the Matter GLib context. + * + * @note This function does not provide type safety for the user data. Please, + * use the GLibMatterContextInvokeSync() template function instead. + */ + CHIP_ERROR _GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData); + + // XXX: Mutex for guarding access to glib main event loop callback indirection + // synchronization primitives. This is a workaround to suppress TSAN warnings. + // TSAN does not know that from the thread synchronization perspective the + // g_source_attach() function should be treated as pthread_create(). Memory + // access to shared data before the call to g_source_attach() without mutex + // is not a race condition - the callback will not be executed on glib main + // event loop thread before the call to g_source_attach(). + std::mutex mGLibMainLoopCallbackIndirectionMutex; + + GMainLoop * mGLibMainLoop = nullptr; + GThread * mGLibMainLoopThread = nullptr; + +#endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +}; + +/** + * Returns the public interface of the PlatformManager singleton object. + * + * chip applications should use this to access features of the PlatformManager object + * that are common to all platforms. + */ +inline PlatformManager & PlatformMgr() +{ + return PlatformManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the PlatformManager singleton object. + * + * chip applications can use this to gain access to features of the PlatformManager + * that are specific to the platform. + */ +inline PlatformManagerImpl & PlatformMgrImpl() +{ + return PlatformManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PosixConfig.cpp b/src/platform/NuttX/PosixConfig.cpp new file mode 100644 index 00000000000000..8e8feda20de202 --- /dev/null +++ b/src/platform/NuttX/PosixConfig.cpp @@ -0,0 +1,588 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019-2020 Google LLC. + * Copyright (c) 2018 Nest Labs, Inc. + * 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. + */ + +/** + * @file + * Utilities for interacting with multiple file partitions and maps + * key-value config calls to the correct partition. + */ + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static ChipLinuxStorage gChipLinuxFactoryStorage; +static ChipLinuxStorage gChipLinuxConfigStorage; +static ChipLinuxStorage gChipLinuxCountersStorage; + +// *** CAUTION ***: Changing the names or namespaces of these values will *break* existing devices. + +// NVS namespaces used to store device configuration information. +const char PosixConfig::kConfigNamespace_ChipFactory[] = "chip-factory"; +const char PosixConfig::kConfigNamespace_ChipConfig[] = "chip-config"; +const char PosixConfig::kConfigNamespace_ChipCounters[] = "chip-counters"; + +// Keys stored in the Chip-factory namespace +const PosixConfig::Key PosixConfig::kConfigKey_SerialNum = { kConfigNamespace_ChipFactory, "serial-num" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceId = { kConfigNamespace_ChipFactory, "device-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceCert = { kConfigNamespace_ChipFactory, "device-cert" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceICACerts = { kConfigNamespace_ChipFactory, "device-ca-certs" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDevicePrivateKey = { kConfigNamespace_ChipFactory, "device-key" }; +const PosixConfig::Key PosixConfig::kConfigKey_HardwareVersion = { kConfigNamespace_ChipFactory, "hardware-ver" }; +const PosixConfig::Key PosixConfig::kConfigKey_ManufacturingDate = { kConfigNamespace_ChipFactory, "mfg-date" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupPinCode = { kConfigNamespace_ChipFactory, "pin-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupDiscriminator = { kConfigNamespace_ChipFactory, "discriminator" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pIterationCount = { kConfigNamespace_ChipFactory, "iteration-count" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pSalt = { kConfigNamespace_ChipFactory, "salt" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pVerifier = { kConfigNamespace_ChipFactory, "verifier" }; +const PosixConfig::Key PosixConfig::kConfigKey_VendorId = { kConfigNamespace_ChipFactory, "vendor-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ProductId = { kConfigNamespace_ChipFactory, "product-id" }; + +// Keys stored in the Chip-config namespace +const PosixConfig::Key PosixConfig::kConfigKey_ServiceConfig = { kConfigNamespace_ChipConfig, "service-config" }; +const PosixConfig::Key PosixConfig::kConfigKey_PairedAccountId = { kConfigNamespace_ChipConfig, "account-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ServiceId = { kConfigNamespace_ChipConfig, "service-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_LastUsedEpochKeyId = { kConfigNamespace_ChipConfig, "last-ek-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_FailSafeArmed = { kConfigNamespace_ChipConfig, "fail-safe-armed" }; +const PosixConfig::Key PosixConfig::kConfigKey_RegulatoryLocation = { kConfigNamespace_ChipConfig, "regulatory-location" }; +const PosixConfig::Key PosixConfig::kConfigKey_CountryCode = { kConfigNamespace_ChipConfig, "country-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_LocationCapability = { kConfigNamespace_ChipConfig, "location-capability" }; +const PosixConfig::Key PosixConfig::kConfigKey_UniqueId = { kConfigNamespace_ChipFactory, "unique-id" }; + +// Keys stored in the Chip-counters namespace +const PosixConfig::Key PosixConfig::kCounterKey_RebootCount = { kConfigNamespace_ChipCounters, "reboot-count" }; +const PosixConfig::Key PosixConfig::kCounterKey_UpTime = { kConfigNamespace_ChipCounters, "up-time" }; +const PosixConfig::Key PosixConfig::kCounterKey_TotalOperationalHours = { kConfigNamespace_ChipCounters, + "total-operational-hours" }; +const PosixConfig::Key PosixConfig::kCounterKey_BootReason = { kConfigNamespace_ChipCounters, "boot-reason" }; + +ChipLinuxStorage * PosixConfig::GetStorageForNamespace(Key key) +{ + if (strcmp(key.Namespace, kConfigNamespace_ChipFactory) == 0) + return &gChipLinuxFactoryStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipConfig) == 0) + return &gChipLinuxConfigStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipCounters) == 0) + return &gChipLinuxCountersStorage; + + return nullptr; +} + +CHIP_ERROR PosixConfig::Init() +{ + return PersistedStorage::KeyValueStoreMgrImpl().Init(CHIP_CONFIG_KVS_PATH); +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, bool & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + uint32_t intVal; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, intVal); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + val = (intVal != 0); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint16_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint32_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint64_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + // Special case the MfrDeviceId value, optionally allowing it to be read as a blob containing + // a 64-bit big-endian integer, instead of a u64 value. + if (key == kConfigKey_MfrDeviceId) + { + uint8_t deviceIdBytes[sizeof(uint64_t)]; + size_t deviceIdLen = sizeof(deviceIdBytes); + size_t deviceIdOutLen; + err = storage->ReadValueBin(key.Name, deviceIdBytes, deviceIdLen, deviceIdOutLen); + if (err == CHIP_NO_ERROR) + { + VerifyOrExit(deviceIdOutLen == sizeof(deviceIdBytes), err = CHIP_ERROR_INCORRECT_STATE); + val = Encoding::BigEndian::Get64(deviceIdBytes); + ExitNow(); + } + } + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueStr(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueBin(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, bool val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %s", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), + val ? "true" : "false"); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint16_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %u (0x%X)", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), val, + val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint32_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu32 " (0x%" PRIX32 ")", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint64_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu64 " (0x%" PRIX64 ")", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (str != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueStr(key.Name, str); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = \"%s\"", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), + str); + } + + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ +#if CHIP_CONFIG_MEMORY_MGMT_MALLOC + CHIP_ERROR err; + char * strCopy = nullptr; + + if (str != nullptr) + { + strCopy = strndup(str, strLen); + VerifyOrExit(strCopy != nullptr, err = CHIP_ERROR_NO_MEMORY); + } + + err = PosixConfig::WriteConfigValueStr(key, strCopy); + +exit: + if (strCopy != nullptr) + { + free(strCopy); + } + return err; +#else +#error "Unsupported CHIP_CONFIG_MEMORY_MGMT configuration" +#endif +} + +CHIP_ERROR PosixConfig::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (data != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueBin(key.Name, data, dataLen); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = (blob length %u)", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), static_cast(dataLen)); + } + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearConfigValue(Key key) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearValue(key.Name); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_NO_ERROR); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS erase: %s/%s", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name)); + +exit: + return err; +} + +bool PosixConfig::ConfigValueExists(Key key) +{ + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + if (storage == nullptr) + return false; + + return storage->HasValue(key.Name); +} + +CHIP_ERROR PosixConfig::EnsureNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipFactory) == 0) + { + storage = &gChipLinuxFactoryStorage; + err = storage->Init(CHIP_DEFAULT_FACTORY_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + err = storage->Init(CHIP_DEFAULT_CONFIG_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + err = storage->Init(CHIP_DEFAULT_DATA_PATH); + } + + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + } + + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetConfig() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset configuration"); + + storage = &gChipLinuxConfigStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetCounters() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset counters"); + + storage = &gChipLinuxCountersStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +void PosixConfig::RunConfigUnitTest() +{ + // Run common unit test. + ::chip::DeviceLayer::Internal::RunConfigUnitTest(); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PosixConfig.h b/src/platform/NuttX/PosixConfig.h new file mode 100644 index 00000000000000..c04d4a9be1093f --- /dev/null +++ b/src/platform/NuttX/PosixConfig.h @@ -0,0 +1,131 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * Linux platforms. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage; + +/** + * Provides functions and definitions for accessing device configuration information on the Posix. + * + * This class is designed to be mixed-in to concrete implementation classes as a means to + * provide access to configuration information to generic base classes. + */ +class PosixConfig +{ +public: + struct Key; + + // Maximum length of an NVS key name. + static constexpr size_t kMaxConfigKeyNameLength = 15; + + // NVS namespaces used to store device configuration information. + static const char kConfigNamespace_ChipFactory[]; + static const char kConfigNamespace_ChipConfig[]; + static const char kConfigNamespace_ChipCounters[]; + + // Key definitions for well-known keys. + static const Key kConfigKey_SerialNum; + static const Key kConfigKey_UniqueId; + static const Key kConfigKey_MfrDeviceId; + static const Key kConfigKey_MfrDeviceCert; + static const Key kConfigKey_MfrDeviceICACerts; + static const Key kConfigKey_MfrDevicePrivateKey; + static const Key kConfigKey_HardwareVersion; + static const Key kConfigKey_ManufacturingDate; + static const Key kConfigKey_SetupPinCode; + static const Key kConfigKey_ServiceConfig; + static const Key kConfigKey_PairedAccountId; + static const Key kConfigKey_ServiceId; + static const Key kConfigKey_LastUsedEpochKeyId; + static const Key kConfigKey_FailSafeArmed; + static const Key kConfigKey_SetupDiscriminator; + static const Key kConfigKey_RegulatoryLocation; + static const Key kConfigKey_CountryCode; + static const Key kConfigKey_LocationCapability; + static const Key kConfigKey_Spake2pIterationCount; + static const Key kConfigKey_Spake2pSalt; + static const Key kConfigKey_Spake2pVerifier; + static const Key kConfigKey_VendorId; + static const Key kConfigKey_ProductId; + + static const Key kCounterKey_RebootCount; + static const Key kCounterKey_UpTime; + static const Key kCounterKey_TotalOperationalHours; + static const Key kCounterKey_BootReason; + + static CHIP_ERROR Init(); + + // Config value accessors. + static CHIP_ERROR ReadConfigValue(Key key, bool & val); + static CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint32_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint64_t & val); + static CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR WriteConfigValue(Key key, bool val); + static CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint32_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint64_t val); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen); + static CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen); + static CHIP_ERROR ClearConfigValue(Key key); + static bool ConfigValueExists(Key key); + static CHIP_ERROR FactoryResetConfig(); + static CHIP_ERROR FactoryResetCounters(); + static void RunConfigUnitTest(); + + // NVS Namespace helper functions. + static CHIP_ERROR EnsureNamespace(const char * ns); + static CHIP_ERROR ClearNamespace(const char * ns); + +private: + static ChipLinuxStorage * GetStorageForNamespace(Key key); +}; + +struct PosixConfig::Key +{ + const char * Namespace; + const char * Name; + + bool operator==(const Key & other) const; +}; + +inline bool PosixConfig::Key::operator==(const Key & other) const +{ + return strcmp(Namespace, other.Namespace) == 0 && strcmp(Name, other.Name) == 0; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/README.md b/src/platform/NuttX/README.md new file mode 100644 index 00000000000000..6cd083f929024c --- /dev/null +++ b/src/platform/NuttX/README.md @@ -0,0 +1,14 @@ +# Overview of CHIP NuttX Adaption + +This platform is based on Linux adaptation, The code introduction can be seen in +Linux's README.md:`src/platform/Linux/README.md` + +To avoid integration errors caused by CI build breaks on the NuttX platform when +new features are added to the Linux code, the current Linux code was copied as +the base for NuttX to avoid a strong dependency between the two platforms. + +The reason for adapting based on Linux is that NuttX is also a POSIX-compliant +operating system, and the code can be almost completely reused while keeping the +definitions and comments with the Linux prefix, which makes it easier to +cherry-pick modifications from the Linux platform to the NuttX platform in the +future. diff --git a/src/platform/NuttX/SystemPlatformConfig.h b/src/platform/NuttX/SystemPlatformConfig.h new file mode 100644 index 00000000000000..21495f93a38b82 --- /dev/null +++ b/src/platform/NuttX/SystemPlatformConfig.h @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP System + * Layer on Linux platforms. + * + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +struct ChipDeviceEvent; +} // namespace DeviceLayer +} // namespace chip + +// ==================== Platform Adaptations ==================== + +#define CHIP_SYSTEM_CONFIG_POSIX_LOCKING 1 +#define CHIP_SYSTEM_CONFIG_FREERTOS_LOCKING 0 +#define CHIP_SYSTEM_CONFIG_NO_LOCKING 0 +#define CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME 1 +#define CHIP_SYSTEM_CONFIG_POOL_USE_HEAP 1 + +// ========== Platform-specific Configuration Overrides ========= +#define CHIP_CONFIG_MDNS_RESOLVE_LOOKUP_RESULTS 5 diff --git a/src/platform/NuttX/SystemTimeSupport.cpp b/src/platform/NuttX/SystemTimeSupport.cpp new file mode 100644 index 00000000000000..7eed058256ac3e --- /dev/null +++ b/src/platform/NuttX/SystemTimeSupport.cpp @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2018 Nest Labs, Inc. + * 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. + */ + +/** + * @file + * Provides implementations of the CHIP System Layer platform + * time/clock functions that are suitable for use on the Posix platform. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace System { +namespace Clock { + +namespace Internal { +ClockImpl gClockImpl; +} // namespace Internal + +Microseconds64 ClockImpl::GetMonotonicMicroseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +Milliseconds64 ClockImpl::GetMonotonicMilliseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +CHIP_ERROR ClockImpl::GetClock_RealTime(Microseconds64 & aCurTime) +{ + struct timeval tv; + if (gettimeofday(&tv, nullptr) != 0) + { + return CHIP_ERROR_POSIX(errno); + } + if (tv.tv_sec < CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + if (tv.tv_usec < 0) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + static_assert(CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD >= 0, "We might be letting through negative tv_sec values!"); + aCurTime = Microseconds64((static_cast(tv.tv_sec) * UINT64_C(1000000)) + static_cast(tv.tv_usec)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClockImpl::GetClock_RealTimeMS(Milliseconds64 & aCurTime) +{ + Microseconds64 curTimeUs; + auto err = GetClock_RealTime(curTimeUs); + aCurTime = std::chrono::duration_cast(curTimeUs); + return err; +} + +CHIP_ERROR ClockImpl::SetClock_RealTime(Microseconds64 aNewCurTime) +{ + struct timeval tv; + tv.tv_sec = static_cast(aNewCurTime.count() / UINT64_C(1000000)); + tv.tv_usec = static_cast(aNewCurTime.count() % UINT64_C(1000000)); + if (settimeofday(&tv, nullptr) != 0) + { + return (errno == EPERM) ? CHIP_ERROR_ACCESS_DENIED : CHIP_ERROR_POSIX(errno); + } +#if CHIP_PROGRESS_LOGGING + { + const time_t timep = tv.tv_sec; + struct tm calendar; + localtime_r(&timep, &calendar); + ChipLogProgress(DeviceLayer, "Real time clock set to %lld (%04d/%02d/%02d %02d:%02d:%02d UTC)", + static_cast(tv.tv_sec), calendar.tm_year, calendar.tm_mon, calendar.tm_mday, calendar.tm_hour, + calendar.tm_min, calendar.tm_sec); + } +#endif // CHIP_PROGRESS_LOGGING + return CHIP_NO_ERROR; +} + +} // namespace Clock +} // namespace System +} // namespace chip diff --git a/src/platform/NuttX/ThreadStackManagerImpl.cpp b/src/platform/NuttX/ThreadStackManagerImpl.cpp new file mode 100644 index 00000000000000..cb717324fc989d --- /dev/null +++ b/src/platform/NuttX/ThreadStackManagerImpl.cpp @@ -0,0 +1,770 @@ +/* + * + * Copyright (c) 2020-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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace ::chip::app; +using namespace ::chip::app::Clusters; +using namespace chip::DeviceLayer::NetworkCommissioning; + +namespace chip { +namespace DeviceLayer { + +ThreadStackManagerImpl ThreadStackManagerImpl::sInstance; + +constexpr char ThreadStackManagerImpl::kDBusOpenThreadService[]; +constexpr char ThreadStackManagerImpl::kDBusOpenThreadObjectPath[]; + +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDisabled[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDetached[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleChild[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleRouter[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleLeader[]; + +constexpr char ThreadStackManagerImpl::kPropertyDeviceRole[]; + +namespace { + +struct SetActiveDatasetContext +{ + OpenthreadIoOpenthreadBorderRouter * proxy; + ByteSpan netInfo; +}; + +CHIP_ERROR GLibMatterContextSetActiveDataset(SetActiveDatasetContext * context) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr bytes(g_bytes_new(context->netInfo.data(), context->netInfo.size())); + if (!bytes) + return CHIP_ERROR_NO_MEMORY; + GAutoPtr value(g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes.release(), true)); + if (!value) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_active_dataset_tlvs(context->proxy, value.release()); + return CHIP_NO_ERROR; +} + +} // namespace + +ThreadStackManagerImpl::ThreadStackManagerImpl() : mAttached(false) {} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextInitThreadStack(ThreadStackManagerImpl * self) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + self->mProxy.reset(openthread_io_openthread_border_router_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kDBusOpenThreadService, kDBusOpenThreadObjectPath, nullptr, + &err.GetReceiver())); + VerifyOrReturnError( + self->mProxy != nullptr, CHIP_ERROR_INTERNAL, + ChipLogError(DeviceLayer, "openthread: failed to create openthread dbus proxy %s", err ? err->message : "unknown error")); + + g_signal_connect(self->mProxy.get(), "g-properties-changed", G_CALLBACK(OnDbusPropertiesChanged), self); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_InitThreadStack() +{ + CHIP_ERROR err; + + err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextInitThreadStack, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to init dbus proxy")); + + // If get property is called inside dbus thread (we are going to make it so), XXX_get_XXX can be used instead of XXX_dup_XXX + // which is a little bit faster and the returned object doesn't need to be freed. Same for all following get properties. + GAutoPtr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (role) + { + ThreadDeviceRoleChangedHandler(role.get()); + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data) +{ + ThreadStackManagerImpl * me = reinterpret_cast(user_data); + if (g_variant_n_children(changed_properties) > 0) + { + const gchar * key; + GVariant * value; + + GAutoPtr iter; + g_variant_get(changed_properties, "a{sv}", &iter.GetReceiver()); + if (!iter) + return; + while (g_variant_iter_loop(iter.get(), "{&sv}", &key, &value)) + { + if (key == nullptr || value == nullptr) + continue; + // ownership of key and value is still holding by the iter + DeviceLayer::SystemLayer().ScheduleLambda([me]() { me->_UpdateNetworkStatus(); }); + + if (strcmp(key, kPropertyDeviceRole) == 0) + { + const gchar * value_str = g_variant_get_string(value, nullptr); + if (value_str == nullptr) + continue; + ChipLogProgress(DeviceLayer, "Thread role changed to: %s", StringOrNullMarker(value_str)); + me->ThreadDeviceRoleChangedHandler(value_str); + } + } + } +} + +void ThreadStackManagerImpl::ThreadDeviceRoleChangedHandler(const gchar * role) +{ + bool attached = strcmp(role, kOpenthreadDeviceRoleDetached) != 0 && strcmp(role, kOpenthreadDeviceRoleDisabled) != 0; + + ChipDeviceEvent event = ChipDeviceEvent{}; + + if (attached != mAttached) + { + event.Type = DeviceEventType::kThreadConnectivityChange; + event.ThreadConnectivityChange.Result = + attached ? ConnectivityChange::kConnectivity_Established : ConnectivityChange::kConnectivity_Lost; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread connectivity change: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + mAttached = attached; + + event.Type = DeviceEventType::kThreadStateChange; + event.ThreadStateChange.RoleChanged = true; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread state change: %" CHIP_ERROR_FORMAT, status.Format()); + } +} + +void ThreadStackManagerImpl::_ProcessThreadActivity() {} + +bool ThreadStackManagerImpl::_HaveRouteToAddress(const Inet::IPAddress & destAddr) +{ + if (!mProxy || !_IsThreadAttached()) + { + return false; + } + if (destAddr.IsIPv6LinkLocal()) + { + return true; + } + + GAutoPtr routes(openthread_io_openthread_border_router_dup_external_routes(mProxy.get())); + if (!routes) + return false; + + if (g_variant_n_children(routes.get()) > 0) + { + GAutoPtr iter; + g_variant_get(routes.get(), "av", &iter.GetReceiver()); + if (!iter) + return false; + + GVariant * route; + while (g_variant_iter_loop(iter.get(), "&v", &route)) + { + if (route == nullptr) + continue; + GAutoPtr prefix; + guint16 rloc16; + guchar preference; + gboolean stable; + gboolean nextHopIsThisDevice; + g_variant_get(route, "(&vqybb)", &prefix.GetReceiver(), &rloc16, &preference, &stable, &nextHopIsThisDevice); + if (!prefix) + continue; + + GAutoPtr address; + guchar prefixLength; + g_variant_get(prefix.get(), "(&vy)", &address.GetReceiver(), &prefixLength); + if (!address) + continue; + + GBytes * bytes = g_variant_get_data_as_bytes(address.get()); // the ownership still hold by address + if (bytes == nullptr) + continue; + gsize size; + gconstpointer data = g_bytes_get_data(bytes, &size); + if (data == nullptr) + continue; + if (size != sizeof(struct in6_addr)) + continue; + + Inet::IPPrefix p; + p.IPAddr = Inet::IPAddress(*reinterpret_cast(data)); + p.Length = prefixLength; + + if (p.MatchAddress(destAddr)) + { + return true; + } + } + } + + return false; +} + +void ThreadStackManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + (void) event; + // The otbr-agent processes the Thread state handling by itself so there + // isn't much to do in the Chip stack. +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadProvision(ByteSpan netInfo) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(Thread::OperationalDataset::IsValid(netInfo), CHIP_ERROR_INVALID_ARGUMENT); + + SetActiveDatasetContext context = { mProxy.get(), netInfo }; + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextSetActiveDataset, &context); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to set active dataset")); + + // post an event alerting other subsystems about change in provisioning state + ChipDeviceEvent event; + event.Type = DeviceEventType::kServiceProvisioningChange; + event.ServiceProvisioningChange.IsServiceProvisioned = true; + return PlatformMgr().PostEvent(&event); +} + +CHIP_ERROR ThreadStackManagerImpl::_GetThreadProvision(Thread::OperationalDataset & dataset) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + + { + GAutoPtr err; + GAutoPtr response(g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "ActiveDatasetTlvs"), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err.GetReceiver())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read ActiveDatasetTlvs property: %s", err->message); + return CHIP_ERROR_INTERNAL; + } + + // Note: The actual value is wrapped by a GVariant container, wrapped in another GVariant with tuple type. + + if (response == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + gsize size; + const uint8_t * data = reinterpret_cast(g_variant_get_fixed_array(value.get(), &size, sizeof(guchar))); + ReturnErrorOnFailure(mDataset.Init(ByteSpan(data, size))); + } + + dataset.Init(mDataset.AsByteSpan()); + + return CHIP_NO_ERROR; +} + +bool ThreadStackManagerImpl::_IsThreadProvisioned() +{ + return static_cast(mDataset).IsCommissioned(); +} + +void ThreadStackManagerImpl::_ErasePersistentInfo() +{ + static_cast(mDataset).Clear(); +} + +bool ThreadStackManagerImpl::_IsThreadEnabled() +{ + VerifyOrReturnError(mProxy, false); + + GAutoPtr err; + GAutoPtr response(g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "DeviceRole"), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err.GetReceiver())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read DeviceRole property: %s", err->message); + return false; + } + + if (response == nullptr) + { + return false; + } + + GAutoPtr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return false; + } + + GAutoPtr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return false; + } + + const gchar * role = g_variant_get_string(value.get(), nullptr); + + if (role == nullptr) + { + return false; + } + + return (strcmp(role, kOpenthreadDeviceRoleDisabled) != 0); +} + +bool ThreadStackManagerImpl::_IsThreadAttached() const +{ + return mAttached; +} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextCallAttach(ThreadStackManagerImpl * self) +{ + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + openthread_io_openthread_border_router_call_attach(self->mProxy.get(), nullptr, _OnThreadBrAttachFinished, self); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadEnabled(bool val) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (val) + { + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallAttach, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to attach")); + } + else + { + GAutoPtr err; + gboolean result = openthread_io_openthread_border_router_call_reset_sync(mProxy.get(), nullptr, &err.GetReceiver()); + if (err) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", err->message); + return CHIP_ERROR_INTERNAL; + } + + if (!result) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", "return false"); + return CHIP_ERROR_INTERNAL; + } + } + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ThreadStackManagerImpl * this_ = reinterpret_cast(user_data); + GAutoPtr attachRes; + GAutoPtr err; + { + gboolean result = openthread_io_openthread_border_router_call_attach_finish(this_->mProxy.get(), res, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s", + err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this_]() { + if (this_->mpConnectCallback != nullptr) + { + // TODO: Replace this with actual thread attach result. + this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0); + this_->mpConnectCallback = nullptr; + } + }); + } + else + { + DeviceLayer::SystemLayer().ScheduleLambda([this_]() { + if (this_->mpConnectCallback != nullptr) + { + // TODO: Replace this with actual thread attach result. + this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0); + this_->mpConnectCallback = nullptr; + } + }); + } + } +} + +ConnectivityManager::ThreadDeviceType ThreadStackManagerImpl::_GetThreadDeviceType() +{ + ConnectivityManager::ThreadDeviceType type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + if (!mProxy) + { + ChipLogError(DeviceLayer, "Cannot get device role with Thread api client: %s", ""); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + + GAutoPtr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (!role) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + if (strcmp(role.get(), kOpenthreadDeviceRoleDetached) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleDisabled) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleChild) == 0) + { + GAutoPtr linkMode(openthread_io_openthread_border_router_dup_link_mode(mProxy.get())); + if (!linkMode) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + gboolean rx_on_when_idle; + gboolean device_type; + gboolean network_data; + g_variant_get(linkMode.get(), "(bbb)", &rx_on_when_idle, &device_type, &network_data); + if (!rx_on_when_idle) + { + type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice; + } + else + { + type = device_type ? ConnectivityManager::ThreadDeviceType::kThreadDeviceType_FullEndDevice + : ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice; + } + return type; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleLeader) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleRouter) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_Router; + } + + ChipLogError(DeviceLayer, "Unknown Thread role: %s", role.get()); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType) +{ + gboolean rx_on_when_idle = true; + gboolean device_type = true; + gboolean network_data = true; + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice) + { + network_data = false; + } + else if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice) + { + rx_on_when_idle = false; + network_data = false; + } + + if (!network_data) + { + GAutoPtr linkMode(g_variant_new("(bbb)", rx_on_when_idle, device_type, network_data)); + if (!linkMode) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_link_mode(mProxy.get(), linkMode.release()); + } + + return CHIP_NO_ERROR; +} + +#if CHIP_CONFIG_ENABLE_ICD_SERVER +CHIP_ERROR ThreadStackManagerImpl::_SetPollingInterval(System::Clock::Milliseconds32 pollingInterval) +{ + (void) pollingInterval; + ChipLogError(DeviceLayer, "Set ICD Polling on linux"); + return CHIP_ERROR_NOT_IMPLEMENTED; +} +#endif /* CHIP_CONFIG_ENABLE_ICD_SERVER */ + +bool ThreadStackManagerImpl::_HaveMeshConnectivity() +{ + // TODO: Remove Weave legacy APIs + // For a leader with a child, the child is considered to have mesh connectivity + // and the leader is not, which is a very confusing definition. + // This API is Weave legacy and should be removed. + + ChipLogError(DeviceLayer, "HaveMeshConnectivity has confusing behavior and shouldn't be called"); + return false; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadStatsCounters() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyMinimal() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyFull() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPrimary802154MACAddress(uint8_t * buf) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + guint64 extAddr = openthread_io_openthread_border_router_get_extended_address(mProxy.get()); + + for (size_t i = 0; i < sizeof(extAddr); i++) + { + buf[sizeof(uint64_t) - i - 1] = (extAddr & UINT8_MAX); + extAddr >>= CHAR_BIT; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetExternalIPv6Address(chip::Inet::IPAddress & addr) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPollPeriod(uint32_t & buf) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_JoinerStart() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextCallScan(ThreadStackManagerImpl * self) +{ + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + openthread_io_openthread_border_router_call_scan(self->mProxy.get(), nullptr, _OnNetworkScanFinished, self); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_StartThreadScan(ThreadDriver::ScanCallback * callback) +{ + // There is another ongoing scan request, reject the new one. + VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpScanCallback = callback; + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallScan, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to start scan")); + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ThreadStackManagerImpl * this_ = reinterpret_cast(user_data); + this_->_OnNetworkScanFinished(res); +} + +void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res) +{ + GAutoPtr scan_result; + GAutoPtr err; + { + gboolean result = openthread_io_openthread_border_router_call_scan_finish(mProxy.get(), &scan_result.GetReceiver(), res, + &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s", + err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter(nullptr); + mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter); + } + mpScanCallback = nullptr; + }); + } + } + + std::vector * scanResult = + new std::vector(); + + if (g_variant_n_children(scan_result.get()) > 0) + { + GAutoPtr iter; + g_variant_get(scan_result.get(), "a(tstayqqynyybb)", &iter.GetReceiver()); + if (!iter) + { + delete scanResult; + return; + } + + guint64 ext_address; + const gchar * network_name; + guint64 ext_panid; + const gchar * steering_data; + guint16 panid; + guint16 joiner_udp_port; + guint8 channel; + gint16 rssi; + guint8 lqi; + guint8 version; + gboolean is_native; + gboolean is_joinable; + + while (g_variant_iter_loop(iter.get(), "(tstayqqynyybb)", &ext_address, &network_name, &ext_panid, &steering_data, &panid, + &joiner_udp_port, &channel, &rssi, &lqi, &version, &is_native, &is_joinable)) + { + ChipLogProgress(DeviceLayer, + "Thread Network: %s (" ChipLogFormatX64 ") ExtPanId(" ChipLogFormatX64 ") RSSI %d LQI %u" + " Version %u", + network_name, ChipLogValueX64(ext_address), ChipLogValueX64(ext_panid), rssi, lqi, version); + NetworkCommissioning::ThreadScanResponse networkScanned; + networkScanned.panId = panid; + networkScanned.extendedPanId = ext_panid; + size_t networkNameLen = strlen(network_name); + if (networkNameLen > 16) + { + ChipLogProgress(DeviceLayer, "Network name is too long, ignore it."); + continue; + } + networkScanned.networkNameLen = static_cast(networkNameLen); + memcpy(networkScanned.networkName, network_name, networkNameLen); + networkScanned.channel = channel; + networkScanned.version = version; + networkScanned.extendedAddress = 0; + if (rssi > std::numeric_limits::max()) + { + networkScanned.rssi = std::numeric_limits::max(); + } + else if (rssi < std::numeric_limits::min()) + { + networkScanned.rssi = std::numeric_limits::min(); + } + else + { + networkScanned.rssi = static_cast(rssi); + } + networkScanned.lqi = lqi; + + scanResult->push_back(networkScanned); + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, scanResult]() { + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of + // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this + // lambda. + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter( + const_cast *>(scanResult)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + delete const_cast *>(scanResult); + }); +} + +void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {} + +CHIP_ERROR +ThreadStackManagerImpl::_AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback) +{ + // Reset the previously set callback since it will never be called in case incorrect dataset was supplied. + mpConnectCallback = nullptr; + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(false)); + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadProvision(dataset.AsByteSpan())); + + if (dataset.IsCommissioned()) + { + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(true)); + mpConnectCallback = callback; + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_UpdateNetworkStatus() +{ + // Thread is not enabled, then we are not trying to connect to the network. + VerifyOrReturn(IsThreadEnabled() && mpStatusChangeCallback != nullptr); + + Thread::OperationalDataset dataset; + uint8_t extpanid[Thread::kSizeExtendedPanId]; + + // If we have not provisioned any Thread network, return the status from last network scan, + // If we have provisioned a network, we assume the ot-br-posix is activitely connecting to that network. + CHIP_ERROR err = ThreadStackMgrImpl().GetThreadProvision(dataset); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // The Thread network is not enabled, but has a different extended pan id. + VerifyOrReturn(dataset.GetExtendedPanId(extpanid) == CHIP_NO_ERROR); + + // We have already connected to the network, thus return success. + if (ThreadStackMgrImpl().IsThreadAttached()) + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kSuccess, MakeOptional(ByteSpan(extpanid)), NullOptional); + } + else + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kNetworkNotFound, MakeOptional(ByteSpan(extpanid)), NullOptional); + } +} + +ThreadStackManager & ThreadStackMgr() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +ThreadStackManagerImpl & ThreadStackMgrImpl() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ThreadStackManagerImpl.h b/src/platform/NuttX/ThreadStackManagerImpl.h new file mode 100644 index 00000000000000..5ef3d8ed36555f --- /dev/null +++ b/src/platform/NuttX/ThreadStackManagerImpl.h @@ -0,0 +1,179 @@ +/* + * + * Copyright (c) 2020 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +namespace DeviceLayer { + +class ThreadStackManagerImpl : public ThreadStackManager +{ +public: + ThreadStackManagerImpl(); + + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } + + CHIP_ERROR _InitThreadStack(); + void _ProcessThreadActivity(); + + CHIP_ERROR _StartThreadTask() { return CHIP_NO_ERROR; } // Intentionally left blank + void _LockThreadStack() {} // Intentionally left blank + bool _TryLockThreadStack() { return false; } // Intentionally left blank + void _UnlockThreadStack() {} // Intentionally left blank + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD_SRP_CLIENT + void _WaitOnSrpClearAllComplete() {} + void _NotifySrpClearAllComplete() {} +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD_SRP_CLIENT + + bool _HaveRouteToAddress(const Inet::IPAddress & destAddr); + + void _OnPlatformEvent(const ChipDeviceEvent * event); + + CHIP_ERROR _GetThreadProvision(Thread::OperationalDataset & dataset); + + CHIP_ERROR _SetThreadProvision(ByteSpan netInfo); + + void _OnNetworkScanFinished(GAsyncResult * res); + static void _OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data); + + CHIP_ERROR GetExtendedPanId(uint8_t extPanId[Thread::kSizeExtendedPanId]); + + void _ErasePersistentInfo(); + + bool _IsThreadProvisioned(); + + bool _IsThreadEnabled(); + + bool _IsThreadAttached() const; + + CHIP_ERROR _AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback); + + CHIP_ERROR _SetThreadEnabled(bool val); + + void _OnThreadAttachFinished(void); + + void _UpdateNetworkStatus(); + + static void _OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data); + + ConnectivityManager::ThreadDeviceType _GetThreadDeviceType(); + + CHIP_ERROR _SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType); + +#if CHIP_CONFIG_ENABLE_ICD_SERVER + CHIP_ERROR _SetPollingInterval(System::Clock::Milliseconds32 pollingInterval); +#endif /* CHIP_CONFIG_ENABLE_ICD_SERVER */ + + bool _HaveMeshConnectivity(); + + CHIP_ERROR _GetAndLogThreadStatsCounters(); + + CHIP_ERROR _GetAndLogThreadTopologyMinimal(); + + CHIP_ERROR _GetAndLogThreadTopologyFull(); + + CHIP_ERROR _GetPrimary802154MACAddress(uint8_t * buf); + + CHIP_ERROR _GetExternalIPv6Address(chip::Inet::IPAddress & addr); + + CHIP_ERROR _GetPollPeriod(uint32_t & buf); + + CHIP_ERROR _JoinerStart(); + + void _ResetThreadNetworkDiagnosticsCounts(); + + CHIP_ERROR _StartThreadScan(NetworkCommissioning::ThreadDriver::ScanCallback * callback); + + ~ThreadStackManagerImpl() = default; + + static ThreadStackManagerImpl sInstance; + +private: + static constexpr char kDBusOpenThreadService[] = "io.openthread.BorderRouter.wpan0"; + static constexpr char kDBusOpenThreadObjectPath[] = "/io/openthread/BorderRouter/wpan0"; + + static constexpr char kOpenthreadDeviceRoleDisabled[] = "disabled"; + static constexpr char kOpenthreadDeviceRoleDetached[] = "detached"; + static constexpr char kOpenthreadDeviceRoleChild[] = "child"; + static constexpr char kOpenthreadDeviceRoleRouter[] = "router"; + static constexpr char kOpenthreadDeviceRoleLeader[] = "leader"; + + static constexpr char kPropertyDeviceRole[] = "DeviceRole"; + + struct ThreadNetworkScanned + { + uint16_t panId; + uint64_t extendedPanId; + uint8_t networkName[16]; + uint8_t networkNameLen; + uint16_t channel; + uint8_t version; + uint64_t extendedAddress; + int8_t rssi; + uint8_t lqi; + }; + + GAutoPtr mProxy; + + static CHIP_ERROR GLibMatterContextInitThreadStack(ThreadStackManagerImpl * self); + static CHIP_ERROR GLibMatterContextCallAttach(ThreadStackManagerImpl * self); + static CHIP_ERROR GLibMatterContextCallScan(ThreadStackManagerImpl * self); + static void OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data); + void ThreadDeviceRoleChangedHandler(const gchar * role); + + Thread::OperationalDataset mDataset = {}; + + NetworkCommissioning::ThreadDriver::ScanCallback * mpScanCallback; + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback; + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; + + bool mAttached; +}; + +inline void ThreadStackManagerImpl::_OnThreadAttachFinished(void) +{ + // stub for ThreadStackManager.h +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/WirelessDefs.h b/src/platform/NuttX/WirelessDefs.h new file mode 100644 index 00000000000000..d01338fd03baf9 --- /dev/null +++ b/src/platform/NuttX/WirelessDefs.h @@ -0,0 +1,186 @@ +/* + * + * Copyright (c) 2021 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. + */ + +/** + * @file + * IEEE 802.11 Frame type definitions. + */ + +#pragma once + +/* Status codes (IEEE Std 802.11-2016, 9.4.1.9, Table 9-46) */ +#define WLAN_STATUS_SUCCESS 0 +#define WLAN_STATUS_UNSPECIFIED_FAILURE 1 +#define WLAN_STATUS_TDLS_WAKEUP_ALTERNATE 2 +#define WLAN_STATUS_TDLS_WAKEUP_REJECT 3 +#define WLAN_STATUS_SECURITY_DISABLED 5 +#define WLAN_STATUS_UNACCEPTABLE_LIFETIME 6 +#define WLAN_STATUS_NOT_IN_SAME_BSS 7 +#define WLAN_STATUS_CAPS_UNSUPPORTED 10 +#define WLAN_STATUS_REASSOC_NO_ASSOC 11 +#define WLAN_STATUS_ASSOC_DENIED_UNSPEC 12 +#define WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG 13 +#define WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION 14 +#define WLAN_STATUS_CHALLENGE_FAIL 15 +#define WLAN_STATUS_AUTH_TIMEOUT 16 +#define WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA 17 +#define WLAN_STATUS_ASSOC_DENIED_RATES 18 +#define WLAN_STATUS_ASSOC_DENIED_NOSHORT 19 +#define WLAN_STATUS_SPEC_MGMT_REQUIRED 22 +#define WLAN_STATUS_PWR_CAPABILITY_NOT_VALID 23 +#define WLAN_STATUS_SUPPORTED_CHANNEL_NOT_VALID 24 +#define WLAN_STATUS_ASSOC_DENIED_NO_SHORT_SLOT_TIME 25 +#define WLAN_STATUS_ASSOC_DENIED_NO_HT 27 +#define WLAN_STATUS_R0KH_UNREACHABLE 28 +#define WLAN_STATUS_ASSOC_DENIED_NO_PCO 29 +#define WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY 30 +#define WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION 31 +#define WLAN_STATUS_UNSPECIFIED_QOS_FAILURE 32 +#define WLAN_STATUS_DENIED_INSUFFICIENT_BANDWIDTH 33 +#define WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS 34 +#define WLAN_STATUS_DENIED_QOS_NOT_SUPPORTED 35 +#define WLAN_STATUS_REQUEST_DECLINED 37 +#define WLAN_STATUS_INVALID_PARAMETERS 38 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_CHANGES 39 +#define WLAN_STATUS_INVALID_IE 40 +#define WLAN_STATUS_GROUP_CIPHER_NOT_VALID 41 +#define WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID 42 +#define WLAN_STATUS_AKMP_NOT_VALID 43 +#define WLAN_STATUS_UNSUPPORTED_RSN_IE_VERSION 44 +#define WLAN_STATUS_INVALID_RSN_IE_CAPAB 45 +#define WLAN_STATUS_CIPHER_REJECTED_PER_POLICY 46 +#define WLAN_STATUS_TS_NOT_CREATED 47 +#define WLAN_STATUS_DIRECT_LINK_NOT_ALLOWED 48 +#define WLAN_STATUS_DEST_STA_NOT_PRESENT 49 +#define WLAN_STATUS_DEST_STA_NOT_QOS_STA 50 +#define WLAN_STATUS_ASSOC_DENIED_LISTEN_INT_TOO_LARGE 51 +#define WLAN_STATUS_INVALID_FT_ACTION_FRAME_COUNT 52 +#define WLAN_STATUS_INVALID_PMKID 53 +#define WLAN_STATUS_INVALID_MDIE 54 +#define WLAN_STATUS_INVALID_FTIE 55 +#define WLAN_STATUS_REQUESTED_TCLAS_NOT_SUPPORTED 56 +#define WLAN_STATUS_INSUFFICIENT_TCLAS_PROCESSING_RESOURCES 57 +#define WLAN_STATUS_TRY_ANOTHER_BSS 58 +#define WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED 59 +#define WLAN_STATUS_NO_OUTSTANDING_GAS_REQ 60 +#define WLAN_STATUS_GAS_RESP_NOT_RECEIVED 61 +#define WLAN_STATUS_STA_TIMED_OUT_WAITING_FOR_GAS_RESP 62 +#define WLAN_STATUS_GAS_RESP_LARGER_THAN_LIMIT 63 +#define WLAN_STATUS_REQ_REFUSED_HOME 64 +#define WLAN_STATUS_ADV_SRV_UNREACHABLE 65 +#define WLAN_STATUS_REQ_REFUSED_SSPN 67 +#define WLAN_STATUS_REQ_REFUSED_UNAUTH_ACCESS 68 +#define WLAN_STATUS_INVALID_RSNIE 72 +#define WLAN_STATUS_U_APSD_COEX_NOT_SUPPORTED 73 +#define WLAN_STATUS_U_APSD_COEX_MODE_NOT_SUPPORTED 74 +#define WLAN_STATUS_BAD_INTERVAL_WITH_U_APSD_COEX 75 +#define WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ 76 +#define WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED 77 +#define WLAN_STATUS_CANNOT_FIND_ALT_TBTT 78 +#define WLAN_STATUS_TRANSMISSION_FAILURE 79 +#define WLAN_STATUS_REQ_TCLAS_NOT_SUPPORTED 80 +#define WLAN_STATUS_TCLAS_RESOURCES_EXCHAUSTED 81 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION 82 +#define WLAN_STATUS_REJECT_WITH_SCHEDULE 83 +#define WLAN_STATUS_REJECT_NO_WAKEUP_SPECIFIED 84 +#define WLAN_STATUS_SUCCESS_POWER_SAVE_MODE 85 +#define WLAN_STATUS_PENDING_ADMITTING_FST_SESSION 86 +#define WLAN_STATUS_PERFORMING_FST_NOW 87 +#define WLAN_STATUS_PENDING_GAP_IN_BA_WINDOW 88 +#define WLAN_STATUS_REJECT_U_PID_SETTING 89 +#define WLAN_STATUS_REFUSED_EXTERNAL_REASON 92 +#define WLAN_STATUS_REFUSED_AP_OUT_OF_MEMORY 93 +#define WLAN_STATUS_REJECTED_EMERGENCY_SERVICE_NOT_SUPPORTED 94 +#define WLAN_STATUS_QUERY_RESP_OUTSTANDING 95 +#define WLAN_STATUS_REJECT_DSE_BAND 96 +#define WLAN_STATUS_TCLAS_PROCESSING_TERMINATED 97 +#define WLAN_STATUS_TS_SCHEDULE_CONFLICT 98 +#define WLAN_STATUS_DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL 99 +#define WLAN_STATUS_MCCAOP_RESERVATION_CONFLICT 100 +#define WLAN_STATUS_MAF_LIMIT_EXCEEDED 101 +#define WLAN_STATUS_MCCA_TRACK_LIMIT_EXCEEDED 102 +#define WLAN_STATUS_DENIED_DUE_TO_SPECTRUM_MANAGEMENT 103 +#define WLAN_STATUS_ASSOC_DENIED_NO_VHT 104 +#define WLAN_STATUS_ENABLEMENT_DENIED 105 +#define WLAN_STATUS_RESTRICTION_FROM_AUTHORIZED_GDB 106 +#define WLAN_STATUS_AUTHORIZATION_DEENABLED 107 +#define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112 +#define WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER 113 +#define WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER 123 + +/* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */ +#define WLAN_REASON_UNSPECIFIED 1 +#define WLAN_REASON_PREV_AUTH_NOT_VALID 2 +#define WLAN_REASON_DEAUTH_LEAVING 3 +#define WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY 4 +#define WLAN_REASON_DISASSOC_AP_BUSY 5 +#define WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA 6 +#define WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA 7 +#define WLAN_REASON_DISASSOC_STA_HAS_LEFT 8 +#define WLAN_REASON_STA_REQ_ASSOC_WITHOUT_AUTH 9 +#define WLAN_REASON_PWR_CAPABILITY_NOT_VALID 10 +#define WLAN_REASON_SUPPORTED_CHANNEL_NOT_VALID 11 +#define WLAN_REASON_BSS_TRANSITION_DISASSOC 12 +#define WLAN_REASON_INVALID_IE 13 +#define WLAN_REASON_MICHAEL_MIC_FAILURE 14 +#define WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT 15 +#define WLAN_REASON_GROUP_KEY_UPDATE_TIMEOUT 16 +#define WLAN_REASON_IE_IN_4WAY_DIFFERS 17 +#define WLAN_REASON_GROUP_CIPHER_NOT_VALID 18 +#define WLAN_REASON_PAIRWISE_CIPHER_NOT_VALID 19 +#define WLAN_REASON_AKMP_NOT_VALID 20 +#define WLAN_REASON_UNSUPPORTED_RSN_IE_VERSION 21 +#define WLAN_REASON_INVALID_RSN_IE_CAPAB 22 +#define WLAN_REASON_IEEE_802_1X_AUTH_FAILED 23 +#define WLAN_REASON_CIPHER_SUITE_REJECTED 24 +#define WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE 25 +#define WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED 26 +#define WLAN_REASON_SSP_REQUESTED_DISASSOC 27 +#define WLAN_REASON_NO_SSP_ROAMING_AGREEMENT 28 +#define WLAN_REASON_BAD_CIPHER_OR_AKM 29 +#define WLAN_REASON_NOT_AUTHORIZED_THIS_LOCATION 30 +#define WLAN_REASON_SERVICE_CHANGE_PRECLUDES_TS 31 +#define WLAN_REASON_UNSPECIFIED_QOS_REASON 32 +#define WLAN_REASON_NOT_ENOUGH_BANDWIDTH 33 +#define WLAN_REASON_DISASSOC_LOW_ACK 34 +#define WLAN_REASON_EXCEEDED_TXOP 35 +#define WLAN_REASON_STA_LEAVING 36 +#define WLAN_REASON_END_TS_BA_DLS 37 +#define WLAN_REASON_UNKNOWN_TS_BA 38 +#define WLAN_REASON_TIMEOUT 39 +#define WLAN_REASON_PEERKEY_MISMATCH 45 +#define WLAN_REASON_AUTHORIZED_ACCESS_LIMIT_REACHED 46 +#define WLAN_REASON_EXTERNAL_SERVICE_REQUIREMENTS 47 +#define WLAN_REASON_INVALID_FT_ACTION_FRAME_COUNT 48 +#define WLAN_REASON_INVALID_PMKID 49 +#define WLAN_REASON_INVALID_MDE 50 +#define WLAN_REASON_INVALID_FTE 51 +#define WLAN_REASON_MESH_PEERING_CANCELLED 52 +#define WLAN_REASON_MESH_MAX_PEERS 53 +#define WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION 54 +#define WLAN_REASON_MESH_CLOSE_RCVD 55 +#define WLAN_REASON_MESH_MAX_RETRIES 56 +#define WLAN_REASON_MESH_CONFIRM_TIMEOUT 57 +#define WLAN_REASON_MESH_INVALID_GTK 58 +#define WLAN_REASON_MESH_INCONSISTENT_PARAMS 59 +#define WLAN_REASON_MESH_INVALID_SECURITY_CAP 60 +#define WLAN_REASON_MESH_PATH_ERROR_NO_PROXY_INFO 61 +#define WLAN_REASON_MESH_PATH_ERROR_NO_FORWARDING_INFO 62 +#define WLAN_REASON_MESH_PATH_ERROR_DEST_UNREACHABLE 63 +#define WLAN_REASON_MAC_ADDRESS_ALREADY_EXISTS_IN_MBSS 64 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_REGULATORY_REQ 65 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_UNSPECIFIED 66 diff --git a/src/platform/NuttX/args.gni b/src/platform/NuttX/args.gni new file mode 100644 index 00000000000000..48dfba5396a100 --- /dev/null +++ b/src/platform/NuttX/args.gni @@ -0,0 +1,15 @@ +# Copyright (c) 2020 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. + +chip_device_platform = "nuttx" diff --git a/src/platform/device.gni b/src/platform/device.gni index dbbc38abf4ca88..332b4b3ee85716 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -40,6 +40,8 @@ if (chip_device_platform == "auto") { chip_device_platform = "webos" } else if (current_os == "zephyr") { chip_device_platform = "zephyr" + } else if (current_os == "nuttx") { + chip_device_platform = "nuttx" } else { chip_device_platform = "none" } @@ -92,7 +94,8 @@ declare_args() { chip_device_platform == "ameba" || chip_device_platform == "webos" || chip_device_platform == "cc32xx" || chip_device_platform == "mw320" || chip_device_platform == "beken" || chip_device_platform == "mt793x" || - chip_device_platform == "asr" || chip_device_platform == "openiotsdk") { + chip_device_platform == "asr" || chip_device_platform == "openiotsdk" || + chip_device_platform == "nuttx") { chip_mdns = "minimal" } else if (chip_device_platform == "darwin" || chip_device_platform == "cc13x4_26x4" || current_os == "android" || @@ -106,7 +109,7 @@ declare_args() { # Enable Subscription persistence / resumption for CI and supported platforms if (chip_device_platform == "darwin" || chip_device_platform == "linux" || chip_device_platform == "fake" || chip_device_platform == "efr32" || - chip_device_platform == "SiWx917") { + chip_device_platform == "SiWx917" || chip_device_platform == "nuttx") { chip_persist_subscriptions = true } else { chip_persist_subscriptions = false @@ -189,6 +192,8 @@ if (chip_device_platform == "cc13x4_26x4") { _chip_device_layer = "openiotsdk" } else if (chip_device_platform == "asr") { _chip_device_layer = "ASR" +} else if (chip_device_platform == "nuttx") { + _chip_device_layer = "NuttX" } else if (chip_device_platform == "stm32") { _chip_device_layer = "stm32" } @@ -261,5 +266,5 @@ assert( chip_device_platform == "bl702l" || chip_device_platform == "mt793x" || chip_device_platform == "SiWx917" || chip_device_platform == "openiotsdk" || chip_device_platform == "asr" || - chip_device_platform == "stm32", + chip_device_platform == "stm32" || chip_device_platform == "nuttx", "Please select a valid value for chip_device_platform") From 11cb2b3da2a2d873d2416a7ffdc29e33c558176d Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 13 May 2024 09:13:06 -0400 Subject: [PATCH 03/10] Remove extra logging in minmdns resolver. (#33407) Resolver list is a fixed size always, so a message of "inactive" tagged as an error seems to just fill up logs (especially seen in python repl) Co-authored-by: Andrei Litvin --- src/lib/dnssd/Resolver_ImplMinimalMdns.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp b/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp index efcdececed8f7b..5d67c1de892a20 100644 --- a/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp +++ b/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp @@ -363,7 +363,6 @@ void MinMdnsResolver::AdvancePendingResolverStates() { if (!resolver->IsActive()) { - ChipLogError(Discovery, "resolver inactive, continue to next"); continue; } From add5b261ae6c07dcbb7a20771bc10d29cf594ba0 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 13 May 2024 15:16:56 +0200 Subject: [PATCH 04/10] [Python] Eliminate ZCLSubscribeAttribute (#33337) * Use asyncio sleep to unblock asyncio event loop * Avoid fixed sleep in TestCaseEviction Use asyncio Event and wait_for to wait for the change and continue immediately when received. * Make TestSubscription an async test The current test implementation starves the asyncio event loop by synchronously waiting for the threading.Condition. This prevents making the SubscriptionTransaction fully leveraging the async paradigm. It probably would be possible to mix asyncio.sleep() and threading, but instead embrace the async pradigm for this test. * Make TestSubscriptionResumption an async test The current test implementation starves the asyncio event loop by synchronously waiting for the threading.Condition. This prevents making the SubscriptionTransaction fully leveraging the async paradigm. It probably would be possible to mix asyncio.sleep() and threading, but instead embrace the async pradigm for this test. * Make TestSubscriptionResumptionCapacityStep1 an async test Eliminate use of ZCLSubscribeAttribute and embrace asyncio. * Make TestSubscriptionResumptionCapacityStep2 an async test Eliminate use of ZCLSubscribeAttribute and embrace asyncio. * Remove ZCLSubscribeAttribute from subscription_resumption_timeout_test Use ReadAttribute with asyncio in subscription_resumption_timeout_test as well. * Rewrite TestWriteBasicAttributes to drop ZCLRead/WriteAttribute * Improve wait for end of update task in TestSubscription --- .../python/test/test_scripts/base.py | 219 +++++++++--------- .../test/test_scripts/mobile-device-test.py | 9 +- ...cription_resumption_capacity_test_ctrl1.py | 5 +- ...cription_resumption_capacity_test_ctrl2.py | 6 +- .../subscription_resumption_test.py | 5 +- .../subscription_resumption_timeout_test.py | 10 +- 6 files changed, 128 insertions(+), 126 deletions(-) diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 6d4017a5b70ed8..9fc9300f4c8116 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -697,13 +697,13 @@ async def TestCaseEviction(self, nodeid: int): # on the sub we established previously. Since it was just marked defunct, it should return back to being # active and a report should get delivered. # - sawValueChange = False + sawValueChangeEvent = asyncio.Event() + loop = asyncio.get_running_loop() def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.SubscriptionTransaction) -> None: - nonlocal sawValueChange self.logger.info("Saw value change!") if (path.AttributeType == Clusters.UnitTesting.Attributes.Int8u and path.Path.EndpointId == 1): - sawValueChange = True + loop.call_soon_threadsafe(sawValueChangeEvent.set) self.logger.info("Testing CASE defunct logic") @@ -720,14 +720,15 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub # was received. # await self.devCtrl2.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(4))]) - time.sleep(2) - sub.Shutdown() - - if sawValueChange is False: + try: + await asyncio.wait_for(sawValueChangeEvent.wait(), 2) + except TimeoutError: self.logger.error( "Didn't see value change in time, likely because sub got terminated due to unexpected session eviction!") return False + finally: + sub.Shutdown() # # In this test, we're going to setup a subscription on fabric1 through devCtl, then, constantly keep @@ -739,7 +740,7 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub # self.logger.info("Testing fabric-isolated CASE eviction") - sawValueChange = False + sawValueChangeEvent.clear() sub = await self.devCtrl.ReadAttribute(nodeid, [(Clusters.UnitTesting.Attributes.Int8u)], reportInterval=(0, 1)) sub.SetAttributeUpdateCallback(OnValueChange) @@ -752,13 +753,14 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub # was received. Use a different value from before, so there is an actual change. # await self.devCtrl2.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(5))]) - time.sleep(2) - - sub.Shutdown() - if sawValueChange is False: + try: + await asyncio.wait_for(sawValueChangeEvent.wait(), 2) + except TimeoutError: self.logger.error("Didn't see value change in time, likely because sub got terminated due to other fabric (fabric1)") return False + finally: + sub.Shutdown() # # Do the same test again, but reversing the roles of fabric1 and fabric2. And again @@ -766,7 +768,7 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub # self.logger.info("Testing fabric-isolated CASE eviction (reverse)") - sawValueChange = False + sawValueChangeEvent.clear() sub = await self.devCtrl2.ReadAttribute(nodeid, [(Clusters.UnitTesting.Attributes.Int8u)], reportInterval=(0, 1)) sub.SetAttributeUpdateCallback(OnValueChange) @@ -775,13 +777,13 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub await self.devCtrl.ReadAttribute(nodeid, [(Clusters.BasicInformation.Attributes.ClusterRevision)]) await self.devCtrl.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(6))]) - time.sleep(2) - - sub.Shutdown() - - if sawValueChange is False: + try: + await asyncio.wait_for(sawValueChangeEvent.wait(), 2) + except TimeoutError: self.logger.error("Didn't see value change in time, likely because sub got terminated due to other fabric (fabric2)") return False + finally: + sub.Shutdown() return True @@ -1199,121 +1201,114 @@ def TestReadBasicAttributes(self, nodeid: int, endpoint: int, group: int): return False return True - def TestWriteBasicAttributes(self, nodeid: int, endpoint: int, group: int): + async def TestWriteBasicAttributes(self, nodeid: int, endpoint: int): @ dataclass class AttributeWriteRequest: - cluster: str - attribute: str + cluster: Clusters.ClusterObjects.Cluster + attribute: Clusters.ClusterObjects.ClusterAttributeDescriptor value: Any expected_status: IM.Status = IM.Status.Success requests = [ - AttributeWriteRequest("BasicInformation", "NodeLabel", "Test"), - AttributeWriteRequest("BasicInformation", "Location", + AttributeWriteRequest(Clusters.BasicInformation, Clusters.BasicInformation.Attributes.NodeLabel, "Test"), + AttributeWriteRequest(Clusters.BasicInformation, Clusters.BasicInformation.Attributes.Location, "a pretty loooooooooooooog string", IM.Status.ConstraintError), ] - failed_zcl = [] + failed_attribute_write = [] for req in requests: try: try: - self.devCtrl.ZCLWriteAttribute(cluster=req.cluster, - attribute=req.attribute, - nodeid=nodeid, - endpoint=endpoint, - groupid=group, - value=req.value) + await self.devCtrl.WriteAttribute(nodeid, [(endpoint, req.attribute, 0)]) if req.expected_status != IM.Status.Success: raise AssertionError( - f"Write attribute {req.cluster}.{req.attribute} expects failure but got success response") + f"Write attribute {req.attribute.__qualname__} expects failure but got success response") except Exception as ex: if req.expected_status != IM.Status.Success: continue else: raise ex - res = self.devCtrl.ZCLReadAttribute( - cluster=req.cluster, attribute=req.attribute, nodeid=nodeid, endpoint=endpoint, groupid=group) - TestResult(f"Read attribute {req.cluster}.{req.attribute}", res).assertValueEqual( - req.value) + + res = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, req.attribute)]) + val = res[endpoint][req.cluster][req.attribute] + if val != req.value: + raise Exception( + f"Read attribute {req.attribute.__qualname__}: expected value {req.value}, got {val}") except Exception as ex: - failed_zcl.append(str(ex)) - if failed_zcl: - self.logger.exception(f"Following attributes failed: {failed_zcl}") + failed_attribute_write.append(str(ex)) + if failed_attribute_write: + self.logger.exception(f"Following attributes failed: {failed_attribute_write}") return False return True - def TestSubscription(self, nodeid: int, endpoint: int): + async def TestSubscription(self, nodeid: int, endpoint: int): desiredPath = None receivedUpdate = 0 - updateLock = threading.Lock() - updateCv = threading.Condition(updateLock) + updateEvent = asyncio.Event() + loop = asyncio.get_running_loop() def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.SubscriptionTransaction) -> None: - nonlocal desiredPath, updateCv, updateLock, receivedUpdate + nonlocal desiredPath, updateEvent, receivedUpdate if path.Path != desiredPath: return data = transaction.GetAttribute(path) logger.info( f"Received report from server: path: {path.Path}, value: {data}") - with updateLock: - receivedUpdate += 1 - updateCv.notify_all() - - class _conductAttributeChange(threading.Thread): - def __init__(self, devCtrl: ChipDeviceCtrl.ChipDeviceController, nodeid: int, endpoint: int): - super(_conductAttributeChange, self).__init__() - self.nodeid = nodeid - self.endpoint = endpoint - self.devCtrl = devCtrl - - def run(self): - for i in range(5): - time.sleep(3) - self.devCtrl.ZCLSend( - "OnOff", "Toggle", self.nodeid, self.endpoint, 0, {}) + receivedUpdate += 1 + loop.call_soon_threadsafe(updateEvent.set) + + async def _conductAttributeChange(devCtrl: ChipDeviceCtrl.ChipDeviceController, nodeid: int, endpoint: int): + for i in range(5): + await asyncio.sleep(3) + await self.devCtrl.SendCommand(nodeid, endpoint, Clusters.OnOff.Commands.Toggle()) try: desiredPath = Clusters.Attribute.AttributePath( EndpointId=1, ClusterId=6, AttributeId=0) # OnOff Cluster, OnOff Attribute - subscription = self.devCtrl.ZCLSubscribeAttribute( - "OnOff", "OnOff", nodeid, endpoint, 1, 10) + subscription = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, Clusters.OnOff.Attributes.OnOff)], None, False, reportInterval=(1, 10), + keepSubscriptions=False, autoResubscribe=True) subscription.SetAttributeUpdateCallback(OnValueChange) - changeThread = _conductAttributeChange( - self.devCtrl, nodeid, endpoint) # Reset the number of subscriptions received as subscribing causes a callback. - changeThread.start() - with updateCv: - while receivedUpdate < 5: - # We should observe 5 attribute changes - # The changing thread will change the value after 3 seconds. If we're waiting more than 10, assume something - # is really wrong and bail out here with some information. - if not updateCv.wait(10.0): - self.logger.error( - "Failed to receive subscription update") - break - - # thread changes 5 times, and sleeps for 3 seconds in between. - # Add an additional 3 seconds of slack. Timeout is in seconds. - changeThread.join(18.0) + taskAttributeChange = loop.create_task(_conductAttributeChange(self.devCtrl, nodeid, endpoint)) - # - # Clean-up by shutting down the sub. Otherwise, we're going to get callbacks through - # OnValueChange on what will soon become an invalid - # execution context above. - # - subscription.Shutdown() + while receivedUpdate < 5: + # We should observe 5 attribute changes + # The changing thread will change the value after 3 seconds. If we're waiting more than 10, assume something + # is really wrong and bail out here with some information. + try: + await asyncio.wait_for(updateEvent.wait(), 10) + updateEvent.clear() + except TimeoutError: + self.logger.error( + "Failed to receive subscription update") + break - if changeThread.is_alive(): - # Thread join timed out - self.logger.error("Failed to join change thread") - return False + # At this point the task should really have done the three attribute, + # otherwise something is wrong. Wait for just 1s in case of a race + # condition between the last attribute update and the callback. + try: + await asyncio.wait_for(taskAttributeChange, 1) + except asyncio.TimeoutError: + # If attribute change task did not finish something is wrong. Cancel + # the task. + taskAttributeChange.cancel() + # This will throw a asyncio.CancelledError and makes sure the test + # is declared failed. + await taskAttributeChange return True if receivedUpdate == 5 else False except Exception as ex: self.logger.exception(f"Failed to finish API test: {ex}") return False + finally: + # + # Clean-up by shutting down the sub. Otherwise, we're going to get callbacks through + # OnValueChange on what will soon become an invalid + # execution context above. + # + subscription.Shutdown() return True @@ -1347,7 +1342,7 @@ def TestFabricScopedCommandDuringPase(self, nodeid: int): return status == IM.Status.UnsupportedAccess - def TestSubscriptionResumption(self, nodeid: int, endpoint: int, remote_ip: str, ssh_port: int, remote_server_app: str): + async def TestSubscriptionResumption(self, nodeid: int, endpoint: int, remote_ip: str, ssh_port: int, remote_server_app: str): ''' This test validates that the device can resume the subscriptions after restarting. It is executed in Linux Cirque tests and the steps of this test are: @@ -1356,42 +1351,40 @@ def TestSubscriptionResumption(self, nodeid: int, endpoint: int, remote_ip: str, 3. Validate that the controller can receive a report from the remote server app ''' desiredPath = None - receivedUpdate = False - updateLock = threading.Lock() - updateCv = threading.Condition(updateLock) + updateEvent = asyncio.Event() + loop = asyncio.get_running_loop() def OnValueReport(path: Attribute.TypedAttributePath, transaction: Attribute.SubscriptionTransaction) -> None: - nonlocal desiredPath, updateCv, updateLock, receivedUpdate + nonlocal desiredPath, updateEvent, receivedUpdate if path.Path != desiredPath: return data = transaction.GetAttribute(path) logger.info( f"Received report from server: path: {path.Path}, value: {data}") - with updateLock: - receivedUpdate = True - updateCv.notify_all() + loop.call_soon_threadsafe(updateEvent.set) try: desiredPath = Clusters.Attribute.AttributePath( EndpointId=0, ClusterId=0x28, AttributeId=5) # BasicInformation Cluster, NodeLabel Attribute - subscription = self.devCtrl.ZCLSubscribeAttribute( - "BasicInformation", "NodeLabel", nodeid, endpoint, 1, 50, keepSubscriptions=True, autoResubscribe=False) + subscription = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, Clusters.BasicInformation.Attributes.NodeLabel)], None, False, reportInterval=(1, 50), + keepSubscriptions=True, autoResubscribe=False) subscription.SetAttributeUpdateCallback(OnValueReport) - self.logger.info("Restart remote deivce") + self.logger.info("Restart remote device") restartRemoteThread = restartRemoteDevice( remote_ip, ssh_port, "root", "admin", remote_server_app, "--thread --discriminator 3840") restartRemoteThread.start() # After device restarts, the attribute will be set dirty so the subscription can receive # the update - with updateCv: - while receivedUpdate is False: - if not updateCv.wait(10.0): - self.logger.error( - "Failed to receive subscription resumption report") - break + receivedUpdate = False + try: + await asyncio.wait_for(updateEvent.wait(), 10) + receivedUpdate = True + except TimeoutError: + self.logger.error( + "Failed to receive subscription resumption report") restartRemoteThread.join(10.0) @@ -1438,25 +1431,26 @@ def OnValueReport(path: Attribute.TypedAttributePath, transaction: Attribute.Sub controller 1 in container 1 while the Step2 is executed in controller 2 in container 2 ''' - def TestSubscriptionResumptionCapacityStep1(self, nodeid: int, endpoint: int, passcode: int, subscription_capacity: int): + async def TestSubscriptionResumptionCapacityStep1(self, nodeid: int, endpoint: int, passcode: int, subscription_capacity: int): try: # BasicInformation Cluster, NodeLabel Attribute for i in range(subscription_capacity): - self.devCtrl.ZCLSubscribeAttribute( - "BasicInformation", "NodeLabel", nodeid, endpoint, 1, 50, keepSubscriptions=True, autoResubscribe=False) + await self.devCtrl.ReadAttribute(nodeid, [(endpoint, Clusters.BasicInformation.Attributes.NodeLabel)], None, + False, reportInterval=(1, 50), + keepSubscriptions=True, autoResubscribe=False) logger.info("Send OpenCommissioningWindow command on fist controller") discriminator = 3840 salt = secrets.token_bytes(16) iterations = 2000 verifier = GenerateVerifier(passcode, salt, iterations) - asyncio.run(self.devCtrl.SendCommand( + await self.devCtrl.SendCommand( nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow( commissioningTimeout=180, PAKEPasscodeVerifier=verifier, discriminator=discriminator, iterations=iterations, - salt=salt), timedRequestTimeoutMs=10000)) + salt=salt), timedRequestTimeoutMs=10000) return True except Exception as ex: @@ -1465,8 +1459,8 @@ def TestSubscriptionResumptionCapacityStep1(self, nodeid: int, endpoint: int, pa return True - def TestSubscriptionResumptionCapacityStep2(self, nodeid: int, endpoint: int, remote_ip: str, ssh_port: int, - remote_server_app: str, subscription_capacity: int): + async def TestSubscriptionResumptionCapacityStep2(self, nodeid: int, endpoint: int, remote_ip: str, ssh_port: int, + remote_server_app: str, subscription_capacity: int): try: self.logger.info("Restart remote deivce") extra_agrs = f"--thread --discriminator 3840 --subscription-capacity {subscription_capacity}" @@ -1480,8 +1474,9 @@ def TestSubscriptionResumptionCapacityStep2(self, nodeid: int, endpoint: int, re self.logger.info("Send a new subscription request from the second controller") # Close previous session so that the second controller will res-establish the session with the remote device self.devCtrl.CloseSession(nodeid) - self.devCtrl.ZCLSubscribeAttribute( - "BasicInformation", "NodeLabel", nodeid, endpoint, 1, 50, keepSubscriptions=True, autoResubscribe=False) + await self.devCtrl.ReadAttribute(nodeid, [(endpoint, Clusters.BasicInformation.Attributes.NodeLabel)], None, + False, reportInterval=(1, 50), + keepSubscriptions=True, autoResubscribe=False) if restartRemoteThread.is_alive(): # Thread join timed out diff --git a/src/controller/python/test/test_scripts/mobile-device-test.py b/src/controller/python/test/test_scripts/mobile-device-test.py index 9ceaa35d24c291..33ae713fe02cb2 100755 --- a/src/controller/python/test/test_scripts/mobile-device-test.py +++ b/src/controller/python/test/test_scripts/mobile-device-test.py @@ -129,9 +129,8 @@ def TestDatamodel(test: BaseTestHelper, device_nodeid: int): "Failed to test Read Basic Attributes") logger.info("Testing attribute writing") - FailIfNot(test.TestWriteBasicAttributes(nodeid=device_nodeid, - endpoint=ENDPOINT_ID, - group=GROUP_ID), + FailIfNot(asyncio.run(test.TestWriteBasicAttributes(nodeid=device_nodeid, + endpoint=ENDPOINT_ID)), "Failed to test Write Basic Attributes") logger.info("Testing attribute reading basic again") @@ -141,11 +140,11 @@ def TestDatamodel(test: BaseTestHelper, device_nodeid: int): "Failed to test Read Basic Attributes") logger.info("Testing subscription") - FailIfNot(test.TestSubscription(nodeid=device_nodeid, endpoint=LIGHTING_ENDPOINT_ID), + FailIfNot(asyncio.run(test.TestSubscription(nodeid=device_nodeid, endpoint=LIGHTING_ENDPOINT_ID)), "Failed to subscribe attributes.") logger.info("Testing another subscription that kills previous subscriptions") - FailIfNot(test.TestSubscription(nodeid=device_nodeid, endpoint=LIGHTING_ENDPOINT_ID), + FailIfNot(asyncio.run(test.TestSubscription(nodeid=device_nodeid, endpoint=LIGHTING_ENDPOINT_ID)), "Failed to subscribe attributes.") logger.info("Testing re-subscription") diff --git a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py index 19065b8a35396a..e02564e293c04a 100755 --- a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py +++ b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py @@ -19,6 +19,7 @@ # Commissioning test. +import asyncio import os import sys from optparse import OptionParser @@ -113,8 +114,8 @@ def main(): "Failed on on-network commissioing") FailIfNot( - test.TestSubscriptionResumptionCapacityStep1( - options.nodeid, TEST_ENDPOINT_ID, options.setuppin, options.subscriptionCapacity), + asyncio.run(test.TestSubscriptionResumptionCapacityStep1( + options.nodeid, TEST_ENDPOINT_ID, options.setuppin, options.subscriptionCapacity)), "Failed on step 1 of testing subscription resumption capacity") timeoutTicker.stop() diff --git a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py index 2f3058afcd3bca..ac449a9f5478ac 100755 --- a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py +++ b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py @@ -19,6 +19,7 @@ # Commissioning test. +import asyncio import os import sys from optparse import OptionParser @@ -125,8 +126,9 @@ def main(): "Failed on on-network commissioing") FailIfNot( - test.TestSubscriptionResumptionCapacityStep2(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, - TEST_SSH_PORT, options.remoteServerApp, options.subscriptionCapacity), + asyncio.run( + test.TestSubscriptionResumptionCapacityStep2(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, + TEST_SSH_PORT, options.remoteServerApp, options.subscriptionCapacity)), "Failed on testing subscription resumption capacity") timeoutTicker.stop() diff --git a/src/controller/python/test/test_scripts/subscription_resumption_test.py b/src/controller/python/test/test_scripts/subscription_resumption_test.py index 8b2000fb070cd7..79edf6a2898d0e 100755 --- a/src/controller/python/test/test_scripts/subscription_resumption_test.py +++ b/src/controller/python/test/test_scripts/subscription_resumption_test.py @@ -19,6 +19,7 @@ # Commissioning test. +import asyncio import os import sys from optparse import OptionParser @@ -115,8 +116,8 @@ def main(): "Failed on on-network commissioing") FailIfNot( - test.TestSubscriptionResumption(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, - TEST_SSH_PORT, options.remoteServerApp), "Failed to resume subscription") + asyncio.run(test.TestSubscriptionResumption(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, + TEST_SSH_PORT, options.remoteServerApp)), "Failed to resume subscription") timeoutTicker.stop() diff --git a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py index 1f6411f63699c3..4932e5b4cc0582 100755 --- a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py +++ b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py @@ -19,11 +19,13 @@ # Commissioning test. +import asyncio import os import sys from optparse import OptionParser from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger +from chip import clusters as Clusters TEST_DISCRIMINATOR = 3840 TEST_SETUPPIN = 20202021 @@ -101,10 +103,12 @@ def main(): FailIfNot( test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), - "Failed on on-network commissioing") + "Failed on on-network commissioning") + try: - test.devCtrl.ZCLSubscribeAttribute("BasicInformation", "NodeLabel", options.nodeid, TEST_ENDPOINT_ID, 1, 2, - keepSubscriptions=True, autoResubscribe=False) + asyncio.run(test.devCtrl.ReadAttribute(options.nodeid, + [(TEST_ENDPOINT_ID, Clusters.BasicInformation.Attributes.NodeLabel)], + None, False, reportInterval=(1, 2), keepSubscriptions=True, autoResubscribe=False)) except Exception as ex: TestFail(f"Failed to subscribe attribute: {ex}") From 6be6e5b61aa9fe03f8012a90fab9d30de37ae204 Mon Sep 17 00:00:00 2001 From: feasel <120589145+feasel0@users.noreply.github.com> Date: Mon, 13 May 2024 10:45:51 -0400 Subject: [PATCH 05/10] Converted unit tests in src/controller/tests/data_model from NL to PW. (#33371) * Converted src/controller/tests/data_model from NL to PW. * Updated to accomodate the fact that AppContext and LoopbackMessagingContext now have SetUp/SetUpTestSuite returning void. * Comment cleanup. * Reordered tests to fix dependency. * Formatting * Formatting * Added comment warning of order dependence. * Formatting * Removed unneeded conditions from SetUpTestSuite/TearDownTestSuite. --- src/controller/tests/data_model/BUILD.gn | 6 +- .../tests/data_model/TestCommands.cpp | 209 +- src/controller/tests/data_model/TestRead.cpp | 2654 ++++++++--------- src/controller/tests/data_model/TestWrite.cpp | 179 +- .../openiotsdk/unit-tests/test_components.txt | 4 +- .../unit-tests/test_components_nl.txt | 1 - 6 files changed, 1423 insertions(+), 1630 deletions(-) diff --git a/src/controller/tests/data_model/BUILD.gn b/src/controller/tests/data_model/BUILD.gn index e7621af5b0359a..017980c4cd11f7 100644 --- a/src/controller/tests/data_model/BUILD.gn +++ b/src/controller/tests/data_model/BUILD.gn @@ -14,12 +14,12 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") -import("//build_overrides/nlunit_test.gni") +import("//build_overrides/pigweed.gni") import("${chip_root}/build/chip/chip_test_suite.gni") import("${chip_root}/src/platform/device.gni") -chip_test_suite_using_nltest("data_model") { +chip_test_suite("data_model") { output_name = "libDataModelTests" if (chip_device_platform != "mbed" && chip_device_platform != "efr32" && @@ -35,9 +35,7 @@ chip_test_suite_using_nltest("data_model") { "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/controller", - "${chip_root}/src/lib/support:testing_nlunit", "${chip_root}/src/messaging/tests:helpers", "${chip_root}/src/transport/raw/tests:helpers", - "${nlunit_test_root}:nlunit-test", ] } diff --git a/src/controller/tests/data_model/TestCommands.cpp b/src/controller/tests/data_model/TestCommands.cpp index 7c585bf44b26f6..adc6bd0cf7c9d0 100644 --- a/src/controller/tests/data_model/TestCommands.cpp +++ b/src/controller/tests/data_model/TestCommands.cpp @@ -22,6 +22,8 @@ * */ +#include + #include "app/data-model/NullObject.h" #include #include @@ -32,11 +34,8 @@ #include #include #include -#include -#include #include #include -#include #include using TestContext = chip::Test::AppContext; @@ -180,25 +179,37 @@ InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & namespace { -class TestCommandInteraction +class TestCommands : public ::testing::Test { public: - TestCommandInteraction() {} - static void TestDataResponse(nlTestSuite * apSuite, void * apContext); - static void TestSuccessNoDataResponse(nlTestSuite * apSuite, void * apContext); - static void TestMultipleSuccessNoDataResponses(nlTestSuite * apSuite, void * apContext); - static void TestAsyncResponse(nlTestSuite * apSuite, void * apContext); - static void TestFailure(nlTestSuite * apSuite, void * apContext); - static void TestMultipleFailures(nlTestSuite * apSuite, void * apContext); - static void TestSuccessNoDataResponseWithClusterStatus(nlTestSuite * apSuite, void * apContext); - static void TestFailureWithClusterStatus(nlTestSuite * apSuite, void * apContext); - -private: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + delete mpContext; + } + +protected: + // Performs setup for each individual test in the test suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each individual test in the test suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; }; +TestContext * TestCommands::mpContext = nullptr; -void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestDataResponse) { - TestContext & ctx = *static_cast(apContext); // We want to send a TestSimpleArgumentRequest::Type, but get a // TestStructArrayArgumentResponse in return, so need to shadow the actual // ResponseType that TestSimpleArgumentRequest has. @@ -208,7 +219,7 @@ void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apCo }; FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -217,23 +228,23 @@ void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apCo // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, - const auto & dataResponse) { + auto onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, + const auto & dataResponse) { uint8_t i = 0; auto iter = dataResponse.arg1.begin(); while (iter.Next()) { auto & item = iter.GetValue(); - NL_TEST_ASSERT(apSuite, item.a == i); - NL_TEST_ASSERT(apSuite, item.b == false); - NL_TEST_ASSERT(apSuite, item.c.a == i); - NL_TEST_ASSERT(apSuite, item.c.b == true); + EXPECT_EQ(item.a, i); + EXPECT_FALSE(item.b); + EXPECT_EQ(item.c.a, i); + EXPECT_TRUE(item.c.b); i++; } - NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, dataResponse.arg6 == true); + EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR); + EXPECT_TRUE(dataResponse.arg6); onSuccessWasCalled = true; }; @@ -244,25 +255,24 @@ void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apCo responseDirective = kSendDataResponse; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestSuccessNoDataResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestSuccessNoDataResponse) { struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type { using ResponseType = DataModel::NullObjectType; }; - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -283,25 +293,24 @@ void TestCommandInteraction::TestSuccessNoDataResponse(nlTestSuite * apSuite, vo responseDirective = kSendSuccessStatusCode; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestMultipleSuccessNoDataResponses(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestMultipleSuccessNoDataResponses) { struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type { using ResponseType = DataModel::NullObjectType; }; - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -322,25 +331,25 @@ void TestCommandInteraction::TestMultipleSuccessNoDataResponses(nlTestSuite * ap responseDirective = kSendMultipleSuccessStatusCodes; - Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); + Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 1 && statusCheck); - NL_TEST_ASSERT(apSuite, failureCalls == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(successCalls == 1 && statusCheck); + EXPECT_EQ(failureCalls, 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestAsyncResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestAsyncResponse) { struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type { using ResponseType = DataModel::NullObjectType; }; - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -361,37 +370,31 @@ void TestCommandInteraction::TestAsyncResponse(nlTestSuite * apSuite, void * apC responseDirective = kAsync; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessWasCalled && !onFailureWasCalled && !statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 2); + EXPECT_TRUE(!onSuccessWasCalled && !onFailureWasCalled && !statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 2u); CommandHandler * commandHandle = asyncHandle.Get(); - NL_TEST_ASSERT(apSuite, commandHandle != nullptr); - - if (commandHandle == nullptr) - { - return; - } + ASSERT_NE(commandHandle, nullptr); commandHandle->AddStatus(ConcreteCommandPath(kTestEndpointId, request.GetClusterId(), request.GetCommandId()), Protocols::InteractionModel::Status::Success); asyncHandle.Release(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestFailure(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestFailure) { - TestContext & ctx = *static_cast(apContext); Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -412,25 +415,24 @@ void TestCommandInteraction::TestFailure(nlTestSuite * apSuite, void * apContext responseDirective = kSendError; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessWasCalled && onFailureWasCalled && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessWasCalled && onFailureWasCalled && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestMultipleFailures(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestMultipleFailures) { struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type { using ResponseType = DataModel::NullObjectType; }; - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -451,25 +453,25 @@ void TestCommandInteraction::TestMultipleFailures(nlTestSuite * apSuite, void * responseDirective = kSendMultipleErrors; - Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); + Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 0); - NL_TEST_ASSERT(apSuite, failureCalls == 1 && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 0u); + EXPECT_TRUE(failureCalls == 1 && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestSuccessNoDataResponseWithClusterStatus(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestSuccessNoDataResponseWithClusterStatus) { struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type { using ResponseType = DataModel::NullObjectType; }; - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -491,20 +493,19 @@ void TestCommandInteraction::TestSuccessNoDataResponseWithClusterStatus(nlTestSu responseDirective = kSendSuccessStatusCodeWithClusterStatus; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestCommandInteraction::TestFailureWithClusterStatus(nlTestSuite * apSuite, void * apContext) +TEST_F(TestCommands, TestFailureWithClusterStatus) { - TestContext & ctx = *static_cast(apContext); Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -531,41 +532,13 @@ void TestCommandInteraction::TestFailureWithClusterStatus(nlTestSuite * apSuite, responseDirective = kSendErrorWithClusterStatus; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessWasCalled && onFailureWasCalled && statusCheck); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessWasCalled && onFailureWasCalled && statusCheck); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -const nlTest sTests[] = { - NL_TEST_DEF("TestDataResponse", TestCommandInteraction::TestDataResponse), - NL_TEST_DEF("TestSuccessNoDataResponse", TestCommandInteraction::TestSuccessNoDataResponse), - NL_TEST_DEF("TestMultipleSuccessNoDataResponses", TestCommandInteraction::TestMultipleSuccessNoDataResponses), - NL_TEST_DEF("TestAsyncResponse", TestCommandInteraction::TestAsyncResponse), - NL_TEST_DEF("TestFailure", TestCommandInteraction::TestFailure), - NL_TEST_DEF("TestMultipleFailures", TestCommandInteraction::TestMultipleFailures), - NL_TEST_DEF("TestSuccessNoDataResponseWithClusterStatus", TestCommandInteraction::TestSuccessNoDataResponseWithClusterStatus), - NL_TEST_DEF("TestFailureWithClusterStatus", TestCommandInteraction::TestFailureWithClusterStatus), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestCommands", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestCommandInteractionTest() -{ - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestCommandInteractionTest) diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index bcf0233662d2bc..a27eb61fcb2901 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "system/SystemClock.h" #include "transport/SecureSession.h" #include @@ -28,11 +30,8 @@ #include #include #include -#include -#include #include #include -#include #include using TestContext = chip::Test::AppContext; @@ -259,56 +258,40 @@ Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventP namespace { -class TestReadInteraction : public app::ReadHandler::ApplicationCallback +class TestRead : public ::testing::Test, public app::ReadHandler::ApplicationCallback { public: - TestReadInteraction() {} - - static void TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext); - static void TestReadAttributeError(nlTestSuite * apSuite, void * apContext); - static void TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext); - static void TestSubscribeAttributeTimeout(nlTestSuite * apSuite, void * apContext); - static void TestResubscribeAttributeTimeout(nlTestSuite * apSuite, void * apContext); - static void TestReadEventResponse(nlTestSuite * apSuite, void * apContext); - static void TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext); - static void TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionAppRejection(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_MultipleReads(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_OneSubscribeMultipleReads(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext); -#if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 - static void TestReadHandler_SubscriptionReportingIntervalsTest1(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionReportingIntervalsTest2(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionReportingIntervalsTest3(nlTestSuite * apSuite, void * apContext); -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER - static void TestReadHandler_SubscriptionReportingIntervalsTest4(nlTestSuite * apSuite, void * apContext); -#if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 - static void TestReadHandler_SubscriptionReportingIntervalsTest5(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionReportingIntervalsTest6(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionReportingIntervalsTest7(nlTestSuite * apSuite, void * apContext); -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER - static void TestReadHandler_SubscriptionReportingIntervalsTest8(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionReportingIntervalsTest9(nlTestSuite * apSuite, void * apContext); - static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext); - static void TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext); - static void TestReadSubscribeAttributeResponseWithVersionOnlyCache(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_KillOldestSubscriptions(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_ParallelReads(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_TooManyPaths(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_TwoParallelReadsSecondTooManyPaths(nlTestSuite * apSuite, void * apContext); - static void TestReadAttribute_ManyDataValues(nlTestSuite * apSuite, void * apContext); - static void TestReadAttribute_ManyDataValuesWrongPath(nlTestSuite * apSuite, void * apContext); - static void TestReadAttribute_ManyErrors(nlTestSuite * apSuite, void * apContext); - static void TestSubscribeAttributeDeniedNotExistPath(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_KeepSubscriptionTest(nlTestSuite * apSuite, void * apContext); - static void TestSubscribe_OnActiveModeNotification(nlTestSuite * apSuite, void * apContext); - static void TestSubscribe_ImmediatelyResubscriptionForLIT(nlTestSuite * apSuite, void * apContext); - static void TestSubscribe_DynamicLITSubscription(nlTestSuite * apSuite, void * apContext); - -private: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each individual test in the test suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each individual test in the test suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; + static uint16_t mMaxInterval; CHIP_ERROR OnSubscriptionRequested(app::ReadHandler & aReadHandler, Transport::SecureSession & aSecureSession) @@ -328,16 +311,16 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback // Issue the given number of reads in parallel and wait for them all to // succeed. - static void MultipleReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount); + static void MultipleReadHelper(TestContext * apCtx, size_t aReadCount); // Helper for MultipleReadHelper that does not spin the event loop, so we // don't end up with nested event loops. - static void MultipleReadHelperInternal(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount, - uint32_t & aNumSuccessCalls, uint32_t & aNumFailureCalls); + static void MultipleReadHelperInternal(TestContext * apCtx, size_t aReadCount, uint32_t & aNumSuccessCalls, + uint32_t & aNumFailureCalls); // Establish the given number of subscriptions, then issue the given number // of reads in parallel and wait for them all to succeed. - static void SubscribeThenReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aSubscribeCount, size_t aReadCount); + static void SubscribeThenReadHelper(TestContext * apCtx, size_t aSubscribeCount, size_t aReadCount); // Compute the amount of time it would take a subscription with a given // max-interval to time out. @@ -348,9 +331,8 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback bool mAlterSubscriptionIntervals = false; }; -uint16_t TestReadInteraction::mMaxInterval = 66; - -TestReadInteraction gTestReadInteraction; +TestContext * TestRead::mpContext = nullptr; +uint16_t TestRead::mMaxInterval = 66; class MockInteractionModelApp : public chip::app::ClusterStateCache::Callback { @@ -401,29 +383,27 @@ class MockInteractionModelApp : public chip::app::ClusterStateCache::Callback CHIP_ERROR mError = CHIP_NO_ERROR; }; -void TestReadInteraction::TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttributeResponse) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; responseDirective = kSendDataResponse; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, - const auto & dataResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { uint8_t i = 0; - NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); + EXPECT_TRUE(attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); auto iter = dataResponse.begin(); while (iter.Next()) { auto & item = iter.GetValue(); - NL_TEST_ASSERT(apSuite, item.member1 == i); + EXPECT_EQ(item.member1, i); i++; } - NL_TEST_ASSERT(apSuite, i == 4); - NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); + EXPECT_EQ(i, 4u); + EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR); onSuccessCbInvoked = true; }; @@ -434,19 +414,92 @@ void TestReadInteraction::TestReadAttributeResponse(nlTestSuite * apSuite, void }; Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext) +// NOTE: This test must execute before TestReadSubscribeAttributeResponseWithCache or else it will fail on +// `EXPECT_TRUE(version1.HasValue() && (version1.Value() == 0))`. +TEST_F(TestRead, TestReadSubscribeAttributeResponseWithVersionOnlyCache) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + responseDirective = kSendDataResponse; + + MockInteractionModelApp delegate; + chip::app::ClusterStateCache cache(delegate, Optional::Missing(), false /*cachedData*/); + + chip::app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); + + // read of E2C2A* and E3C2A2. Expect cache E2C2 version + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), + cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); + chip::app::AttributePathParams attributePathParams2[2]; + attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; + attributePathParams2[0].mClusterId = chip::Test::MockClusterId(3); + attributePathParams2[0].mAttributeId = kInvalidAttributeId; + + attributePathParams2[1].mEndpointId = chip::Test::kMockEndpoint3; + attributePathParams2[1].mClusterId = chip::Test::MockClusterId(2); + attributePathParams2[1].mAttributeId = chip::Test::MockAttributeId(2); + readPrepareParams.mpAttributePathParamsList = attributePathParams2; + readPrepareParams.mAttributePathParamsListSize = 2; + err = readClient.SendRequest(readPrepareParams); + EXPECT_EQ(err, CHIP_NO_ERROR); + + mpContext->DrainAndServiceIO(); + // There are supported 2 global and 3 non-global attributes in E2C2A* and 1 E3C2A2 + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); + Optional version1; + app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 0)); + Optional version2; + app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); + + { + app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), + chip::Test::MockAttributeId(2)); + TLV::TLVReader reader; + EXPECT_NE(cache.Get(attributePath, reader), CHIP_NO_ERROR); + } + + { + app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), + chip::Test::MockAttributeId(3)); + TLV::TLVReader reader; + EXPECT_NE(cache.Get(attributePath, reader), CHIP_NO_ERROR); + } + + { + app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), + chip::Test::MockAttributeId(2)); + TLV::TLVReader reader; + EXPECT_NE(cache.Get(attributePath, reader), CHIP_NO_ERROR); + } + delegate.mNumAttributeResponse = 0; + } + + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); +} + +TEST_F(TestRead, TestReadSubscribeAttributeResponseWithCache) { - TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; responseDirective = kSendDataResponse; @@ -461,14 +514,14 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit eventPathParam.mEventId = 0; } - chip::app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 4; // // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); [[maybe_unused]] int testId = 0; @@ -477,7 +530,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -495,15 +548,15 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_FALSE(version1.HasValue()); delegate.mNumAttributeResponse = 0; } @@ -512,7 +565,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -530,48 +583,48 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 3); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_FALSE(version1.HasValue()); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; @@ -583,7 +636,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = kInvalidEndpointId; @@ -601,45 +654,45 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 2); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_FALSE(version1.HasValue()); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint1, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) != CHIP_NO_ERROR); + EXPECT_NE(cache.Get(attributePath, reader), CHIP_NO_ERROR); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -649,7 +702,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams2[2]; attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -662,59 +715,59 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams2; readPrepareParams.mAttributePathParamsListSize = 2; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // There are supported 2 global and 3 non-global attributes in E2C2A* and 1 E3C2A2 - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 0)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -724,7 +777,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -742,48 +795,48 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 1); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 0)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -793,7 +846,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams2[2]; attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -806,58 +859,58 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams2; readPrepareParams.mAttributePathParamsListSize = 2; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 1); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 0)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -870,7 +923,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -888,48 +941,48 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 3); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_FALSE(version1.HasValue()); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -939,7 +992,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[3]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -957,48 +1010,48 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 3); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_FALSE(version1.HasValue()); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -1008,7 +1061,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams2[2]; attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -1021,58 +1074,58 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams2; readPrepareParams.mAttributePathParamsListSize = 2; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 1)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 1)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; } @@ -1083,7 +1136,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams2[2]; attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; @@ -1104,58 +1157,58 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mEventPathParamsListSize = 75; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 1)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 1)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_FALSE(version2.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; readPrepareParams.mpEventPathParamsList = nullptr; @@ -1169,7 +1222,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams3[3]; @@ -1188,82 +1241,82 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mAttributePathParamsListSize = 3; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // E1C2A* has 3 attributes and E2C3A* has 5 attributes and E2C2A* has 4 attributes - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 12); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + EXPECT_EQ(delegate.mNumAttributeResponse, 12); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 2)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version2.HasValue() && (version2.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_TRUE(version2.HasValue() && (version2.Value() == 2)); Optional version3; app::ConcreteClusterPath clusterPath3(chip::Test::kMockEndpoint1, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath3, version3) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version3.HasValue() && (version3.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath3, version3), CHIP_NO_ERROR); + EXPECT_TRUE(version3.HasValue() && (version3.Value() == 2)); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint1, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; @@ -1276,7 +1329,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams3[3]; @@ -1300,82 +1353,82 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit static_assert(73 <= ArraySize(eventPathParams)); readPrepareParams.mEventPathParamsListSize = 73; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 7); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 7); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath1, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue() && (version1.Value() == 2)); Optional version2; app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version2.HasValue() && (version2.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath2, version2), CHIP_NO_ERROR); + EXPECT_TRUE(version2.HasValue() && (version2.Value() == 2)); Optional version3; app::ConcreteClusterPath clusterPath3(chip::Test::kMockEndpoint1, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath3, version3) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version3.HasValue() && (version3.Value() == 2)); + EXPECT_EQ(cache.GetVersion(clusterPath3, version3), CHIP_NO_ERROR); + EXPECT_TRUE(version3.HasValue() && (version3.Value() == 2)); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint1, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } delegate.mNumAttributeResponse = 0; readPrepareParams.mpEventPathParamsList = nullptr; @@ -1387,7 +1440,7 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit { testId++; ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId); - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); chip::app::AttributePathParams attributePathParams1[1]; attributePathParams1[0].mEndpointId = chip::Test::kMockEndpoint3; @@ -1396,147 +1449,72 @@ void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuit readPrepareParams.mpAttributePathParamsList = attributePathParams1; readPrepareParams.mAttributePathParamsListSize = 1; err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); + mpContext->DrainAndServiceIO(); + EXPECT_EQ(delegate.mNumAttributeResponse, 6); + EXPECT_FALSE(delegate.mReadError); Optional version1; app::ConcreteClusterPath clusterPath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue()); + EXPECT_EQ(cache.GetVersion(clusterPath, version1), CHIP_NO_ERROR); + EXPECT_TRUE(version1.HasValue()); { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); bool receivedAttribute1; reader.Get(receivedAttribute1); - NL_TEST_ASSERT(apSuite, receivedAttribute1 == expectedAttribute1); + EXPECT_EQ(receivedAttribute1, expectedAttribute1); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(2)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); int16_t receivedAttribute2; reader.Get(receivedAttribute2); - NL_TEST_ASSERT(apSuite, receivedAttribute2 == expectedAttribute2); + EXPECT_EQ(receivedAttribute2, expectedAttribute2); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(3)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint64_t receivedAttribute3; reader.Get(receivedAttribute3); - NL_TEST_ASSERT(apSuite, receivedAttribute3 == expectedAttribute3); + EXPECT_EQ(receivedAttribute3, expectedAttribute3); } { app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(4)); TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) == CHIP_NO_ERROR); + EXPECT_EQ(cache.Get(attributePath, reader), CHIP_NO_ERROR); uint8_t receivedAttribute4[256]; reader.GetBytes(receivedAttribute4, 256); - NL_TEST_ASSERT(apSuite, memcmp(receivedAttribute4, expectedAttribute4, 256)); + EXPECT_TRUE(memcmp(receivedAttribute4, expectedAttribute4, 256)); } delegate.mNumAttributeResponse = 0; } - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadSubscribeAttributeResponseWithVersionOnlyCache(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadEventResponse) { - TestContext & ctx = *static_cast(apContext); - CHIP_ERROR err = CHIP_NO_ERROR; - responseDirective = kSendDataResponse; - - MockInteractionModelApp delegate; - chip::app::ClusterStateCache cache(delegate, Optional::Missing(), false /*cachedData*/); - - chip::app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); - // - // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination - // callbacks. - // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); - - // read of E2C2A* and E3C2A2. Expect cache E2C2 version - { - app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), - cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); - chip::app::AttributePathParams attributePathParams2[2]; - attributePathParams2[0].mEndpointId = chip::Test::kMockEndpoint2; - attributePathParams2[0].mClusterId = chip::Test::MockClusterId(3); - attributePathParams2[0].mAttributeId = kInvalidAttributeId; - - attributePathParams2[1].mEndpointId = chip::Test::kMockEndpoint3; - attributePathParams2[1].mClusterId = chip::Test::MockClusterId(2); - attributePathParams2[1].mAttributeId = chip::Test::MockAttributeId(2); - readPrepareParams.mpAttributePathParamsList = attributePathParams2; - readPrepareParams.mAttributePathParamsListSize = 2; - err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - - ctx.DrainAndServiceIO(); - // There are supported 2 global and 3 non-global attributes in E2C2A* and 1 E3C2A2 - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); - Optional version1; - app::ConcreteClusterPath clusterPath1(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath1, version1) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0)); - Optional version2; - app::ConcreteClusterPath clusterPath2(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2)); - NL_TEST_ASSERT(apSuite, cache.GetVersion(clusterPath2, version2) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, !version2.HasValue()); - - { - app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), - chip::Test::MockAttributeId(2)); - TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) != CHIP_NO_ERROR); - } - - { - app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint2, chip::Test::MockClusterId(3), - chip::Test::MockAttributeId(3)); - TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) != CHIP_NO_ERROR); - } - - { - app::ConcreteAttributePath attributePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), - chip::Test::MockAttributeId(2)); - TLV::TLVReader reader; - NL_TEST_ASSERT(apSuite, cache.Get(attributePath, reader) != CHIP_NO_ERROR); - } - delegate.mNumAttributeResponse = 0; - } - - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); -} - -void TestReadInteraction::TestReadEventResponse(nlTestSuite * apSuite, void * apContext) -{ - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false, onDoneCbInvoked = false; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::EventHeader & eventHeader, const auto & EventResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::EventHeader & eventHeader, const auto & EventResponse) { // TODO: Need to add check when IM event server integration completes - IgnoreUnusedVariable(apSuite); onSuccessCbInvoked = true; }; @@ -1549,22 +1527,21 @@ void TestReadInteraction::TestReadEventResponse(nlTestSuite * apSuite, void * ap auto onDoneCb = [&onDoneCbInvoked](app::ReadClient * apReadClient) { onDoneCbInvoked = true; }; Controller::ReadEvent( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, onDoneCb); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, onDoneCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, onDoneCbInvoked); + EXPECT_FALSE(onFailureCbInvoked); + EXPECT_TRUE(onDoneCbInvoked); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadAttributeError(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttributeError) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; responseDirective = kSendDataError; @@ -1577,26 +1554,25 @@ void TestReadInteraction::TestReadAttributeError(nlTestSuite * apSuite, void * a // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { - NL_TEST_ASSERT(apSuite, aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Busy); + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + EXPECT_TRUE(aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Busy); onFailureCbInvoked = true; }; Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttributeTimeout) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; responseDirective = kSendDataError; @@ -1609,38 +1585,38 @@ void TestReadInteraction::TestReadAttributeTimeout(nlTestSuite * apSuite, void * // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { - NL_TEST_ASSERT(apSuite, aError == CHIP_ERROR_TIMEOUT); + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + EXPECT_EQ(aError, CHIP_ERROR_TIMEOUT); onFailureCbInvoked = true; }; Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.ExpireSessionAliceToBob(); + mpContext->ExpireSessionAliceToBob(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 1u); - ctx.ExpireSessionBobToAlice(); + mpContext->ExpireSessionBobToAlice(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); + EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); // // Let's put back the sessions so that the next tests (which assume a valid initialized set of sessions) // can function correctly. // - ctx.CreateSessionAliceToBob(); - ctx.CreateSessionBobToAlice(); + mpContext->CreateSessionAliceToBob(); + mpContext->CreateSessionBobToAlice(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } class TestResubscriptionCallback : public app::ReadClient::Callback @@ -1699,21 +1675,20 @@ class TestResubscriptionCallback : public app::ReadClient::Callback // TODO: This does not validate the CASE establishment pathways since we're limited by the PASE-centric TestContext. // // -void TestReadInteraction::TestResubscribeAttributeTimeout(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestResubscribeAttributeTimeout) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { TestResubscriptionCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); callback.SetReadClient(&readClient); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. app::AttributePathParams attributePathParams[1]; @@ -1728,16 +1703,16 @@ void TestReadInteraction::TestResubscribeAttributeTimeout(nlTestSuite * apSuite, readPrepareParams.mMaxIntervalCeilingSeconds = maxIntervalCeilingSeconds; auto err = readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 0); chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0); @@ -1749,55 +1724,54 @@ void TestReadInteraction::TestResubscribeAttributeTimeout(nlTestSuite * apSuite, // Disable packet transmission, and drive IO till we have reported a re-subscription attempt. // // - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), - [&]() { return callback.mOnResubscriptionsAttempted > 0; }); + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return callback.mOnResubscriptionsAttempted > 0; }); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_TIMEOUT); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_TIMEOUT); - ctx.GetLoopback().mNumMessagesToDrop = 0; + mpContext->GetLoopback().mNumMessagesToDrop = 0; callback.ClearCounters(); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); // // With re-sub enabled, we shouldn't have encountered any errors // - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 0); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnDone, 0); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } // // This validates a vanilla subscription with re-susbcription disabled timing out correctly on the client // side and triggering the OnError callback with the right error code. // -void TestReadInteraction::TestSubscribeAttributeTimeout(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestSubscribeAttributeTimeout) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { TestResubscriptionCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); callback.SetReadClient(&readClient); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); app::AttributePathParams attributePathParams[1]; readPrepareParams.mpAttributePathParamsList = attributePathParams; @@ -1813,19 +1787,19 @@ void TestReadInteraction::TestSubscribeAttributeTimeout(nlTestSuite * apSuite, v readPrepareParams.mMaxIntervalCeilingSeconds = maxIntervalCeilingSeconds; auto err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); // // Request we drop all further messages. // - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0); @@ -1838,26 +1812,25 @@ void TestReadInteraction::TestSubscribeAttributeTimeout(nlTestSuite * apSuite, v // by the liveness timer firing once we hit our max-interval plus // retransmit timeouts. // - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), - [&]() { return callback.mOnError >= 1; }); + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return callback.mOnError >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnError == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_TIMEOUT); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0); + EXPECT_EQ(callback.mOnError, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_TIMEOUT); + EXPECT_EQ(callback.mOnDone, 1); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 0); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); - ctx.GetLoopback().mNumMessagesToDrop = 0; + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + mpContext->GetLoopback().mNumMessagesToDrop = 0; } -void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_MultipleSubscriptions) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -1871,11 +1844,11 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { // // We shouldn't be encountering any failures in this test. // - NL_TEST_ASSERT(apSuite, false); + EXPECT_TRUE(false); }; auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, @@ -1887,7 +1860,7 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Try to issue parallel subscriptions that will exceed the value for app::InteractionModelEngine::kReadHandlerPoolSize. @@ -1896,37 +1869,35 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap // for (size_t i = 0; i < (app::InteractionModelEngine::kReadHandlerPoolSize + 1); i++) { - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 20, - onSubscriptionEstablishedCb, nullptr, false, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 20, + onSubscriptionEstablishedCb, nullptr, false, true), + CHIP_NO_ERROR); } // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { return numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1) && numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1); }); - NL_TEST_ASSERT(apSuite, numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); - NL_TEST_ASSERT(apSuite, - gTestReadInteraction.mNumActiveSubscriptions == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + EXPECT_EQ(numSuccessCalls, (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + EXPECT_EQ(numSubscriptionEstablishedCalls, (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + EXPECT_EQ(mNumActiveSubscriptions, static_cast(app::InteractionModelEngine::kReadHandlerPoolSize + 1)); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); } -void TestReadInteraction::TestReadHandler_SubscriptionAppRejection(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionAppRejection) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -1954,46 +1925,45 @@ void TestReadInteraction::TestReadHandler_SubscriptionAppRejection(nlTestSuite * // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the application rejecting subscriptions. // - gTestReadInteraction.mEmitSubscriptionError = true; + mEmitSubscriptionError = true; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, false, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, false, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); + EXPECT_EQ(numSuccessCalls, 0u); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 0u); + EXPECT_EQ(mNumActiveSubscriptions, 0); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mEmitSubscriptionError = false; + mEmitSubscriptionError = false; } #if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 // Subscriber sends the request with particular max-interval value: // Max interval equal to client-requested min-interval. -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest1) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2012,16 +1982,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 5); - NL_TEST_ASSERT(apSuite, maxInterval == 5); + EXPECT_EQ(minInterval, 5); + EXPECT_EQ(maxInterval, 5); numSubscriptionEstablishedCalls++; }; @@ -2030,46 +2000,45 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 5, 5, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 5, 5, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but lower than 60m: // With no server adjustment. -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest2) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2088,16 +2057,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == 10); + EXPECT_EQ(minInterval, 0); + EXPECT_EQ(maxInterval, 10); numSubscriptionEstablishedCalls++; }; @@ -2106,46 +2075,45 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but lower than 60m: // With server adjustment to a value greater than client-requested, but less than 60m (allowed). -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest3) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2164,16 +2132,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == 3000); + EXPECT_EQ(minInterval, 0); + EXPECT_EQ(maxInterval, 3000); numSubscriptionEstablishedCalls++; }; @@ -2182,37 +2150,37 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = 3000; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + mAlterSubscriptionIntervals = true; + mMaxInterval = 3000; + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } #endif // CHIP_CONFIG_ENABLE_ICD_SERVER @@ -2220,10 +2188,9 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3(nl // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but lower than 60m: // server adjustment to a value greater than client-requested, but greater than 60 (not allowed). -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest4) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2251,36 +2218,36 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = 3700; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + mAlterSubscriptionIntervals = true; + mMaxInterval = 3700; + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(numSuccessCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 0u); + EXPECT_EQ(mNumActiveSubscriptions, 0); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } #if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 @@ -2288,10 +2255,9 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4(nl // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but greater than 60m: // With no server adjustment. -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest5) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2310,16 +2276,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == 4000); + EXPECT_EQ(minInterval, 0); + EXPECT_EQ(maxInterval, 4000); numSubscriptionEstablishedCalls++; }; @@ -2328,46 +2294,45 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but greater than 60m: // With server adjustment to a value lower than 60m. Allowed -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest6) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2386,16 +2351,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == 3000); + EXPECT_EQ(minInterval, 0); + EXPECT_EQ(maxInterval, 3000); numSubscriptionEstablishedCalls++; }; @@ -2404,46 +2369,45 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = 3000; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + mAlterSubscriptionIntervals = true; + mMaxInterval = 3000; + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but greater than 60m: // With server adjustment to a value larger than 60m, but less than max interval. Allowed -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest7) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2462,16 +2426,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7(nl numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { uint16_t minInterval = 0, maxInterval = 0; CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == 3700); + EXPECT_EQ(minInterval, 0); + EXPECT_EQ(maxInterval, 3700); numSubscriptionEstablishedCalls++; }; @@ -2479,37 +2443,37 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = 3700; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + mAlterSubscriptionIntervals = true; + mMaxInterval = 3700; + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + EXPECT_NE(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 1u); + EXPECT_EQ(mNumActiveSubscriptions, 1); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } #endif // CHIP_CONFIG_ENABLE_ICD_SERVER @@ -2517,10 +2481,9 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7(nl // Subscriber sends the request with particular max-interval value: // Max interval greater than client-requested min-interval but greater than 60m: // With server adjustment to a value larger than 60m, but larger than max interval. Disallowed -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest8(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest8) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2547,44 +2510,43 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest8(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = 4100; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + mAlterSubscriptionIntervals = true; + mMaxInterval = 4100; + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(numSuccessCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 0u); + EXPECT_EQ(mNumActiveSubscriptions, 0); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } // Subscriber sends the request with particular max-interval value: // Validate client is not requesting max-interval < min-interval. -void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest9(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_SubscriptionReportingIntervalsTest9) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2612,56 +2574,55 @@ void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest9(nl // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination // callbacks. // - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 5, 4, - onSubscriptionEstablishedCb, nullptr, true) == CHIP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 5, 4, + onSubscriptionEstablishedCb, nullptr, true), + CHIP_ERROR_INVALID_ARGUMENT); // // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(numSuccessCalls, 0u); + EXPECT_EQ(numSubscriptionEstablishedCalls, 0u); + EXPECT_EQ(mNumActiveSubscriptions, 0); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + EXPECT_EQ(mNumActiveSubscriptions, 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); - gTestReadInteraction.mAlterSubscriptionIntervals = false; + mAlterSubscriptionIntervals = false; } /** * When the liveness timeout of a subscription to ICD is reached, the subscription will enter "InactiveICDSubscription" state, the * client should call "OnActiveModeNotification" to re-activate it again when the check-in message is received from the ICD. */ -void TestReadInteraction::TestSubscribe_OnActiveModeNotification(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestSubscribe_OnActiveModeNotification) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { TestResubscriptionCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); callback.mScheduleLITResubscribeImmediately = false; callback.SetReadClient(&readClient); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. app::AttributePathParams attributePathParams[1]; @@ -2677,16 +2638,16 @@ void TestReadInteraction::TestSubscribe_OnActiveModeNotification(nlTestSuite * a readPrepareParams.mIsPeerLIT = true; auto err = readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 0); chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0); uint16_t minInterval; @@ -2699,52 +2660,52 @@ void TestReadInteraction::TestSubscribe_OnActiveModeNotification(nlTestSuite * a // WakeUp() is called. // // - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), [&]() { return false; }); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return false; }); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); - ctx.GetLoopback().mNumMessagesToDrop = 0; + mpContext->GetLoopback().mNumMessagesToDrop = 0; callback.ClearCounters(); app::InteractionModelEngine::GetInstance()->OnActiveModeNotification( ScopedNodeId(readClient.GetPeerNodeId(), readClient.GetFabricIndex())); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_TIMEOUT); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_TIMEOUT); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); // // With re-sub enabled, we shouldn't have encountered any errors // - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 0); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnDone, 0); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * When the liveness timeout of a subscription to ICD is reached, the subscription will enter "InactiveICDSubscription" state, the * client should call "OnActiveModeNotification" to re-activate it again when the check-in message is received from the ICD. */ -void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestSubscribe_DynamicLITSubscription) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { TestResubscriptionCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); responseDirective = kSendDataResponse; @@ -2752,7 +2713,7 @@ void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apS callback.SetReadClient(&readClient); isLitIcd = false; - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. app::AttributePathParams attributePathParams[1]; @@ -2768,16 +2729,16 @@ void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apS readPrepareParams.mIsPeerLIT = true; auto err = readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 0); chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0); uint16_t minInterval; @@ -2793,27 +2754,27 @@ void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apS // // Even if we set the peer type to LIT before, the report indicates that the peer is a SIT now, it will just bahve as // normal, non-LIT subscriptions. - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), - [&]() { return callback.mOnResubscriptionsAttempted != 0; }); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_TIMEOUT); + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return callback.mOnResubscriptionsAttempted != 0; }); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_TIMEOUT); - ctx.GetLoopback().mNumMessagesToDrop = 0; + mpContext->GetLoopback().mNumMessagesToDrop = 0; callback.ClearCounters(); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); // // With re-sub enabled, we shouldn't have encountered any errors // - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 0); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnDone, 0); // Part 2. SIT -> LIT @@ -2826,24 +2787,25 @@ void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apS app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(path); } callback.ClearCounters(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0; }); // When we received the update that OperatingMode becomes LIT, we automatically set the inner peer type to LIT ICD. - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), [&]() { return false; }); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return false; }); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); - ctx.GetLoopback().mNumMessagesToDrop = 0; + mpContext->GetLoopback().mNumMessagesToDrop = 0; callback.ClearCounters(); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); isLitIcd = false; } @@ -2852,22 +2814,21 @@ void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apS * When the liveness timeout of a subscription to ICD is reached, the app can issue resubscription immediately * if they know the peer is active. */ -void TestReadInteraction::TestSubscribe_ImmediatelyResubscriptionForLIT(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestSubscribe_ImmediatelyResubscriptionForLIT) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { TestResubscriptionCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); callback.mScheduleLITResubscribeImmediately = true; callback.SetReadClient(&readClient); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. app::AttributePathParams attributePathParams[1]; @@ -2883,16 +2844,16 @@ void TestReadInteraction::TestSubscribe_ImmediatelyResubscriptionForLIT(nlTestSu readPrepareParams.mIsPeerLIT = true; auto err = readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount >= 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 0); chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0); uint16_t minInterval; @@ -2905,83 +2866,76 @@ void TestReadInteraction::TestSubscribe_ImmediatelyResubscriptionForLIT(nlTestSu // WakeUp() is called. // // - ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; - ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), - [&]() { return callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT; }); - NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); + mpContext->GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount; + mpContext->GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), + [&]() { return callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT; }); + EXPECT_EQ(callback.mOnResubscriptionsAttempted, 1); + EXPECT_EQ(callback.mLastError, CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT); - ctx.GetLoopback().mNumMessagesToDrop = 0; + mpContext->GetLoopback().mNumMessagesToDrop = 0; callback.ClearCounters(); // // Drive servicing IO till we have established a subscription. // - ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), - [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000), + [&]() { return callback.mOnSubscriptionEstablishedCount == 1; }); + EXPECT_EQ(callback.mOnSubscriptionEstablishedCount, 1); // // With re-sub enabled, we shouldn't have encountered any errors // - NL_TEST_ASSERT(apSuite, callback.mOnError == 0); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 0); + EXPECT_EQ(callback.mOnError, 0); + EXPECT_EQ(callback.mOnDone, 0); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadHandler_MultipleReads(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_MultipleReads) { - TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); - MultipleReadHelper(apSuite, ctx, CHIP_IM_MAX_REPORTS_IN_FLIGHT); + MultipleReadHelper(mpContext, CHIP_IM_MAX_REPORTS_IN_FLIGHT); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); } -void TestReadInteraction::TestReadHandler_OneSubscribeMultipleReads(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_OneSubscribeMultipleReads) { - TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 1, "We won't do any reads"); - SubscribeThenReadHelper(apSuite, ctx, 1, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 1); + SubscribeThenReadHelper(mpContext, 1, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 1); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); } -void TestReadInteraction::TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_TwoSubscribesMultipleReads) { - TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 2, "We won't do any reads"); - SubscribeThenReadHelper(apSuite, ctx, 2, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 2); + SubscribeThenReadHelper(mpContext, 2, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 2); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); } -void TestReadInteraction::SubscribeThenReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aSubscribeCount, - size_t aReadCount) +void TestRead::SubscribeThenReadHelper(TestContext * apCtx, size_t aSubscribeCount, size_t aReadCount) { - auto sessionHandle = aCtx.GetSessionBobToAlice(); + auto sessionHandle = apCtx->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -2998,90 +2952,89 @@ void TestReadInteraction::SubscribeThenReadHelper(nlTestSuite * apSuite, TestCon // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { // // We shouldn't be encountering any failures in this test. // - NL_TEST_ASSERT(apSuite, false); + EXPECT_TRUE(false); }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite, &aCtx, aSubscribeCount, aReadCount, - &numReadSuccessCalls, &numReadFailureCalls](const app::ReadClient & readClient, - chip::SubscriptionId aSubscriptionId) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apCtx, aSubscribeCount, aReadCount, &numReadSuccessCalls, + &numReadFailureCalls](const app::ReadClient & readClient, + chip::SubscriptionId aSubscriptionId) { numSubscriptionEstablishedCalls++; if (numSubscriptionEstablishedCalls == aSubscribeCount) { - MultipleReadHelperInternal(apSuite, aCtx, aReadCount, numReadSuccessCalls, numReadFailureCalls); + MultipleReadHelperInternal(apCtx, aReadCount, numReadSuccessCalls, numReadFailureCalls); } }; for (size_t i = 0; i < aSubscribeCount; ++i) { - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &aCtx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, false, true) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &apCtx->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, false, true), + CHIP_NO_ERROR); } - aCtx.DrainAndServiceIO(); + apCtx->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, numSuccessCalls == aSubscribeCount); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == aSubscribeCount); - NL_TEST_ASSERT(apSuite, numReadSuccessCalls == aReadCount); - NL_TEST_ASSERT(apSuite, numReadFailureCalls == 0); + EXPECT_EQ(numSuccessCalls, aSubscribeCount); + EXPECT_EQ(numSubscriptionEstablishedCalls, aSubscribeCount); + EXPECT_EQ(numReadSuccessCalls, aReadCount); + EXPECT_EQ(numReadFailureCalls, 0u); } // The guts of MultipleReadHelper which take references to the success/failure // counts to modify and assume the consumer will be spinning the event loop. -void TestReadInteraction::MultipleReadHelperInternal(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount, - uint32_t & aNumSuccessCalls, uint32_t & aNumFailureCalls) +void TestRead::MultipleReadHelperInternal(TestContext * apCtx, size_t aReadCount, uint32_t & aNumSuccessCalls, + uint32_t & aNumFailureCalls) { - NL_TEST_ASSERT(apSuite, aNumSuccessCalls == 0); - NL_TEST_ASSERT(apSuite, aNumFailureCalls == 0); + EXPECT_EQ(aNumSuccessCalls, 0u); + EXPECT_EQ(aNumFailureCalls, 0u); - auto sessionHandle = aCtx.GetSessionBobToAlice(); + auto sessionHandle = apCtx->GetSessionBobToAlice(); responseDirective = kSendDataResponse; uint16_t firstExpectedResponse = totalReadCount + 1; - auto onFailureCb = [apSuite, &aNumFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&aNumFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { aNumFailureCalls++; - NL_TEST_ASSERT(apSuite, attributePath == nullptr); + EXPECT_EQ(attributePath, nullptr); }; for (size_t i = 0; i < aReadCount; ++i) { - auto onSuccessCb = [&aNumSuccessCalls, apSuite, firstExpectedResponse, - i](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { - NL_TEST_ASSERT(apSuite, dataResponse == firstExpectedResponse + i); + auto onSuccessCb = [&aNumSuccessCalls, firstExpectedResponse, i](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { + EXPECT_EQ(dataResponse, firstExpectedResponse + i); aNumSuccessCalls++; }; - NL_TEST_ASSERT(apSuite, - Controller::ReadAttribute( - &aCtx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::ReadAttribute( + &apCtx->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb), + CHIP_NO_ERROR); } } -void TestReadInteraction::MultipleReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount) +void TestRead::MultipleReadHelper(TestContext * apCtx, size_t aReadCount) { uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; - MultipleReadHelperInternal(apSuite, aCtx, aReadCount, numSuccessCalls, numFailureCalls); + MultipleReadHelperInternal(apCtx, aReadCount, numSuccessCalls, numFailureCalls); - aCtx.DrainAndServiceIO(); + apCtx->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, numSuccessCalls == aReadCount); - NL_TEST_ASSERT(apSuite, numFailureCalls == 0); + EXPECT_EQ(numSuccessCalls, aReadCount); + EXPECT_EQ(numFailureCalls, 0u); } -void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_MultipleSubscriptionsWithDataVersionFilter) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; @@ -3089,19 +3042,18 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFi // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, - const auto & dataResponse) { - NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + EXPECT_TRUE(attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); numSuccessCalls++; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { // // We shouldn't be encountering any failures in this test. // - NL_TEST_ASSERT(apSuite, false); + EXPECT_TRUE(false); }; auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient, @@ -3117,15 +3069,15 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFi chip::Optional dataVersion(1); for (size_t i = 0; i < (app::InteractionModelEngine::kReadHandlerPoolSize + 1); i++) { - NL_TEST_ASSERT(apSuite, - Controller::SubscribeAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, nullptr, false, true, dataVersion) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::SubscribeAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, false, true, dataVersion), + CHIP_NO_ERROR); } // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(30), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(30), [&]() { return numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1) && numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1); }); @@ -3134,17 +3086,16 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFi numSuccessCalls, uint32_t(app::InteractionModelEngine::kReadHandlerPoolSize + 1), numSubscriptionEstablishedCalls, uint32_t(app::InteractionModelEngine::kReadHandlerPoolSize + 1)); - NL_TEST_ASSERT(apSuite, numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + EXPECT_EQ(numSuccessCalls, (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + EXPECT_EQ(numSubscriptionEstablishedCalls, (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandlerResourceExhaustion_MultipleReads) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; @@ -3158,33 +3109,33 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { numFailureCalls++; - NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(Busy)); - NL_TEST_ASSERT(apSuite, attributePath == nullptr); + EXPECT_EQ(aError, CHIP_IM_GLOBAL_STATUS(Busy)); + EXPECT_EQ(attributePath, nullptr); }; app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(0); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); - NL_TEST_ASSERT(apSuite, - Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR); + EXPECT_EQ(Controller::ReadAttribute( + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb), + CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); - NL_TEST_ASSERT(apSuite, numFailureCalls == 1); + EXPECT_EQ(numSuccessCalls, 0u); + EXPECT_EQ(numFailureCalls, 1u); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadFabricScopedWithoutFabricFilter) { /** * TODO: we cannot implement the e2e read tests w/ fabric filter since the test session has only one session, and the @@ -3196,20 +3147,18 @@ void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * * encoder. * - When a fabric filtered read request is received, the response encoder is able to encode the attribute correctly. */ - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; responseDirective = kSendDataResponse; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, - const auto & dataResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { size_t len = 0; - NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, len > 1); + EXPECT_EQ(dataResponse.ComputeSize(&len), CHIP_NO_ERROR); + EXPECT_GT(len, 1u); onSuccessCbInvoked = true; }; @@ -3221,17 +3170,17 @@ void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * }; Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, false /* fabric filtered */); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, false /* fabric filtered */); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadFabricScopedWithFabricFilter) { /** * TODO: we cannot implement the e2e read tests w/ fabric filter since the test session has only one session, and the @@ -3243,20 +3192,18 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS * encoder. * - When a fabric filtered read request is received, the response encoder is able to encode the attribute correctly. */ - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; responseDirective = kSendDataResponse; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, - const auto & dataResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { size_t len = 0; - NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, len == 1); + EXPECT_EQ(dataResponse.ComputeSize(&len), CHIP_NO_ERROR); + EXPECT_EQ(len, 1u); // TODO: Uncomment the following code after we have fabric support in unit tests. /* @@ -3264,7 +3211,7 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS if (iter.Next()) { auto & item = iter.GetValue(); - NL_TEST_ASSERT(apSuite, item.fabricIndex == 1); + EXPECT_EQ(item.fabricIndex, 1); } */ onSuccessCbInvoked = true; @@ -3277,14 +3224,14 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS }; Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true /* fabric filtered */); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true /* fabric filtered */); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } namespace SubscriptionPathQuotaHelpers { @@ -3356,7 +3303,7 @@ class TestPerpetualListReadCallback : public app::ReadClient::Callback int32_t reportsReceived = 0; }; -void EstablishReadOrSubscriptions(nlTestSuite * apSuite, const SessionHandle & sessionHandle, size_t numSubs, size_t pathPerSub, +void EstablishReadOrSubscriptions(const SessionHandle & sessionHandle, size_t numSubs, size_t pathPerSub, app::AttributePathParams path, app::ReadClient::InteractionType type, app::ReadClient::Callback * callback, std::vector> & readClients) { @@ -3376,26 +3323,25 @@ void EstablishReadOrSubscriptions(nlTestSuite * apSuite, const SessionHandle & s std::unique_ptr readClient = std::make_unique(app::InteractionModelEngine::GetInstance(), app::InteractionModelEngine::GetInstance()->GetExchangeManager(), *callback, type); - NL_TEST_ASSERT(apSuite, readClient->SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient->SendRequest(readParams), CHIP_NO_ERROR); readClients.push_back(std::move(readClient)); } } } // namespace SubscriptionPathQuotaHelpers -void TestReadInteraction::TestSubscribeAttributeDeniedNotExistPath(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestSubscribeAttributeDeniedNotExistPath) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive); { SubscriptionPathQuotaHelpers::TestReadCallback callback; - app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback, + app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), callback, app::ReadClient::InteractionType::Subscribe); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + app::ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); app::AttributePathParams attributePathParams[1]; readPrepareParams.mpAttributePathParamsList = attributePathParams; @@ -3409,33 +3355,32 @@ void TestReadInteraction::TestSubscribeAttributeDeniedNotExistPath(nlTestSuite * readPrepareParams.mMaxIntervalCeilingSeconds = 1; auto err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, callback.mOnError == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_IM_GLOBAL_STATUS(InvalidAction)); - NL_TEST_ASSERT(apSuite, callback.mOnDone == 1); + EXPECT_EQ(callback.mOnError, 1u); + EXPECT_EQ(callback.mLastError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); + EXPECT_EQ(callback.mOnDone, 1u); } - ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); + mpContext->SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_KillOverQuotaSubscriptions) { - // Note: We cannot use ctx.DrainAndServiceIO() since the perpetual read will make DrainAndServiceIO never return. + // Note: We cannot use mpContext->DrainAndServiceIO() since the perpetual read will make DrainAndServiceIO never return. using namespace SubscriptionPathQuotaHelpers; - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); const auto kExpectedParallelSubs = - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric * ctx.GetFabricTable().FabricCount(); + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric * mpContext->GetFabricTable().FabricCount(); const auto kExpectedParallelPaths = kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription; - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); // Here, we set up two background perpetual read requests to simulate parallel Read + Subscriptions. // We don't care about the data read, we only care about the existence of such read transactions. @@ -3444,19 +3389,17 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite TestPerpetualListReadCallback perpetualReadCallback; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, kPerpetualAttributeid), app::ReadClient::InteractionType::Read, &perpetualReadCallback, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, kPerpetualAttributeid), app::ReadClient::InteractionType::Read, &perpetualReadCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2; }); // Ensure our read transactions are established. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == - 2); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), 2u); // Intentially establish subscriptions using exceeded resources. app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); @@ -3468,29 +3411,28 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite // // Subscription A EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallback, readClients); // Subscription B EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), kExpectedParallelSubs, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, + mpContext->GetSessionBobToAlice(), kExpectedParallelSubs, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallback, readClients); // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs + 1 && readCallback.mAttributeCount == kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription + app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1; }); - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == - kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription + - app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1); - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs + 1); + EXPECT_EQ(readCallback.mAttributeCount, + kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription + + app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1); + EXPECT_EQ(readCallback.mOnSubscriptionEstablishedCount, kExpectedParallelSubs + 1); // We have set up the environment for testing the evicting logic. // We now have a full stable of subscriptions setup AND we've artificially limited the capacity, creation of further @@ -3506,28 +3448,28 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite TestReadCallback callback; std::vector> outReadClient; EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &callback, outReadClient); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback.mOnError == 1; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback.mOnError == 1; }); // Over-sized request after used all paths will receive Paths Exhausted status code. - NL_TEST_ASSERT(apSuite, callback.mOnError == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_IM_GLOBAL_STATUS(PathsExhausted)); + EXPECT_EQ(callback.mOnError, 1u); + EXPECT_EQ(callback.mLastError, CHIP_IM_GLOBAL_STATUS(PathsExhausted)); } // This next test validates that a compliant subscription request will kick out an existing subscription (arguably, the one that // was previously established with more paths than the limit per fabric) { EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallback, readClients); readCallback.ClearCounters(); // Run until the new subscription got setup fully as viewed by the client. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnSubscriptionEstablishedCount == 1 && readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerSubscription; }); @@ -3535,8 +3477,8 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite // This read handler should evict some existing subscriptions for enough space. // Validate that the new subscription got setup fully as viewed by the client. And we will validate we handled this // subscription by evicting the correct subscriptions later. - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerSubscription); + EXPECT_EQ(readCallback.mOnSubscriptionEstablishedCount, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerSubscription); } // Validate we evicted the right subscription for handling the new subscription above. @@ -3552,8 +3494,9 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite readCallback.ClearCounters(); // Run until all subscriptions are clean. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), - [&]() { return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { + return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0; + }); // Before the above subscription, we have one subscription with kMinSupportedPathsPerSubscription + 1 paths, we should evict // that subscription before evicting any other subscriptions, which will result we used exactly kExpectedParallelPaths and have @@ -3561,28 +3504,26 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite // We have exactly one subscription than uses more resources than others, so the interaction model must evict it first, and we // will have exactly kExpectedParallelPaths only when that subscription have been evicted. We use this indirect method to verify // the subscriptions since the read client won't shutdown until the timeout fired. - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == kExpectedParallelPaths); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Subscribe) == static_cast(kExpectedParallelSubs)); + EXPECT_EQ(readCallback.mAttributeCount, kExpectedParallelPaths); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Subscribe), + static_cast(kExpectedParallelSubs)); // Part 2: Testing per fabric minimas. // Validate we have more than kMinSupportedSubscriptionsPerFabric subscriptions for testing per fabric minimas. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Subscribe, ctx.GetAliceFabricIndex()) > - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_GT(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Subscribe, + mpContext->GetAliceFabricIndex()), + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); // The following check will trigger the logic in im to kill the read handlers that use more paths than the limit per fabric. { EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric, + mpContext->GetSessionAliceToBob(), app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallbackFabric2, readClients); // Run until we have established the subscriptions. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallbackFabric2.mOnSubscriptionEstablishedCount == app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric && readCallbackFabric2.mAttributeCount == @@ -3591,13 +3532,11 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite }); // Verify the subscriptions are established successfully. We will check if we evicted the expected subscriptions later. - NL_TEST_ASSERT(apSuite, - readCallbackFabric2.mOnSubscriptionEstablishedCount == - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); - NL_TEST_ASSERT(apSuite, - readCallbackFabric2.mAttributeCount == - app::InteractionModelEngine::kMinSupportedPathsPerSubscription * - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(readCallbackFabric2.mOnSubscriptionEstablishedCount, + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(readCallbackFabric2.mAttributeCount, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); } // Validate the subscriptions are handled by evicting one or more subscriptions from Fabric A. @@ -3612,55 +3551,49 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite readCallbackFabric2.ClearCounters(); // Run until all subscriptions are clean. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), - [&]() { return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { + return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0; + }); // Some subscriptions on fabric 1 should be evicted since fabric 1 is using more resources than the limits. - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == - app::InteractionModelEngine::kMinSupportedPathsPerSubscription * - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); - NL_TEST_ASSERT(apSuite, - readCallbackFabric2.mAttributeCount == - app::InteractionModelEngine::kMinSupportedPathsPerSubscription * - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Subscribe, ctx.GetAliceFabricIndex()) == - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Subscribe, ctx.GetBobFabricIndex()) == - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(readCallback.mAttributeCount, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(readCallbackFabric2.mAttributeCount, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Subscribe, + mpContext->GetAliceFabricIndex()), + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Subscribe, + mpContext->GetBobFabricIndex()), + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); // Ensure our read transactions are still alive. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == - 2); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), 2u); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Shutdown all clients readClients.clear(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions(-1); app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForSubscriptions(-1); } -void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_KillOldestSubscriptions) { using namespace SubscriptionPathQuotaHelpers; - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); const auto kExpectedParallelSubs = - app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric * ctx.GetFabricTable().FabricCount(); + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric * mpContext->GetFabricTable().FabricCount(); const auto kExpectedParallelPaths = kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription; - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); TestReadCallback readCallback; std::vector> readClients; @@ -3671,50 +3604,47 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * // This should just use all availbale resources. EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), kExpectedParallelSubs, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, + mpContext->GetSessionBobToAlice(), kExpectedParallelSubs, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallback, readClients); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == - kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription); - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == kExpectedParallelSubs); + EXPECT_EQ(readCallback.mAttributeCount, kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription); + EXPECT_EQ(readCallback.mOnSubscriptionEstablishedCount, kExpectedParallelSubs); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), kExpectedParallelSubs); // The following check will trigger the logic in im to kill the read handlers that uses more paths than the limit per fabric. { TestReadCallback callback; std::vector> outReadClient; EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &callback, outReadClient); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Over-sized request after used all paths will receive Paths Exhausted status code. - NL_TEST_ASSERT(apSuite, callback.mOnError == 1); - NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_IM_GLOBAL_STATUS(PathsExhausted)); + EXPECT_EQ(callback.mOnError, 1u); + EXPECT_EQ(callback.mLastError, CHIP_IM_GLOBAL_STATUS(PathsExhausted)); } // The following check will trigger the logic in im to kill the read handlers that uses more paths than the limit per fabric. { EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Subscribe, &readCallback, readClients); readCallback.ClearCounters(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // This read handler should evict some existing subscriptions for enough space - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerSubscription); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == - static_cast(kExpectedParallelSubs)); + EXPECT_EQ(readCallback.mOnSubscriptionEstablishedCount, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerSubscription); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), + static_cast(kExpectedParallelSubs)); } { @@ -3725,17 +3655,17 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(path); } readCallback.ClearCounters(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount <= kExpectedParallelPaths); + EXPECT_LE(readCallback.mAttributeCount, kExpectedParallelPaths); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Shutdown all clients readClients.clear(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions(-1); app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForSubscriptions(-1); @@ -3748,12 +3678,10 @@ struct TestReadHandler_ParallelReads_TestCase_Parameters int MaxFabrics = -1; }; -static void TestReadHandler_ParallelReads_TestCase(nlTestSuite * apSuite, void * apContext, +static void TestReadHandler_ParallelReads_TestCase(TestContext * apContext, const TestReadHandler_ParallelReads_TestCase_Parameters & params, std::function body) { - TestContext & ctx = *static_cast(apContext); - app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(params.ReadHandlerCapacity); app::InteractionModelEngine::GetInstance()->SetConfigMaxFabrics(params.MaxFabrics); @@ -3763,10 +3691,10 @@ static void TestReadHandler_ParallelReads_TestCase(nlTestSuite * apSuite, void * // Clean up app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - ctx.DrainAndServiceIO(); + apContext->DrainAndServiceIO(); // Sanity check - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(apContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); @@ -3774,22 +3702,20 @@ static void TestReadHandler_ParallelReads_TestCase(nlTestSuite * apSuite, void * app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForReads(-1); } -void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_ParallelReads) { - // Note: We cannot use ctx.DrainAndServiceIO() except at the end of each test case since the perpetual read transactions will - // never end. - // Note: We use ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { CONDITION }); and NL_TEST_ASSERT(apSuite, - // CONDITION ) to ensure the CONDITION is satisfied. + // Note: We cannot use mpContext->DrainAndServiceIO() except at the end of each test case since the perpetual read transactions + // will never end. Note: We use mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { CONDITION }); and + // EXPECT_EQ( CONDITION ) to ensure the CONDITION is satisfied. using namespace SubscriptionPathQuotaHelpers; using Params = TestReadHandler_ParallelReads_TestCase_Parameters; - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); - app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(this); auto TestCase = [&](const TestReadHandler_ParallelReads_TestCase_Parameters & params, std::function body) { - TestReadHandler_ParallelReads_TestCase(apSuite, apContext, params, body); + TestReadHandler_ParallelReads_TestCase(mpContext, params, body); }; // Case 1.1: 2 reads used up the path pool (but not the ReadHandler pool), and one incoming oversized read request => @@ -3806,35 +3732,35 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The two subscriptions should still alive - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); // The new read request should be rejected - NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); - NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(PathsExhausted)); + EXPECT_NE(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mLastError, CHIP_IM_GLOBAL_STATUS(PathsExhausted)); }); // Case 1.2: 2 reads used up the ReadHandler pool (not the PathPool), and one incoming oversized read request => Busy. @@ -3851,33 +3777,33 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The two subscriptions should still alive - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); // The new read request should be rejected - NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); - NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + EXPECT_NE(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mLastError, CHIP_IM_GLOBAL_STATUS(Busy)); }); // Case 1.3.1: If we have enough resource, any read requests will be accepted (case for oversized read request). @@ -3893,39 +3819,38 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1); - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1); + EXPECT_EQ(readCallback.mOnError, 0u); // The two subscriptions should still alive backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); }); // Case 1.3.2: If we have enough resource, any read requests will be accepted (case for non-oversized read requests) @@ -3941,41 +3866,41 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, 1, + mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + EXPECT_EQ(readCallback.mAttributeCount, 1u); + EXPECT_EQ(readCallback.mOnError, 0u); // The two subscriptions should still alive backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); }); // Case 2: 1 oversized read and one non-oversized read, and one incoming read request from __another__ fabric => accept by @@ -3992,46 +3917,46 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + EXPECT_TRUE(readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // The oversized read handler should be evicted -> We should have one active read handler. - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 1u); backgroundReadCallback.ClearCounter(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); // We don't check the readCallbackForOversizedRead, since it cannot prove anything -- it can be 0 even when the // oversized read request is alive. We ensure this by checking (1) we have only one active read handler, (2) the one // active read handler is the non-oversized one. // The non-oversized read handler should not be evicted. - NL_TEST_ASSERT(apSuite, backgroundReadCallback.reportsReceived > 0); + EXPECT_GT(backgroundReadCallback.reportsReceived, 0); }); // Case 2 (Repeat): we swapped the order of the oversized and non-oversized read handler to ensure we always evict the oversized @@ -4048,46 +3973,46 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + EXPECT_TRUE(readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // The oversized read handler should be evicted -> We should have one active read handler. - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 1u); backgroundReadCallback.ClearCounter(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); // We don't check the readCallbackForOversizedRead, since it cannot prove anything -- it can be 0 even when the // oversized read request is alive. We ensure this by checking (1) we have only one active read handler, (2) the one // active read handler is the non-oversized one. // The non-oversized read handler should not be evicted. - NL_TEST_ASSERT(apSuite, backgroundReadCallback.reportsReceived > 0); + EXPECT_GT(backgroundReadCallback.reportsReceived, 0); }); // Case 3: one oversized read and one non-oversized read, the remaining path in PathPool is suffcient but the ReadHandler pool @@ -4106,37 +4031,37 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback.reportsReceived > 0 && readCallbackForOversizedRead.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + EXPECT_TRUE(readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be rejected. - NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); - NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + EXPECT_NE(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mLastError, CHIP_IM_GLOBAL_STATUS(Busy)); // Ensure the two read transactions are not evicted. backgroundReadCallback.ClearCounter(); readCallbackForOversizedRead.ClearCounter(); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + EXPECT_TRUE(readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); }); // Case 4.1: 1 fabric is oversized, and one incoming read request from __another__ fabric => accept by evicting one read request @@ -4154,43 +4079,43 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be rejected. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // One of the read requests from Bob to Alice should be evicted. // We should have only one 1 active read handler, since the transaction from Alice to Bob has finished already, and one // of two Bob to Alice transactions has been evicted. - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 1u); // Note: Younger read handler will be evicted. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback1.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + EXPECT_GT(backgroundReadCallback1.reportsReceived, 0); }); // Case 4.2: Like case 4.1, but now the over sized fabric contains one (older) oversized read request and one (younger) @@ -4207,44 +4132,44 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); backgroundReadCallback1.ClearCounter(); backgroundReadCallback2.ClearCounter(); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be rejected. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // One of the read requests from Bob to Alice should be evicted. // We should have only one 1 active read handler, since the transaction from Alice to Bob has finished already, and one // of two Bob to Alice transactions has been evicted. - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 1u); // Note: Larger read handler will be evicted before evicting the younger one. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), - [&]() { return backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback2.reportsReceived > 0); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + EXPECT_GT(backgroundReadCallback2.reportsReceived, 0); }); // The following tests are the cases of read transactions on PASE sessions. @@ -4264,42 +4189,41 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback3; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && backgroundReadCallback3.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, - backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && - backgroundReadCallback3.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // Should evict one read request from Bob fabric for enough resources. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetAliceFabricIndex()), + 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetBobFabricIndex()), + 1u); }); // Case 5.2: The device's fabric table is not full, PASE sessions are counted as a "valid" fabric and can evict existing read @@ -4319,42 +4243,41 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback3; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && backgroundReadCallback3.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, - backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && - backgroundReadCallback3.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // Should evict one read request from Bob fabric for enough resources. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetAliceFabricIndex()), + 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetBobFabricIndex()), + 1u); }); // Case 6: The device's fabric table is full, PASE sessions won't be counted as a valid fabric and cannot evict existing read @@ -4372,44 +4295,43 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback3; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && backgroundReadCallback3.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, - backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && - backgroundReadCallback3.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be rejected. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 1); - NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + EXPECT_EQ(readCallback.mOnError, 1u); + EXPECT_EQ(readCallback.mLastError, CHIP_IM_GLOBAL_STATUS(Busy)); // Should evict one read request from Bob fabric for enough resources. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 2); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetAliceFabricIndex()), + 2u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetBobFabricIndex()), + 1u); }); // Case 7: We will accept read transactions on PASE session when the fabric table is full but we have enough resources for it. @@ -4426,38 +4348,38 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // No read transactions should be evicted. - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetAliceFabricIndex()), + 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + mpContext->GetBobFabricIndex()), + 1u); }); // Case 8.1: If the fabric table on the device is full, read transactions on PASE session will always be evicted when another @@ -4474,34 +4396,33 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionCharlieToDavid(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // Should evict the read request on PASE session for enough resources. - NL_TEST_ASSERT( - apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 1); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), + 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 0u); }); // Case 8.2: If the fabric table on the device is full, read transactions on PASE session will always be evicted when another @@ -4519,35 +4440,34 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v TestPerpetualListReadCallback backgroundReadCallback2; std::vector> readClients; - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionCharlieToDavid(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, 1, + mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, 1u); // Should evict the read request on PASE session for enough resources. - NL_TEST_ASSERT( - apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 1); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), + 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 0u); }); // Case 9.1: If the fabric table on the device is not full, read transactions on PASE session will NOT be evicted when the @@ -4566,41 +4486,39 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v std::vector> readClients; EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + mpContext->GetSessionCharlieToDavid(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallbackForPASESession, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; }); - NL_TEST_ASSERT(apSuite, - backgroundReadCallbackForPASESession.reportsReceived > 0 && - backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + EXPECT_TRUE(backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && + backgroundReadCallback2.reportsReceived > 0); EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, 1, + mpContext->GetSessionBobToAlice(), 1, 1, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, 1u); // The read handler on PASE session should not be evicted since the resources used by all PASE sessions are not // exceeding the resources guaranteed to a normal fabric. - NL_TEST_ASSERT( - apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), + 2u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 1u); }); // Case 9.2: If the fabric table on the device is not full, the read handlers from normal fabrics MAY be evicted before all read @@ -4620,42 +4538,41 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v std::vector> readClients; EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 3, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, + mpContext->GetSessionCharlieToDavid(), 3, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallbackForPASESession, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 3, + EstablishReadOrSubscriptions(mpContext->GetSessionBobToAlice(), 3, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( app::ReadHandler::InteractionType::Read) == 6; }); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 3); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 3u); // We have to evict one read transaction on PASE session and one read transaction on Alice's fabric. EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // No more than one read handler on PASE session should be evicted exceeding the resources guaranteed to a normal // fabric. Note: We are using ">=" here since it is also acceptable if we choose to evict one read transaction from // Alice fabric. - NL_TEST_ASSERT( - apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) >= 4); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) >= 2); + EXPECT_GE(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), + 4u); + EXPECT_GE(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 2u); }); // Case 10: If the fabric table on the device is full, we won't evict read requests from normal fabrics before we have evicted @@ -4674,55 +4591,53 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v std::vector> readClients; EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionCharlieToDavid(), 2, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, + mpContext->GetSessionCharlieToDavid(), 2, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallbackForPASESession, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); - EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + EstablishReadOrSubscriptions(mpContext->GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 2; }); - NL_TEST_ASSERT(apSuite, - backgroundReadCallbackForPASESession.reportsReceived > 0 && - backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 2); + EXPECT_TRUE(backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && + backgroundReadCallback2.reportsReceived > 0 && + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 2); // To handle this read request, we must evict both read transactions from the PASE session. EstablishReadOrSubscriptions( - apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + mpContext->GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, app::AttributePathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id), app::ReadClient::InteractionType::Read, &readCallback, readClients); - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); // The new read request should be accepted. - NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + EXPECT_EQ(readCallback.mOnError, 0u); + EXPECT_EQ(readCallback.mOnDone, 1u); + EXPECT_EQ(readCallback.mAttributeCount, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); // The read handler on PASE session should be evicted, and the read transactions on a normal fabric should be untouched // although it is oversized. - NL_TEST_ASSERT( - apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2); - NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( - app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read), + 2u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex), + 0u); }); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->SetConfigMaxFabrics(-1); app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); @@ -4732,20 +4647,18 @@ void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, v // Needs to be larger than our plausible path pool. constexpr size_t sTooLargePathCount = 200; -void TestReadInteraction::TestReadHandler_TooManyPaths(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_TooManyPaths) { using namespace chip::app; - TestContext & ctx = *static_cast(apContext); - - chip::Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + chip::Messaging::ReliableMessageMgr * rm = mpContext->GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. - NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); auto * engine = InteractionModelEngine::GetInstance(); engine->SetForceHandlerQuota(true); - ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + ReadPrepareParams readPrepareParams(mpContext->GetSessionBobToAlice()); // Needs to be larger than our plausible path pool. chip::app::AttributePathParams attributePathParams[sTooLargePathCount]; readPrepareParams.mpAttributePathParamsList = attributePathParams; @@ -4753,93 +4666,90 @@ void TestReadInteraction::TestReadHandler_TooManyPaths(nlTestSuite * apSuite, vo { MockInteractionModelApp delegate; - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, !delegate.mReadError); - ReadClient readClient(InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + EXPECT_EQ(delegate.mNumAttributeResponse, 0); + EXPECT_FALSE(delegate.mReadError); + ReadClient readClient(InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), delegate, ReadClient::InteractionType::Read); CHIP_ERROR err = readClient.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, delegate.mReadError); + EXPECT_EQ(delegate.mNumAttributeResponse, 0); + EXPECT_TRUE(delegate.mReadError); StatusIB status(delegate.mError); - NL_TEST_ASSERT(apSuite, status.mStatus == Protocols::InteractionModel::Status::PathsExhausted); + EXPECT_EQ(status.mStatus, Protocols::InteractionModel::Status::PathsExhausted); } - NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); engine->SetForceHandlerQuota(false); } -void TestReadInteraction::TestReadHandler_TwoParallelReadsSecondTooManyPaths(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_TwoParallelReadsSecondTooManyPaths) { using namespace chip::app; - TestContext & ctx = *static_cast(apContext); - - chip::Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + chip::Messaging::ReliableMessageMgr * rm = mpContext->GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. - NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); auto * engine = InteractionModelEngine::GetInstance(); engine->SetForceHandlerQuota(true); { MockInteractionModelApp delegate1; - NL_TEST_ASSERT(apSuite, delegate1.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, !delegate1.mReadError); - ReadClient readClient1(InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate1, + EXPECT_EQ(delegate1.mNumAttributeResponse, 0); + EXPECT_FALSE(delegate1.mReadError); + ReadClient readClient1(InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), delegate1, ReadClient::InteractionType::Read); MockInteractionModelApp delegate2; - NL_TEST_ASSERT(apSuite, delegate2.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, !delegate2.mReadError); - ReadClient readClient2(InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate2, + EXPECT_EQ(delegate2.mNumAttributeResponse, 0); + EXPECT_FALSE(delegate2.mReadError); + ReadClient readClient2(InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), delegate2, ReadClient::InteractionType::Read); - ReadPrepareParams readPrepareParams1(ctx.GetSessionBobToAlice()); + ReadPrepareParams readPrepareParams1(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. chip::app::AttributePathParams attributePathParams1[2]; readPrepareParams1.mpAttributePathParamsList = attributePathParams1; readPrepareParams1.mAttributePathParamsListSize = ArraySize(attributePathParams1); CHIP_ERROR err = readClient1.SendRequest(readPrepareParams1); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ReadPrepareParams readPrepareParams2(ctx.GetSessionBobToAlice()); + ReadPrepareParams readPrepareParams2(mpContext->GetSessionBobToAlice()); // Read full wildcard paths, repeat twice to ensure chunking. chip::app::AttributePathParams attributePathParams2[sTooLargePathCount]; readPrepareParams2.mpAttributePathParamsList = attributePathParams2; readPrepareParams2.mAttributePathParamsListSize = ArraySize(attributePathParams2); err = readClient2.SendRequest(readPrepareParams2); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, delegate1.mNumAttributeResponse != 0); - NL_TEST_ASSERT(apSuite, !delegate1.mReadError); + EXPECT_NE(delegate1.mNumAttributeResponse, 0); + EXPECT_FALSE(delegate1.mReadError); - NL_TEST_ASSERT(apSuite, delegate2.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, delegate2.mReadError); + EXPECT_EQ(delegate2.mNumAttributeResponse, 0); + EXPECT_TRUE(delegate2.mReadError); StatusIB status(delegate2.mError); - NL_TEST_ASSERT(apSuite, status.mStatus == Protocols::InteractionModel::Status::PathsExhausted); + EXPECT_EQ(status.mStatus, Protocols::InteractionModel::Status::PathsExhausted); } - NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); engine->SetForceHandlerQuota(false); } -void TestReadInteraction::TestReadAttribute_ManyDataValues(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttribute_ManyDataValues) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -4847,10 +4757,10 @@ void TestReadInteraction::TestReadAttribute_ManyDataValues(nlTestSuite * apSuite // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { - NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); + auto onSuccessCb = [&successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + EXPECT_TRUE(attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); - NL_TEST_ASSERT(apSuite, dataResponse); + EXPECT_TRUE(dataResponse); ++successCalls; }; @@ -4858,22 +4768,21 @@ void TestReadInteraction::TestReadAttribute_ManyDataValues(nlTestSuite * apSuite // not safe to do so. auto onFailureCb = [&failureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; }; - Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + Controller::ReadAttribute(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 1); - NL_TEST_ASSERT(apSuite, failureCalls == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 1u); + EXPECT_EQ(failureCalls, 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadAttribute_ManyDataValuesWrongPath(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttribute_ManyDataValuesWrongPath) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -4881,10 +4790,10 @@ void TestReadInteraction::TestReadAttribute_ManyDataValuesWrongPath(nlTestSuite // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { - NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); + auto onSuccessCb = [&successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + EXPECT_TRUE(attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); - NL_TEST_ASSERT(apSuite, dataResponse); + EXPECT_TRUE(dataResponse); ++successCalls; }; @@ -4892,22 +4801,21 @@ void TestReadInteraction::TestReadAttribute_ManyDataValuesWrongPath(nlTestSuite // not safe to do so. auto onFailureCb = [&failureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; }; - Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + Controller::ReadAttribute(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 0); - NL_TEST_ASSERT(apSuite, failureCalls == 1); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 0u); + EXPECT_EQ(failureCalls, 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestReadInteraction::TestReadAttribute_ManyErrors(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadAttribute_ManyErrors) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -4915,10 +4823,10 @@ void TestReadInteraction::TestReadAttribute_ManyErrors(nlTestSuite * apSuite, vo // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { - NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); + auto onSuccessCb = [&successCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + EXPECT_TRUE(attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion); - NL_TEST_ASSERT(apSuite, dataResponse); + EXPECT_TRUE(dataResponse); ++successCalls; }; @@ -4926,16 +4834,16 @@ void TestReadInteraction::TestReadAttribute_ManyErrors(nlTestSuite * apSuite, vo // not safe to do so. auto onFailureCb = [&failureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; }; - Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + Controller::ReadAttribute(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 0); - NL_TEST_ASSERT(apSuite, failureCalls == 1); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 0u); + EXPECT_EQ(failureCalls, 1u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients(), 0u); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } // @@ -4944,15 +4852,14 @@ void TestReadInteraction::TestReadAttribute_ManyErrors(nlTestSuite * apSuite, vo // // This should evict the previous subscription before sending back an error. // -void TestReadInteraction::TestReadHandler_KeepSubscriptionTest(nlTestSuite * apSuite, void * apContext) +TEST_F(TestRead, TestReadHandler_KeepSubscriptionTest) { using namespace SubscriptionPathQuotaHelpers; - TestContext & ctx = *static_cast(apContext); TestReadCallback readCallback; app::AttributePathParams pathParams(kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int16u::Id); - app::ReadPrepareParams readParam(ctx.GetSessionAliceToBob()); + app::ReadPrepareParams readParam(mpContext->GetSessionAliceToBob()); readParam.mpAttributePathParamsList = &pathParams; readParam.mAttributePathParamsListSize = 1; readParam.mMaxIntervalCeilingSeconds = 1; @@ -4961,11 +4868,11 @@ void TestReadInteraction::TestReadHandler_KeepSubscriptionTest(nlTestSuite * apS std::unique_ptr readClient = std::make_unique( app::InteractionModelEngine::GetInstance(), app::InteractionModelEngine::GetInstance()->GetExchangeManager(), readCallback, app::ReadClient::InteractionType::Subscribe); - NL_TEST_ASSERT(apSuite, readClient->SendRequest(readParam) == CHIP_NO_ERROR); + EXPECT_EQ(readClient->SendRequest(readParam), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 1u); ChipLogProgress(DataManagement, "Issue another subscription that will evict the first sub..."); @@ -4973,17 +4880,17 @@ void TestReadInteraction::TestReadHandler_KeepSubscriptionTest(nlTestSuite * apS readClient = std::make_unique(app::InteractionModelEngine::GetInstance(), app::InteractionModelEngine::GetInstance()->GetExchangeManager(), readCallback, app::ReadClient::InteractionType::Subscribe); - NL_TEST_ASSERT(apSuite, readClient->SendRequest(readParam) == CHIP_NO_ERROR); + EXPECT_EQ(readClient->SendRequest(readParam), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); - NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); + EXPECT_EQ(app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(), 0u); + EXPECT_NE(readCallback.mOnError, 0u); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); } -System::Clock::Timeout TestReadInteraction::ComputeSubscriptionTimeout(System::Clock::Seconds16 aMaxInterval) +System::Clock::Timeout TestRead::ComputeSubscriptionTimeout(System::Clock::Seconds16 aMaxInterval) { // Add 1000ms of slack to our max interval to make sure we hit the // subscription liveness timer. 100ms was tried in the past and is not @@ -4996,75 +4903,4 @@ System::Clock::Timeout TestReadInteraction::ComputeSubscriptionTimeout(System::C return publisherTransmissionTimeout + aMaxInterval + System::Clock::Milliseconds32(1000); } -// clang-format off -const nlTest sTests[] = -{ - NL_TEST_DEF("TestReadAttributeResponse", TestReadInteraction::TestReadAttributeResponse), - NL_TEST_DEF("TestReadEventResponse", TestReadInteraction::TestReadEventResponse), - NL_TEST_DEF("TestReadAttributeError", TestReadInteraction::TestReadAttributeError), - NL_TEST_DEF("TestReadFabricScopedWithoutFabricFilter", TestReadInteraction::TestReadFabricScopedWithoutFabricFilter), - NL_TEST_DEF("TestReadFabricScopedWithFabricFilter", TestReadInteraction::TestReadFabricScopedWithFabricFilter), - NL_TEST_DEF("TestReadHandler_MultipleSubscriptions", TestReadInteraction::TestReadHandler_MultipleSubscriptions), - NL_TEST_DEF("TestReadHandler_SubscriptionAppRejection", TestReadInteraction::TestReadHandler_SubscriptionAppRejection), - NL_TEST_DEF("TestReadHandler_MultipleSubscriptionsWithDataVersionFilter", TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter), - NL_TEST_DEF("TestReadHandler_MultipleReads", TestReadInteraction::TestReadHandler_MultipleReads), - NL_TEST_DEF("TestReadHandler_OneSubscribeMultipleReads", TestReadInteraction::TestReadHandler_OneSubscribeMultipleReads), - NL_TEST_DEF("TestReadHandler_TwoSubscribesMultipleReads", TestReadInteraction::TestReadHandler_TwoSubscribesMultipleReads), - NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads), - NL_TEST_DEF("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout), -/* - Disabling SubscriptionReportingIntervals tests for ICD run. - These tests test the non-ICD behavior and cannot take into account that an ICD will always - change the max interval of a subscription. -*/ -#if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest1", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1), // no good - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest2", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2), - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest3", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3), -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest4", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4), -#if CHIP_CONFIG_ENABLE_ICD_SERVER != 1 - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest5", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5), - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest6", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6), - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest7", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7), -#endif // CHIP_CONFIG_ENABLE_ICD_SERVER - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest8", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest8), - NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest9", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest9), - NL_TEST_DEF("TestReadSubscribeAttributeResponseWithVersionOnlyCache", TestReadInteraction::TestReadSubscribeAttributeResponseWithVersionOnlyCache), - NL_TEST_DEF("TestReadSubscribeAttributeResponseWithCache", TestReadInteraction::TestReadSubscribeAttributeResponseWithCache), - NL_TEST_DEF("TestReadHandler_KillOverQuotaSubscriptions", TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions), - NL_TEST_DEF("TestReadHandler_KillOldestSubscriptions", TestReadInteraction::TestReadHandler_KillOldestSubscriptions), - NL_TEST_DEF("TestReadHandler_ParallelReads", TestReadInteraction::TestReadHandler_ParallelReads), - NL_TEST_DEF("TestReadHandler_TooManyPaths", TestReadInteraction::TestReadHandler_TooManyPaths), - NL_TEST_DEF("TestReadHandler_TwoParallelReadsSecondTooManyPaths", TestReadInteraction::TestReadHandler_TwoParallelReadsSecondTooManyPaths), - NL_TEST_DEF("TestReadAttribute_ManyDataValues", TestReadInteraction::TestReadAttribute_ManyDataValues), - NL_TEST_DEF("TestReadAttribute_ManyDataValuesWrongPath", TestReadInteraction::TestReadAttribute_ManyDataValuesWrongPath), - NL_TEST_DEF("TestReadAttribute_ManyErrors", TestReadInteraction::TestReadAttribute_ManyErrors), - NL_TEST_DEF("TestSubscribeAttributeDeniedNotExistPath", TestReadInteraction::TestSubscribeAttributeDeniedNotExistPath), - NL_TEST_DEF("TestResubscribeAttributeTimeout", TestReadInteraction::TestResubscribeAttributeTimeout), - NL_TEST_DEF("TestSubscribeAttributeTimeout", TestReadInteraction::TestSubscribeAttributeTimeout), - NL_TEST_DEF("TestReadHandler_KeepSubscriptionTest", TestReadInteraction::TestReadHandler_KeepSubscriptionTest), - NL_TEST_DEF("TestSubscribe_OnActiveModeNotification", TestReadInteraction::TestSubscribe_OnActiveModeNotification), - NL_TEST_DEF("TestSubscribe_ImmediatelyResubscriptionForLIT", TestReadInteraction::TestSubscribe_ImmediatelyResubscriptionForLIT), - NL_TEST_DEF("TestSubscribe_DynamicLITSubscription", TestReadInteraction::TestSubscribe_DynamicLITSubscription), - NL_TEST_SENTINEL() -}; -// clang-format on - -nlTestSuite sSuite = { - "TestRead", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestReadInteractionTest() -{ - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestReadInteractionTest) diff --git a/src/controller/tests/data_model/TestWrite.cpp b/src/controller/tests/data_model/TestWrite.cpp index 077c9770852447..28f57970dd6b6c 100644 --- a/src/controller/tests/data_model/TestWrite.cpp +++ b/src/controller/tests/data_model/TestWrite.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "app-common/zap-generated/ids/Clusters.h" #include #include @@ -23,11 +25,8 @@ #include #include #include -#include -#include #include #include -#include #include using TestContext = chip::Test::AppContext; @@ -185,25 +184,45 @@ CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDesc namespace { -class TestWriteInteraction +class TestWrite : public ::testing::Test { public: - TestWriteInteraction() {} - - static void TestDataResponse(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseWithAcceptedDataVersion(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseWithRejectedDataVersion(nlTestSuite * apSuite, void * apContext); - static void TestAttributeError(nlTestSuite * apSuite, void * apContext); - static void TestFabricScopedAttributeWithoutFabricIndex(nlTestSuite * apSuite, void * apContext); - static void TestWriteTimeout(nlTestSuite * apSuite, void * apContext); - static void TestMultipleSuccessResponses(nlTestSuite * apSuite, void * apContext); - static void TestMultipleFailureResponses(nlTestSuite * apSuite, void * apContext); + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each individual test in the test suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each individual test in the test suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; }; +TestContext * TestWrite::mpContext = nullptr; -void TestWriteInteraction::TestDataResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestDataResponse) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; @@ -232,17 +251,16 @@ void TestWriteInteraction::TestDataResponse(nlTestSuite * apSuite, void * apCont chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestDataResponseWithAcceptedDataVersion(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestDataResponseWithAcceptedDataVersion) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; @@ -273,17 +291,16 @@ void TestWriteInteraction::TestDataResponseWithAcceptedDataVersion(nlTestSuite * chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestDataResponseWithRejectedDataVersion(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestDataResponseWithRejectedDataVersion) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; @@ -313,17 +330,16 @@ void TestWriteInteraction::TestDataResponseWithRejectedDataVersion(nlTestSuite * chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestAttributeError(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestAttributeError) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Attributes::ListStructOctetString::TypeInfo::Type value; Structs::TestListStructOctet::Type valueBuf[4]; @@ -345,25 +361,24 @@ void TestWriteInteraction::TestAttributeError(nlTestSuite * apSuite, void * apCo // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [apSuite, &onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { - NL_TEST_ASSERT(apSuite, attributePath != nullptr); + auto onFailureCb = [&onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + EXPECT_TRUE(attributePath != nullptr); onFailureCbInvoked = true; }; Controller::WriteAttribute(sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); + EXPECT_EQ(InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestFabricScopedAttributeWithoutFabricIndex(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestFabricScopedAttributeWithoutFabricIndex) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestFabricScoped::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListFabricScoped::TypeInfo::Type value; @@ -383,25 +398,24 @@ void TestWriteInteraction::TestFabricScopedAttributeWithoutFabricIndex(nlTestSui // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked, &apSuite](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { - NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); + auto onFailureCb = [&onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + EXPECT_EQ(aError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); onFailureCbInvoked = true; }; chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestMultipleSuccessResponses(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestMultipleSuccessResponses) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -418,18 +432,17 @@ void TestWriteInteraction::TestMultipleSuccessResponses(nlTestSuite * apSuite, v chip::Controller::WriteAttribute(sessionHandle, kTestEndpointId, true, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 1); - NL_TEST_ASSERT(apSuite, failureCalls == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 1u); + EXPECT_EQ(failureCalls, 0u); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -void TestWriteInteraction::TestMultipleFailureResponses(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWrite, TestMultipleFailureResponses) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; @@ -446,40 +459,12 @@ void TestWriteInteraction::TestMultipleFailureResponses(nlTestSuite * apSuite, v chip::Controller::WriteAttribute(sessionHandle, kTestEndpointId, true, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, successCalls == 0); - NL_TEST_ASSERT(apSuite, failureCalls == 1); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers() == 0); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(successCalls, 0u); + EXPECT_EQ(failureCalls, 1u); + EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } -const nlTest sTests[] = { - NL_TEST_DEF("TestDataResponse", TestWriteInteraction::TestDataResponse), - NL_TEST_DEF("TestDataResponseWithAcceptedDataVersion", TestWriteInteraction::TestDataResponseWithAcceptedDataVersion), - NL_TEST_DEF("TestDataResponseWithRejectedDataVersion", TestWriteInteraction::TestDataResponseWithRejectedDataVersion), - NL_TEST_DEF("TestAttributeError", TestWriteInteraction::TestAttributeError), - NL_TEST_DEF("TestWriteFabricScopedAttributeWithoutFabricIndex", - TestWriteInteraction::TestFabricScopedAttributeWithoutFabricIndex), - NL_TEST_DEF("TestMultipleSuccessResponses", TestWriteInteraction::TestMultipleSuccessResponses), - NL_TEST_DEF("TestMultipleFailureResponses", TestWriteInteraction::TestMultipleFailureResponses), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestWrite", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestWriteInteractionTest() -{ - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestWriteInteractionTest) diff --git a/src/test_driver/openiotsdk/unit-tests/test_components.txt b/src/test_driver/openiotsdk/unit-tests/test_components.txt index a509669979de01..8c59caffcf8de4 100644 --- a/src/test_driver/openiotsdk/unit-tests/test_components.txt +++ b/src/test_driver/openiotsdk/unit-tests/test_components.txt @@ -17,4 +17,6 @@ SetupPayloadTests SupportTests UserDirectedCommissioningTests SecureChannelTests -ICDServerTests \ No newline at end of file +ICDServerTests +DataModelTests +InetLayerTests diff --git a/src/test_driver/openiotsdk/unit-tests/test_components_nl.txt b/src/test_driver/openiotsdk/unit-tests/test_components_nl.txt index 941d4863204b16..55ebc4e3cd7e7e 100644 --- a/src/test_driver/openiotsdk/unit-tests/test_components_nl.txt +++ b/src/test_driver/openiotsdk/unit-tests/test_components_nl.txt @@ -1,4 +1,3 @@ AppTests -DataModelTests MessagingLayerTests SecureChannelTestsNL From 0180ee62a6eb01190db6103af0ebef47960fc9bd Mon Sep 17 00:00:00 2001 From: feasel <120589145+feasel0@users.noreply.github.com> Date: Mon, 13 May 2024 11:09:16 -0400 Subject: [PATCH 06/10] Converted src/controller/tests from NL to PW unit tests. (#33351) * Converted controller/tests from NL to PW unit tests. * Formatting * Omitted ControllerTests from test_components.txt --- src/controller/tests/BUILD.gn | 6 +- .../TestCommissionableNodeController.cpp | 106 ++---- src/controller/tests/TestEventCaching.cpp | 310 +++++++++--------- src/controller/tests/TestEventChunking.cpp | 215 ++++++------ .../tests/TestEventNumberCaching.cpp | 161 ++++----- src/controller/tests/TestReadChunking.cpp | 278 ++++++++-------- .../tests/TestServerCommandDispatch.cpp | 182 +++++----- src/controller/tests/TestWriteChunking.cpp | 251 +++++++------- 8 files changed, 690 insertions(+), 819 deletions(-) diff --git a/src/controller/tests/BUILD.gn b/src/controller/tests/BUILD.gn index 1d93c12c37053c..1e2b15d7778beb 100644 --- a/src/controller/tests/BUILD.gn +++ b/src/controller/tests/BUILD.gn @@ -14,11 +14,11 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") -import("//build_overrides/nlunit_test.gni") +import("//build_overrides/pigweed.gni") import("${chip_root}/build/chip/chip_test_suite.gni") -chip_test_suite_using_nltest("tests") { +chip_test_suite("tests") { output_name = "libControllerTests" test_sources = [ "TestCommissionableNodeController.cpp" ] @@ -40,10 +40,8 @@ chip_test_suite_using_nltest("tests") { "${chip_root}/src/app/tests:helpers", "${chip_root}/src/controller", "${chip_root}/src/lib/support:test_utils", - "${chip_root}/src/lib/support:testing_nlunit", "${chip_root}/src/messaging/tests:helpers", "${chip_root}/src/transport/raw/tests:helpers", - "${nlunit_test_root}:nlunit-test", ] if (chip_device_platform != "mbed") { diff --git a/src/controller/tests/TestCommissionableNodeController.cpp b/src/controller/tests/TestCommissionableNodeController.cpp index 5e866b4b25ea72..b68d0748d62c42 100644 --- a/src/controller/tests/TestCommissionableNodeController.cpp +++ b/src/controller/tests/TestCommissionableNodeController.cpp @@ -16,10 +16,10 @@ * limitations under the License. */ +#include + #include #include -#include -#include using namespace chip; using namespace chip::Dnssd; @@ -61,8 +61,15 @@ class MockResolver : public Resolver namespace { +class TestCommissionableNodeController : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + #if INET_CONFIG_ENABLE_IPV4 -void TestGetDiscoveredCommissioner_HappyCase(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_HappyCase) { MockResolver resolver; CommissionableNodeController controller(&resolver); @@ -76,14 +83,14 @@ void TestGetDiscoveredCommissioner_HappyCase(nlTestSuite * inSuite, void * inCon controller.OnNodeDiscovered(discNodeData); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0) != nullptr); - NL_TEST_ASSERT(inSuite, strcmp(inNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName) == 0); - NL_TEST_ASSERT(inSuite, inNodeData.ipAddress[0] == controller.GetDiscoveredCommissioner(0)->ipAddress[0]); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->port == 5540); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->numIPs == 1); + ASSERT_NE(controller.GetDiscoveredCommissioner(0), nullptr); + EXPECT_STREQ(inNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName); + EXPECT_EQ(inNodeData.ipAddress[0], controller.GetDiscoveredCommissioner(0)->ipAddress[0]); + EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->port, 5540); + EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->numIPs, 1u); } -void TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr) { MockResolver resolver; CommissionableNodeController controller(&resolver); @@ -98,11 +105,11 @@ void TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr(nlTestSu for (int i = 0; i < CHIP_DEVICE_CONFIG_MAX_DISCOVERED_NODES; i++) { - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(i) == nullptr); + EXPECT_EQ(controller.GetDiscoveredCommissioner(i), nullptr); } } -void TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode) { MockResolver resolver; CommissionableNodeController controller(&resolver); @@ -123,101 +130,56 @@ void TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode(nlTestSuite controller.OnNodeDiscovered(validDiscNodeData); controller.OnNodeDiscovered(invalidDiscNodeData); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0) != nullptr); - NL_TEST_ASSERT(inSuite, strcmp(validNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName) == 0); - NL_TEST_ASSERT(inSuite, validNodeData.ipAddress[0] == controller.GetDiscoveredCommissioner(0)->ipAddress[0]); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->port == 5540); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->numIPs == 1); + ASSERT_NE(controller.GetDiscoveredCommissioner(0), nullptr); + EXPECT_STREQ(validNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName); + EXPECT_EQ(validNodeData.ipAddress[0], controller.GetDiscoveredCommissioner(0)->ipAddress[0]); + EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->port, 5540); + EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->numIPs, 1u); - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(1) == nullptr); + EXPECT_EQ(controller.GetDiscoveredCommissioner(1), nullptr); } #endif // INET_CONFIG_ENABLE_IPV4 -void TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr) { MockResolver resolver; CommissionableNodeController controller(&resolver); for (int i = 0; i < CHIP_DEVICE_CONFIG_MAX_DISCOVERED_NODES; i++) { - NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(i) == nullptr); + EXPECT_EQ(controller.GetDiscoveredCommissioner(i), nullptr); } } -void TestDiscoverCommissioners_HappyCase(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_HappyCase) { MockResolver resolver; CommissionableNodeController controller(&resolver); - NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() == CHIP_NO_ERROR); + EXPECT_EQ(controller.DiscoverCommissioners(), CHIP_NO_ERROR); } -void TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter) { MockResolver resolver; CommissionableNodeController controller(&resolver); - NL_TEST_ASSERT(inSuite, - controller.DiscoverCommissioners(Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, 35)) == - CHIP_NO_ERROR); + EXPECT_EQ(controller.DiscoverCommissioners(Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, 35)), CHIP_NO_ERROR); } -void TestDiscoverCommissioners_InitError_ReturnsError(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_InitError_ReturnsError) { MockResolver resolver; resolver.InitStatus = CHIP_ERROR_INTERNAL; CommissionableNodeController controller(&resolver); - NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() != CHIP_NO_ERROR); + EXPECT_NE(controller.DiscoverCommissioners(), CHIP_NO_ERROR); } -void TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError(nlTestSuite * inSuite, void * inContext) +TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError) { MockResolver resolver; resolver.DiscoverCommissionersStatus = CHIP_ERROR_INTERNAL; CommissionableNodeController controller(&resolver); - NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() != CHIP_NO_ERROR); + EXPECT_NE(controller.DiscoverCommissioners(), CHIP_NO_ERROR); } -// clang-format off -const nlTest sTests[] = -{ -#if INET_CONFIG_ENABLE_IPV4 - NL_TEST_DEF("TestGetDiscoveredCommissioner_HappyCase", TestGetDiscoveredCommissioner_HappyCase), - NL_TEST_DEF("TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode", TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode), - NL_TEST_DEF("TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr", TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr), -#endif // INET_CONFIG_ENABLE_IPV4 - NL_TEST_DEF("TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr", TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr), - NL_TEST_DEF("TestDiscoverCommissioners_HappyCase", TestDiscoverCommissioners_HappyCase), - NL_TEST_DEF("TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter", TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter), - NL_TEST_DEF("TestDiscoverCommissioners_InitError_ReturnsError", TestDiscoverCommissioners_InitError_ReturnsError), - NL_TEST_DEF("TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError", TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError), - NL_TEST_SENTINEL() -}; -// clang-format on - } // namespace - -int TestCommissionableNodeController_Setup(void * inContext) -{ - if (CHIP_NO_ERROR != chip::Platform::MemoryInit()) - { - return FAILURE; - } - - return SUCCESS; -} - -int TestCommissionableNodeController_Teardown(void * inContext) -{ - chip::Platform::MemoryShutdown(); - return SUCCESS; -} - -int TestCommissionableNodeController() -{ - nlTestSuite theSuite = { "CommissionableNodeController", &sTests[0], TestCommissionableNodeController_Setup, - TestCommissionableNodeController_Teardown }; - nlTestRunner(&theSuite, nullptr); - return nlTestRunnerStats(&theSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestCommissionableNodeController) diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp index 3ff85ebdf96cdc..b4c262725f74d3 100644 --- a/src/controller/tests/TestEventCaching.cpp +++ b/src/controller/tests/TestEventCaching.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ClusterStateCache.h" @@ -33,12 +35,9 @@ #include #include #include -#include -#include #include #include #include -#include using namespace chip; using namespace chip::app; @@ -51,11 +50,43 @@ static uint8_t gInfoEventBuffer[4096]; static uint8_t gCritEventBuffer[4096]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; -class TestContext : public chip::Test::AppContext +using TestContext = chip::Test::AppContext; + +// +// The generated endpoint_config for the controller app has Endpoint 1 +// already used in the fixed endpoint set of size 1. Consequently, let's use the next +// number higher than that for our dynamic test endpoint. +// +constexpr EndpointId kTestEndpointId = 2; + +class TestEventCaching : public ::testing::Test { public: - // Performs setup for each individual test in the test suite - void SetUp() override + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { const chip::app::LogStorageResources logStorageResources[] = { { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug }, @@ -63,44 +94,29 @@ class TestContext : public chip::Test::AppContext { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - chip::Test::AppContext::SetUp(); + mpContext->SetUp(); CHIP_ERROR err = CHIP_NO_ERROR; // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer, "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format()); - chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), + chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); } - // Performs teardown for each individual test in the test suite - void TearDown() override + // Performs teardown for each test in the suite + void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - chip::Test::AppContext::TearDown(); + mpContext->TearDown(); } -private: - MonotonicallyIncreasingCounter mEventCounter; -}; - -nlTestSuite * gSuite = nullptr; - -// -// The generated endpoint_config for the controller app has Endpoint 1 -// already used in the fixed endpoint set of size 1. Consequently, let's use the next -// number higher than that for our dynamic test endpoint. -// -constexpr EndpointId kTestEndpointId = 2; - -class TestReadEvents -{ -public: - TestReadEvents() {} - static void TestBasicCaching(nlTestSuite * apSuite, void * apContext); + static TestContext * mpContext; private: + MonotonicallyIncreasingCounter mEventCounter; }; +TestContext * TestEventCaching::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -132,7 +148,7 @@ class TestReadCallback : public app::ClusterStateCache::Callback namespace { -void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) +void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) { CHIP_ERROR err = CHIP_NO_ERROR; static uint8_t generationCount = 0; @@ -142,7 +158,7 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, for (int i = 0; i < 5; i++) { content.arg1 = static_cast(generationCount++); - NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR); + EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR); if (i == 0) { firstEventNumber = lastEventNumber; @@ -161,10 +177,9 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, * events are present in the cache. * */ -void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) +TEST_F(TestEventCaching, TestBasicCaching) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -177,8 +192,8 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) chip::EventNumber firstEventNumber; chip::EventNumber lastEventNumber; - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); - NL_TEST_ASSERT(apSuite, lastEventNumber > firstEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); + EXPECT_GT(lastEventNumber, firstEventNumber); app::EventPathParams eventPath; eventPath.mEndpointId = kTestEndpointId; @@ -192,81 +207,81 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) TestReadCallback readCallback; { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); uint8_t generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, firstEventNumber); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1); Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); // // Re-run the iterator but pass in a path filter: EP*/TestCluster/EID* // generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, firstEventNumber); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }, app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId)); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1); // // Re-run the iterator but pass in a path filter: EP*/TestCluster/TestEvent // generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, firstEventNumber); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }, app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Events::TestEvent::Id)); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1); // // Re-run the iterator but pass in a min event number filter @@ -274,23 +289,23 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) // generationCount = 1; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber + 1); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, firstEventNumber + 1); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }, app::EventPathParams(), firstEventNumber + 1); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1); // // Re-run the iterator but pass in a min event number filter @@ -299,38 +314,38 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) // generationCount = 1; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber + 1); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, firstEventNumber + 1); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }, app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId), firstEventNumber + 1); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1); } // // Generate more events. // const EventNumber oldFirstEventNumber = firstEventNumber; - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Validate that we still have all 5 of the old events we received, as well as the new ones that just got generated. @@ -338,27 +353,27 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) // uint8_t generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, oldFirstEventNumber, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber >= oldFirstEventNumber); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, oldFirstEventNumber, lastEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GE(header.mEventNumber, oldFirstEventNumber); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - oldFirstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1); Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9); readCallback.mClusterCacheAdapter.ClearEventCache(); generationCount = 0; @@ -367,9 +382,9 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) return CHIP_NO_ERROR; }); - NL_TEST_ASSERT(apSuite, generationCount == 0); + EXPECT_EQ(generationCount, 0u); readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9); } // @@ -377,46 +392,46 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) // we don't receive events lower than that value. // { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(); constexpr EventNumber kLastSeenEventNumber = 3; - NL_TEST_ASSERT(apSuite, kLastSeenEventNumber < lastEventNumber); + EXPECT_LT(kLastSeenEventNumber, lastEventNumber); readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(kLastSeenEventNumber); readParams.mEventNumber.ClearValue(); readCallback.mEventsSeen = 0; - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // We should only get events with event numbers larger than kHighestEventNumberSeen. - NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - kLastSeenEventNumber); + EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kLastSeenEventNumber); uint8_t generationCount = kLastSeenEventNumber + 1; readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount, lastEventNumber](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); - NL_TEST_ASSERT(apSuite, header.mEventNumber > kLastSeenEventNumber); - NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber); + [&readCallback, &generationCount, lastEventNumber, kLastSeenEventNumber](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); + EXPECT_GT(header.mEventNumber, kLastSeenEventNumber); + EXPECT_LE(header.mEventNumber, lastEventNumber); Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); + EXPECT_EQ(eventData.arg1, generationCount); generationCount++; return CHIP_NO_ERROR; }); - NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - oldFirstEventNumber + 1); + EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1); Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); } // @@ -426,65 +441,42 @@ void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext) { readParams.mEventNumber.SetValue(5); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(true); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Validate that we would receive 5 events // uint8_t generationCount = 5; - readCallback.mClusterCacheAdapter.ForEachEventData( - [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) { - NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id); - NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId); + readCallback.mClusterCacheAdapter.ForEachEventData([&readCallback, &generationCount](const app::EventHeader & header) { + EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id); + EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id); + EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId); - Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; - NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR); + Clusters::UnitTesting::Events::TestEvent::DecodableType eventData; + EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount); - generationCount++; + EXPECT_EQ(eventData.arg1, generationCount); + generationCount++; - return CHIP_NO_ERROR; - }); + return CHIP_NO_ERROR; + }); - NL_TEST_ASSERT(apSuite, generationCount == 10); + EXPECT_EQ(generationCount, 10u); Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9); } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -const nlTest sTests[] = { - NL_TEST_DEF("TestBasicCaching", TestReadEvents::TestBasicCaching), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestEventCaching", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestEventCaching() -{ - gSuite = &sSuite; - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestEventCaching) diff --git a/src/controller/tests/TestEventChunking.cpp b/src/controller/tests/TestEventChunking.cpp index fb2b068fcbc0a0..c7790959e6866e 100644 --- a/src/controller/tests/TestEventChunking.cpp +++ b/src/controller/tests/TestEventChunking.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ConcreteAttributePath.h" @@ -38,12 +40,8 @@ #include #include #include -#include -#include -#include #include #include -#include using namespace chip; using namespace chip::app; @@ -56,11 +54,49 @@ static uint8_t gInfoEventBuffer[4096]; static uint8_t gCritEventBuffer[4096]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; -class TestContext : public chip::Test::AppContext +using TestContext = chip::Test::AppContext; + +uint32_t gIterationCount = 0; + +// +// The generated endpoint_config for the controller app has Endpoint 1 +// already used in the fixed endpoint set of size 1. Consequently, let's use the next +// number higher than that for our dynamic test endpoint. +// +constexpr EndpointId kTestEndpointId = 2; +constexpr AttributeId kTestListLargeAttribute = 8; // This attribute will be larger than the event size we used in this test. + +// The size of the attribute which is a bit larger than the size of event used in the test. +constexpr size_t kSizeOfLargeAttribute = 60; + +class TestEventChunking : public ::testing::Test { public: - // Performs setup for each individual test in the test suite - void SetUp() override + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { const chip::app::LogStorageResources logStorageResources[] = { { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug }, @@ -68,51 +104,29 @@ class TestContext : public chip::Test::AppContext { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - chip::Test::AppContext::SetUp(); + mpContext->SetUp(); CHIP_ERROR err = CHIP_NO_ERROR; // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer, "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format()); - chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), + chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); } - // Performs teardown for each individual test in the test suite - void TearDown() override + // Performs teardown for each test in the suite + void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - chip::Test::AppContext::TearDown(); + mpContext->TearDown(); } -private: - MonotonicallyIncreasingCounter mEventCounter; -}; - -uint32_t gIterationCount = 0; -nlTestSuite * gSuite = nullptr; - -// -// The generated endpoint_config for the controller app has Endpoint 1 -// already used in the fixed endpoint set of size 1. Consequently, let's use the next -// number higher than that for our dynamic test endpoint. -// -constexpr EndpointId kTestEndpointId = 2; -constexpr AttributeId kTestListLargeAttribute = 8; // This attribute will be larger than the event size we used in this test. - -// The size of the attribute which is a bit larger than the size of event used in the test. -constexpr size_t kSizeOfLargeAttribute = 60; - -class TestReadEvents -{ -public: - TestReadEvents() {} - static void TestEventChunking(nlTestSuite * apSuite, void * apContext); - static void TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, void * apContext); - static void TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apSuite, void * apContext); + static TestContext * mpContext; private: + MonotonicallyIncreasingCounter mEventCounter; }; +TestContext * TestEventChunking::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -167,30 +181,30 @@ void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aP if (aPath.mAttributeId == Globals::Attributes::GeneratedCommandList::Id) { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; while (it.Next()) { - NL_TEST_ASSERT(gSuite, false); + FAIL(); } - NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 0); + EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 0u); } else if (aPath.mAttributeId == Globals::Attributes::AcceptedCommandList::Id) { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; while (it.Next()) { - NL_TEST_ASSERT(gSuite, false); + FAIL(); } - NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 0); + EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 0u); } #if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE else if (aPath.mAttributeId == Globals::Attributes::EventList::Id) @@ -205,17 +219,17 @@ void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aP else if (aPath.mAttributeId == kTestListLargeAttribute) { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 4); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 4u); } else { uint8_t v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v == (uint8_t) gIterationCount); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); + EXPECT_EQ(v, (uint8_t) gIterationCount); } mAttributeCount++; } @@ -267,9 +281,7 @@ CHIP_ERROR TestAttrAccess::Write(const app::ConcreteDataAttributePath & aPath, a return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } -namespace { - -void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) +void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -278,7 +290,7 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, for (int i = 0; i < 5; i++) { - NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR); + EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR); if (i == 0) { firstEventNumber = lastEventNumber; @@ -286,8 +298,6 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, } } -} // namespace - /* * This validates all the various corner cases encountered during chunking by * artificially reducing the size of a packet buffer used to encode attribute & event data @@ -305,10 +315,9 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, * as we can possibly cover. * */ -void TestReadEvents::TestEventChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestEventChunking, TestEventChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -321,7 +330,7 @@ void TestReadEvents::TestEventChunking(nlTestSuite * apSuite, void * apContext) chip::EventNumber firstEventNumber; chip::EventNumber lastEventNumber; - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); app::EventPathParams eventPath; eventPath.mEndpointId = kTestEndpointId; @@ -349,20 +358,20 @@ void TestReadEvents::TestEventChunking(nlTestSuite * apSuite, void * apContext) app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast(800 + i)); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast((lastEventNumber - firstEventNumber) + 1)); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(readCallback.mEventCount, static_cast((lastEventNumber - firstEventNumber) + 1)); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } @@ -372,10 +381,9 @@ void TestReadEvents::TestEventChunking(nlTestSuite * apSuite, void * apContext) } // Similar to the tests above, but it will read attributes AND events -void TestReadEvents::TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestEventChunking, TestMixedEventsAndAttributesChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -389,7 +397,7 @@ void TestReadEvents::TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, chip::EventNumber lastEventNumber; // We will always read from the first event, so it is enough to only generate events once. - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); app::EventPathParams eventPath; app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id); @@ -418,26 +426,26 @@ void TestReadEvents::TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast(800 + i)); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Always returns the same number of attributes read (5 + revision + GlobalAttributesNotInMetadata). // - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 6 + ArraySize(GlobalAttributesNotInMetadata)); - NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast(lastEventNumber - firstEventNumber + 1)); + EXPECT_TRUE(readCallback.mOnReportEnd); + EXPECT_EQ(readCallback.mAttributeCount, 6 + ArraySize(GlobalAttributesNotInMetadata)); + EXPECT_EQ(readCallback.mEventCount, static_cast(lastEventNumber - firstEventNumber + 1)); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } @@ -449,10 +457,9 @@ void TestReadEvents::TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, // Similar to the tests above, however, there is one another case -- the event payload is very large usually, so when it is failed // to encode an attribute, it is usually impossible to encode a event data, so we cannot verify the case when events and attributes // can be encoded in to one chunk in the tests above. This test will force it by reading only one attribtue and read many events. -void TestReadEvents::TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestEventChunking, TestMixedEventsAndLargeAttributesChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -466,7 +473,7 @@ void TestReadEvents::TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apS chip::EventNumber lastEventNumber; // We will always read from the first event, so it is enough to only generate events once. - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); app::EventPathParams eventPath; app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListLargeAttribute); @@ -495,23 +502,23 @@ void TestReadEvents::TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apS app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast(800 + i)); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); - NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast(lastEventNumber - firstEventNumber + 1)); + EXPECT_TRUE(readCallback.mOnReportEnd); + EXPECT_EQ(readCallback.mAttributeCount, 1u); + EXPECT_EQ(readCallback.mEventCount, static_cast(lastEventNumber - firstEventNumber + 1)); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } @@ -520,28 +527,4 @@ void TestReadEvents::TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apS emberAfClearDynamicEndpoint(0); } -const nlTest sTests[] = { - NL_TEST_DEF("TestEventChunking", TestReadEvents::TestEventChunking), - NL_TEST_DEF("TestMixedEventsAndAttributesChunking", TestReadEvents::TestMixedEventsAndAttributesChunking), - NL_TEST_DEF("TestMixedEventsAndLargeAttributesChunking", TestReadEvents::TestMixedEventsAndLargeAttributesChunking), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestEventChunking", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestEventChunkingTests() -{ - gSuite = &sSuite; - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestEventChunkingTests) diff --git a/src/controller/tests/TestEventNumberCaching.cpp b/src/controller/tests/TestEventNumberCaching.cpp index 1244a77481209e..4d77813262a691 100644 --- a/src/controller/tests/TestEventNumberCaching.cpp +++ b/src/controller/tests/TestEventNumberCaching.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "app-common/zap-generated/ids/Clusters.h" #include "app/ClusterStateCache.h" #include @@ -30,12 +32,8 @@ #include #include #include -#include -#include -#include #include #include -#include using namespace chip; using namespace chip::app; @@ -48,11 +46,43 @@ static uint8_t gInfoEventBuffer[4096]; static uint8_t gCritEventBuffer[4096]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; -class TestContext : public chip::Test::AppContext +using TestContext = chip::Test::AppContext; + +// +// The generated endpoint_config for the controller app has Endpoint 1 +// already used in the fixed endpoint set of size 1. Consequently, let's use the next +// number higher than that for our dynamic test endpoint. +// +constexpr EndpointId kTestEndpointId = 2; + +class TestEventNumberCaching : public ::testing::Test { public: - // Performs setup for each individual test in the test suite - void SetUp() override + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { const chip::app::LogStorageResources logStorageResources[] = { { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug }, @@ -60,44 +90,29 @@ class TestContext : public chip::Test::AppContext { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - chip::Test::AppContext::SetUp(); + mpContext->SetUp(); CHIP_ERROR err = CHIP_NO_ERROR; // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer, "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format()); - chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), + chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); } - // Performs teardown for each individual test in the test suite - void TearDown() override + // Performs teardown for each test in the suite + void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - chip::Test::AppContext::TearDown(); + mpContext->TearDown(); } -private: - MonotonicallyIncreasingCounter mEventCounter; -}; - -nlTestSuite * gSuite = nullptr; - -// -// The generated endpoint_config for the controller app has Endpoint 1 -// already used in the fixed endpoint set of size 1. Consequently, let's use the next -// number higher than that for our dynamic test endpoint. -// -constexpr EndpointId kTestEndpointId = 2; - -class TestReadEvents -{ -public: - TestReadEvents() {} - static void TestEventNumberCaching(nlTestSuite * apSuite, void * apContext); + static TestContext * mpContext; private: + MonotonicallyIncreasingCounter mEventCounter; }; +TestContext * TestEventNumberCaching::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -127,9 +142,7 @@ class TestReadCallback : public app::ClusterStateCache::Callback size_t mEventsSeen = 0; }; -namespace { - -void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) +void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber) { CHIP_ERROR err = CHIP_NO_ERROR; static uint8_t generationCount = 0; @@ -139,7 +152,7 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, for (int i = 0; i < 5; i++) { content.arg1 = static_cast(generationCount++); - NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR); + EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR); if (i == 0) { firstEventNumber = lastEventNumber; @@ -147,18 +160,15 @@ void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, } } -} // namespace - /* * This validates event caching by forcing a bunch of events to get generated, then reading them back * and upon completion of that operation, check the received version from cache, and note that cache would store * correpsonding attribute data since data cache is disabled. * */ -void TestReadEvents::TestEventNumberCaching(nlTestSuite * apSuite, void * apContext) +TEST_F(TestEventNumberCaching, TestEventNumberCaching) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -171,8 +181,8 @@ void TestReadEvents::TestEventNumberCaching(nlTestSuite * apSuite, void * apCont chip::EventNumber firstEventNumber; chip::EventNumber lastEventNumber; - GenerateEvents(apSuite, firstEventNumber, lastEventNumber); - NL_TEST_ASSERT(apSuite, lastEventNumber > firstEventNumber); + GenerateEvents(firstEventNumber, lastEventNumber); + EXPECT_GT(lastEventNumber, firstEventNumber); app::EventPathParams eventPath; eventPath.mEndpointId = kTestEndpointId; @@ -188,25 +198,24 @@ void TestReadEvents::TestEventNumberCaching(nlTestSuite * apSuite, void * apCont { Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, !highestEventNumber.HasValue()); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + EXPECT_FALSE(highestEventNumber.HasValue()); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - firstEventNumber + 1); + EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - firstEventNumber + 1); - readCallback.mClusterCacheAdapter.ForEachEventData([&apSuite](const app::EventHeader & header) { + readCallback.mClusterCacheAdapter.ForEachEventData([](const app::EventHeader & header) { // We are not caching data. - NL_TEST_ASSERT(apSuite, false); - + ADD_FAILURE(); // Can't use FAIL() because lambda has non-void return type. return CHIP_NO_ERROR; }); readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); } // @@ -214,68 +223,42 @@ void TestReadEvents::TestEventNumberCaching(nlTestSuite * apSuite, void * apCont // we don't receive events except ones larger than that value. // { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(true); Optional highestEventNumber; readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, !highestEventNumber.HasValue()); + EXPECT_FALSE(highestEventNumber.HasValue()); const EventNumber kHighestEventNumberSeen = lastEventNumber - 1; - NL_TEST_ASSERT(apSuite, kHighestEventNumberSeen < lastEventNumber); + EXPECT_LT(kHighestEventNumberSeen, lastEventNumber); readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(kHighestEventNumberSeen); readCallback.mEventsSeen = 0; readParams.mEventNumber.ClearValue(); - NL_TEST_ASSERT(apSuite, !readParams.mEventNumber.HasValue()); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_FALSE(readParams.mEventNumber.HasValue()); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // We should only get events with event numbers larger than kHighestEventNumberSeen. - NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - kHighestEventNumberSeen); + EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kHighestEventNumberSeen); - readCallback.mClusterCacheAdapter.ForEachEventData([&apSuite](const app::EventHeader & header) { + readCallback.mClusterCacheAdapter.ForEachEventData([](const app::EventHeader & header) { // We are not caching data. - NL_TEST_ASSERT(apSuite, false); - + ADD_FAILURE(); // Can't use FAIL() because lambda has non-void return type. return CHIP_NO_ERROR; }); readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber); - NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); + EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber); } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -const nlTest sTests[] = { - NL_TEST_DEF("TestEventNumberCaching", TestReadEvents::TestEventNumberCaching), - NL_TEST_SENTINEL(), -}; - -// clang-format off -nlTestSuite sSuite = -{ - "TestEventNumberCaching", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; -// clang-format on - } // namespace - -int TestEventNumberCaching() -{ - gSuite = &sSuite; - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestEventNumberCaching) diff --git a/src/controller/tests/TestReadChunking.cpp b/src/controller/tests/TestReadChunking.cpp index 1095e0f26ebf61..8fe5e51c2b097f 100644 --- a/src/controller/tests/TestReadChunking.cpp +++ b/src/controller/tests/TestReadChunking.cpp @@ -16,6 +16,12 @@ * limitations under the License. */ +#include +#include +#include + +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ConcreteAttributePath.h" @@ -34,17 +40,11 @@ #include #include #include -#include #include #include -#include -#include #include #include -#include #include -#include -#include using TestContext = chip::Test::AppContext; using namespace chip; @@ -54,8 +54,6 @@ using namespace chip::app::Clusters; namespace { uint32_t gIterationCount = 0; -nlTestSuite * gSuite = nullptr; -TestContext * gCtx = nullptr; // // The generated endpoint_config for the controller app has Endpoint 1 @@ -74,18 +72,41 @@ constexpr AttributeId kTestBadAttribute = constexpr int kListAttributeItems = 5; -class TestReadChunking +class TestReadChunking : public ::testing::Test { public: - TestReadChunking() {} - static void TestChunking(nlTestSuite * apSuite, void * apContext); - static void TestListChunking(nlTestSuite * apSuite, void * apContext); - static void TestBadChunking(nlTestSuite * apSuite, void * apContext); - static void TestDynamicEndpoint(nlTestSuite * apSuite, void * apContext); - static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext); - -private: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each test in the suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; }; +TestContext * TestReadChunking::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -271,30 +292,30 @@ void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aP if (aPath.mAttributeId == Globals::Attributes::GeneratedCommandList::Id) { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; while (it.Next()) { - NL_TEST_ASSERT(gSuite, false); + FAIL(); } - NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 0); + EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 0u); } else if (aPath.mAttributeId == Globals::Attributes::AcceptedCommandList::Id) { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; while (it.Next()) { - NL_TEST_ASSERT(gSuite, false); + FAIL(); } - NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 0); + EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 0u); } #if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE else if (aPath.mAttributeId == Globals::Attributes::EventList::Id) @@ -309,22 +330,22 @@ void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aP else if (aPath.mAttributeId != kTestListAttribute) { uint8_t v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v == (uint8_t) gIterationCount); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); + EXPECT_EQ(v, (uint8_t) gIterationCount); } else { app::DataModel::DecodableList v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); auto it = v.begin(); size_t arraySize = 0; while (it.Next()) { - NL_TEST_ASSERT(gSuite, it.GetValue() == static_cast(gIterationCount)); + EXPECT_EQ(it.GetValue(), static_cast(gIterationCount)); } - NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR); - NL_TEST_ASSERT(gSuite, arraySize == 5); + EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); + EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR); + EXPECT_EQ(arraySize, 5u); } mAttributeCount++; } @@ -448,13 +469,13 @@ void TestMutableReadCallback::OnAttributeData(const app::ConcreteDataAttributePa const app::StatusIB & aStatus) { VerifyOrReturn(apData != nullptr); - NL_TEST_ASSERT(gSuite, aPath.mClusterId == Clusters::UnitTesting::Id); + EXPECT_EQ(aPath.mClusterId, Clusters::UnitTesting::Id); mAttributeCount++; if (aPath.mAttributeId <= 5) { uint8_t v; - NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR); + EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR); mValues[std::make_pair(aPath.mEndpointId, aPath.mAttributeId)] = v; auto action = mActionOn.find(std::make_pair(aPath.mEndpointId, aPath.mAttributeId)); @@ -488,10 +509,9 @@ void TestMutableReadCallback::OnAttributeData(const app::ConcreteDataAttributePa * as we can possibly cover. * */ -void TestReadChunking::TestChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestReadChunking, TestChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -522,26 +542,26 @@ void TestReadChunking::TestChunking(nlTestSuite * apSuite, void * apContext) app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast(850 + i)); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); + mpContext->DrainAndServiceIO(); + EXPECT_TRUE(readCallback.mOnReportEnd); // // Always returns the same number of attributes read (5 + revision + GlobalAttributesNotInMetadata). // - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 6 + ArraySize(GlobalAttributesNotInMetadata)); + EXPECT_EQ(readCallback.mAttributeCount, 6 + ArraySize(GlobalAttributesNotInMetadata)); readCallback.mAttributeCount = 0; - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } @@ -551,10 +571,9 @@ void TestReadChunking::TestChunking(nlTestSuite * apSuite, void * apContext) } // Similar to the test above, but for the list chunking feature. -void TestReadChunking::TestListChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestReadChunking, TestListChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -598,12 +617,12 @@ void TestReadChunking::TestListChunking(nlTestSuite * apSuite, void * apContext) app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved( static_cast(maxPacketSize - packetSize)); - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Up until our packets are big enough, we might just keep getting // errors due to the inability to encode even a single IB in a packet. @@ -612,14 +631,13 @@ void TestReadChunking::TestListChunking(nlTestSuite * apSuite, void * apContext) { gotFailureResponse = true; // Check for the right error type. - NL_TEST_ASSERT(apSuite, - StatusIB(readCallback.mReadError).mStatus == Protocols::InteractionModel::Status::ResourceExhausted); + EXPECT_EQ(StatusIB(readCallback.mReadError).mStatus, Protocols::InteractionModel::Status::ResourceExhausted); } else { gotSuccessfulEncode = true; - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); + EXPECT_TRUE(readCallback.mOnReportEnd); // // Always returns the same number of attributes read (merged by buffered read callback). The content is checked in @@ -628,36 +646,35 @@ void TestReadChunking::TestListChunking(nlTestSuite * apSuite, void * apContext) // just a replace of the first read's path and buffers it all up as a // single value. // - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + EXPECT_EQ(readCallback.mAttributeCount, 1u); readCallback.mAttributeCount = 0; // Check that we never saw an empty-list data IB. - NL_TEST_ASSERT(apSuite, !readCallback.mBufferedCallback.mDecodingFailed); - NL_TEST_ASSERT(apSuite, !readCallback.mBufferedCallback.mSawEmptyList); + EXPECT_FALSE(readCallback.mBufferedCallback.mDecodingFailed); + EXPECT_FALSE(readCallback.mBufferedCallback.mSawEmptyList); } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } } // If this fails, our smallest packet size was not small enough. - NL_TEST_ASSERT(apSuite, gotFailureResponse); + EXPECT_TRUE(gotFailureResponse); emberAfClearDynamicEndpoint(0); } // Read an attribute that can never fit into the buffer. Result in an empty report, server should shutdown the transaction. -void TestReadChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestReadChunking, TestBadChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -678,27 +695,27 @@ void TestReadChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) TestReadCallback readCallback; { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Read); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // The server should return an empty list as attribute data for the first report (for list chunking), and encodes nothing // (then shuts down the read handler) for the second report. // // Nothing is actually encoded. buffered callback does not handle the message to us. - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 0); - NL_TEST_ASSERT(apSuite, !readCallback.mOnReportEnd); + EXPECT_EQ(readCallback.mAttributeCount, 0u); + EXPECT_FALSE(readCallback.mOnReportEnd); // The server should shutted down, while the client is still alive (pending for the attribute data.) - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } // Sanity check - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -706,10 +723,9 @@ void TestReadChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) /* * This test contains two parts, one is to enable a new endpoint on the fly, another is to disable it and re-enable it. */ -void TestReadChunking::TestDynamicEndpoint(nlTestSuite * apSuite, void * apContext) +TEST_F(TestReadChunking, TestDynamicEndpoint) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -729,30 +745,28 @@ void TestReadChunking::TestDynamicEndpoint(nlTestSuite * apSuite, void * apConte { - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Subscribe); // Enable the new endpoint emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span(dataVersionStorage)); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablished); + EXPECT_TRUE(readCallback.mOnSubscriptionEstablished); readCallback.mAttributeCount = 0; emberAfSetDynamicEndpoint(0, kTestEndpointId4, &testEndpoint4, Span(dataVersionStorage)); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Ensure we have received the report, we do not care about the initial report here. // GlobalAttributesNotInMetadata attributes are not included in testClusterAttrsOnEndpoint4. - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == - ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata)); + EXPECT_EQ(readCallback.mAttributeCount, ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata)); // We have received all report data. - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); + EXPECT_TRUE(readCallback.mOnReportEnd); readCallback.mAttributeCount = 0; readCallback.mOnReportEnd = false; @@ -760,7 +774,7 @@ void TestReadChunking::TestDynamicEndpoint(nlTestSuite * apSuite, void * apConte // Disable the new endpoint emberAfEndpointEnableDisable(kTestEndpointId4, false); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // We may receive some attribute reports for descriptor cluster, but we do not care about it for now. @@ -770,24 +784,22 @@ void TestReadChunking::TestDynamicEndpoint(nlTestSuite * apSuite, void * apConte readCallback.mOnReportEnd = false; emberAfEndpointEnableDisable(kTestEndpointId4, true); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // Ensure we have received the report, we do not care about the initial report here. // GlobalAttributesNotInMetadata attributes are not included in testClusterAttrsOnEndpoint4. - NL_TEST_ASSERT(apSuite, - readCallback.mAttributeCount == - ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata)); + EXPECT_EQ(readCallback.mAttributeCount, ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata)); // We have received all report data. - NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd); + EXPECT_TRUE(readCallback.mOnReportEnd); } chip::test_utils::SleepMillis(SecondsToMilliseconds(2)); // Destroying the read client will terminate the subscription transaction. - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -845,20 +857,20 @@ struct Instruction std::vector attributesWithSameDataVersion; }; -void DriveIOUntilSubscriptionEstablished(TestMutableReadCallback * callback) +void DriveIOUntilSubscriptionEstablished(TestContext * pContext, TestMutableReadCallback * callback) { callback->mOnReportEnd = false; - gCtx->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnSubscriptionEstablished; }); - NL_TEST_ASSERT(gSuite, callback->mOnReportEnd); - NL_TEST_ASSERT(gSuite, callback->mOnSubscriptionEstablished); + pContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnSubscriptionEstablished; }); + EXPECT_TRUE(callback->mOnReportEnd); + EXPECT_TRUE(callback->mOnSubscriptionEstablished); callback->mActionOn.clear(); } -void DriveIOUntilEndOfReport(TestMutableReadCallback * callback) +void DriveIOUntilEndOfReport(TestContext * pContext, TestMutableReadCallback * callback) { callback->mOnReportEnd = false; - gCtx->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnReportEnd; }); - NL_TEST_ASSERT(gSuite, callback->mOnReportEnd); + pContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnReportEnd; }); + EXPECT_TRUE(callback->mOnReportEnd); callback->mActionOn.clear(); } @@ -866,7 +878,7 @@ void CheckValues(TestMutableReadCallback * callback, std::vectormValues[vals.first] == vals.second); + EXPECT_EQ(callback->mValues[vals.first], vals.second); } } @@ -879,11 +891,11 @@ void ExpectSameDataVersions(TestMutableReadCallback * callback, AttributesList a DataVersion expectedVersion = callback->mDataVersions[attrList[0]]; for (const auto & attr : attrList) { - NL_TEST_ASSERT(gSuite, callback->mDataVersions[attr] == expectedVersion); + EXPECT_EQ(callback->mDataVersions[attr], expectedVersion); } } -void DoTest(TestMutableReadCallback * callback, Instruction instruction) +void DoTest(TestContext * pContext, TestMutableReadCallback * callback, Instruction instruction) { app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(instruction.chunksize); @@ -892,7 +904,7 @@ void DoTest(TestMutableReadCallback * callback, Instruction instruction) act(); } - DriveIOUntilEndOfReport(callback); + DriveIOUntilEndOfReport(pContext, callback); CheckValues(callback, instruction.expectedValues); @@ -904,16 +916,12 @@ void DoTest(TestMutableReadCallback * callback, Instruction instruction) }; // namespace TestSetDirtyBetweenChunksUtil -void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext) +TEST_F(TestReadChunking, TestSetDirtyBetweenChunks) { using namespace TestSetDirtyBetweenChunksUtil; - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); - gCtx = &ctx; - gSuite = apSuite; - // Initialize the ember side server logic InitDataModelHandler(); @@ -945,10 +953,10 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a gIterationCount = 1; - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Subscribe); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); // CASE 1 -- Touch an attribute during priming report, then verify it is included in first report after priming report. { @@ -957,11 +965,11 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a // We are expected to miss attributes on kTestEndpointId during initial reports. ChipLogProgress(DataManagement, "Case 1-1: Set dirty during priming report."); readCallback.mActionOn[AttrOnEp5] = TouchAttrOp(AttrOnEp1); - DriveIOUntilSubscriptionEstablished(&readCallback); + DriveIOUntilSubscriptionEstablished(mpContext, &readCallback); CheckValues(&readCallback, { { AttrOnEp1, 1 } }); ChipLogProgress(DataManagement, "Case 1-2: Check for attributes missed last report."); - DoTest(&readCallback, Instruction{ .chunksize = 2, .expectedValues = { { AttrOnEp1, 2 } } }); + DoTest(mpContext, &readCallback, Instruction{ .chunksize = 2, .expectedValues = { { AttrOnEp1, 2 } } }); } // CASE 2 -- Set dirty during chunked report, the attribute is already dirty. @@ -969,7 +977,7 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a ChipLogProgress(DataManagement, "Case 2: Set dirty during chunked report by wildcard path."); readCallback.mActionOn[AttrOnEp5] = WriteAttrOp(AttrOnEp5, 3); DoTest( - &readCallback, + mpContext, &readCallback, Instruction{ .chunksize = 2, .preworks = { WriteAttrOp(AttrOnEp5, 2), WriteAttrOp(AttrOnEp5, 2), WriteAttrOp(AttrOnEp5, 2) }, @@ -983,7 +991,7 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a "Case 3-1: Set dirty during chunked report by wildcard path -- new dirty attribute."); readCallback.mActionOn[AttrOnEp5] = WriteAttrOp(AttrOnEp5, 4); DoTest( - &readCallback, + mpContext, &readCallback, Instruction{ .chunksize = 1, .preworks = { WriteAttrOp(AttrOnEp5, 4), WriteAttrOp(AttrOnEp5, 4) }, .expectedValues = { { AttrOnEp5, 4 }, { AttrOnEp5, 4 }, { AttrOnEp5, 4 } }, @@ -994,7 +1002,7 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(1); readCallback.mActionOn[AttrOnEp5] = WriteAttrOp(AttrOnEp5, 5); DoTest( - &readCallback, + mpContext, &readCallback, Instruction{ .chunksize = 1, .preworks = { WriteAttrOp(AttrOnEp5, 5), WriteAttrOp(AttrOnEp5, 5) }, .expectedValues = { { AttrOnEp5, 5 }, { AttrOnEp5, 5 }, { AttrOnEp5, 5 } }, @@ -1024,18 +1032,18 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a { TestMutableReadCallback readCallback; - app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback, + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback, app::ReadClient::InteractionType::Subscribe); - NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR); + EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - DriveIOUntilSubscriptionEstablished(&readCallback); + DriveIOUntilSubscriptionEstablished(mpContext, &readCallback); // Note, although the two attributes comes from the same cluster, they are generated by different interested paths. // In this case, we won't reset the path iterator. ChipLogProgress(DataManagement, "Case 1-1: Test set dirty during reports generated by concrete paths."); readCallback.mActionOn[AttrOnEp5] = WriteAttrOp(AttrOnEp5, 4); - DoTest(&readCallback, + DoTest(mpContext, &readCallback, Instruction{ .chunksize = 1, .preworks = { WriteAttrOp(AttrOnEp5, 3), WriteAttrOp(AttrOnEp5, 3), WriteAttrOp(AttrOnEp5, 3) }, @@ -1043,46 +1051,20 @@ void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * a // The attribute failed to catch last report will be picked by this report. ChipLogProgress(DataManagement, "Case 1-2: Check for attributes missed last report."); - DoTest(&readCallback, { .chunksize = 1, .expectedValues = { { AttrOnEp5, 4 } } }); + DoTest(mpContext, &readCallback, { .chunksize = 1, .expectedValues = { { AttrOnEp5, 4 } } }); } } chip::test_utils::SleepMillis(SecondsToMilliseconds(3)); // Destroying the read client will terminate the subscription transaction. - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(1); emberAfClearDynamicEndpoint(0); app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(UINT32_MAX); } -const nlTest sTests[] = { - NL_TEST_DEF("TestChunking", TestReadChunking::TestChunking), - NL_TEST_DEF("TestListChunking", TestReadChunking::TestListChunking), - NL_TEST_DEF("TestBadChunking", TestReadChunking::TestBadChunking), - NL_TEST_DEF("TestDynamicEndpoint", TestReadChunking::TestDynamicEndpoint), - NL_TEST_DEF("TestSetDirtyBetweenChunks", TestReadChunking::TestSetDirtyBetweenChunks), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestReadChunking", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestReadChunkingTests() -{ - gSuite = &sSuite; - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestReadChunkingTests) diff --git a/src/controller/tests/TestServerCommandDispatch.cpp b/src/controller/tests/TestServerCommandDispatch.cpp index a87ec5d1bcd8d3..41fab0ae0e3802 100644 --- a/src/controller/tests/TestServerCommandDispatch.cpp +++ b/src/controller/tests/TestServerCommandDispatch.cpp @@ -22,6 +22,8 @@ * */ +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "protocols/interaction_model/Constants.h" @@ -33,11 +35,8 @@ #include #include #include -#include -#include #include #include -#include using TestContext = chip::Test::AppContext; @@ -131,22 +130,45 @@ CHIP_ERROR TestClusterCommandHandler::EnumerateAcceptedCommands(const ConcreteCl namespace { -class TestCommandInteraction +class TestServerCommandDispatch : public ::testing::Test { public: - TestCommandInteraction() {} - static void TestNoHandler(nlTestSuite * apSuite, void * apContext); - static void TestDataResponse(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseNoCommand1(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseNoCommand2(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseNoCommand3(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseHandlerOverride1(nlTestSuite * apSuite, void * apContext); - static void TestDataResponseHandlerOverride2(nlTestSuite * apSuite, void * apContext); + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } -private: - static void TestDataResponseHelper(nlTestSuite * apSuite, void * apContext, const EmberAfEndpointType * aEndpoint, - bool aExpectSuccess); + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each test in the suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; + + // Helpers + + static void TestDataResponseHelper(const EmberAfEndpointType * aEndpoint, bool aExpectSuccess); }; +TestContext * TestServerCommandDispatch::mpContext = nullptr; // We want to send a TestSimpleArgumentRequest::Type, but get a // TestStructArrayArgumentResponse in return, so need to shadow the actual @@ -156,40 +178,37 @@ struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentR using ResponseType = Clusters::UnitTesting::Commands::TestStructArrayArgumentResponse::DecodableType; }; -void TestCommandInteraction::TestNoHandler(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestNoHandler) { - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); request.arg1 = true; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, - const auto & dataResponse) { + auto onSuccessCb = [](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, const auto & dataResponse) { // // We shouldn't be arriving here, since we don't have a command handler installed. // - NL_TEST_ASSERT(apSuite, false); + FAIL(); }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [apSuite](CHIP_ERROR aError) { - NL_TEST_ASSERT(apSuite, - aError.IsIMStatus() && - app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::UnsupportedEndpoint); + auto onFailureCb = [](CHIP_ERROR aError) { + EXPECT_TRUE(aError.IsIMStatus() && + app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::UnsupportedEndpoint); }; responseDirective = kSendDataResponse; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); } static const int kDescriptorAttributeArraySize = 254; @@ -239,12 +258,10 @@ DECLARE_DYNAMIC_CLUSTER(chip::app::Clusters::UnitTesting::Id, testClusterAttrs, DECLARE_DYNAMIC_ENDPOINT(testEndpoint3, testEndpointClusters3); -void TestCommandInteraction::TestDataResponseHelper(nlTestSuite * apSuite, void * apContext, const EmberAfEndpointType * aEndpoint, - bool aExpectSuccess) +void TestServerCommandDispatch::TestDataResponseHelper(const EmberAfEndpointType * aEndpoint, bool aExpectSuccess) { - TestContext & ctx = *static_cast(apContext); FakeRequest request; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool onSuccessWasCalled = false; bool onFailureWasCalled = false; @@ -263,23 +280,23 @@ void TestCommandInteraction::TestDataResponseHelper(nlTestSuite * apSuite, void // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, - const auto & dataResponse) { + auto onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, + const auto & dataResponse) { uint8_t i = 0; auto iter = dataResponse.arg1.begin(); while (iter.Next()) { auto & item = iter.GetValue(); - NL_TEST_ASSERT(apSuite, item.a == i); - NL_TEST_ASSERT(apSuite, item.b == false); - NL_TEST_ASSERT(apSuite, item.c.a == i); - NL_TEST_ASSERT(apSuite, item.c.b == true); + EXPECT_EQ(item.a, i); + EXPECT_FALSE(item.b); + EXPECT_EQ(item.c.a, i); + EXPECT_TRUE(item.c.b); i++; } - NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); - NL_TEST_ASSERT(apSuite, dataResponse.arg6 == true); + EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR); + EXPECT_TRUE(dataResponse.arg6); onSuccessWasCalled = true; }; @@ -290,36 +307,36 @@ void TestCommandInteraction::TestDataResponseHelper(nlTestSuite * apSuite, void responseDirective = kSendDataResponse; - chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, + chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled == aExpectSuccess && onFailureWasCalled != aExpectSuccess); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled == aExpectSuccess && onFailureWasCalled != aExpectSuccess); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); onSuccessWasCalled = false; onFailureWasCalled = false; - auto readSuccessCb = [apSuite, &onSuccessWasCalled, aExpectSuccess](const ConcreteDataAttributePath &, - const DataModel::DecodableList & commandList) { + auto readSuccessCb = [&onSuccessWasCalled, aExpectSuccess](const ConcreteDataAttributePath &, + const DataModel::DecodableList & commandList) { auto count = 0; auto iter = commandList.begin(); while (iter.Next()) { // We only expect 0 or 1 command ids here. - NL_TEST_ASSERT(apSuite, count == 0); - NL_TEST_ASSERT(apSuite, iter.GetValue() == Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Id); + EXPECT_EQ(count, 0); + EXPECT_EQ(iter.GetValue(), Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Id); ++count; } - NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); + EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR); if (aExpectSuccess) { - NL_TEST_ASSERT(apSuite, count == 1); + EXPECT_EQ(count, 1); } else { - NL_TEST_ASSERT(apSuite, count == 0); + EXPECT_EQ(count, 0); } onSuccessWasCalled = true; }; @@ -329,91 +346,60 @@ void TestCommandInteraction::TestDataResponseHelper(nlTestSuite * apSuite, void }; chip::Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, readSuccessCb, readFailureCb); + &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, readSuccessCb, readFailureCb); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponse) { TestClusterCommandHandler commandHandler; - TestDataResponseHelper(apSuite, apContext, &testEndpoint1, true); + TestDataResponseHelper(&testEndpoint1, true); } -void TestCommandInteraction::TestDataResponseNoCommand1(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand1) { // Check what happens if we don't claim our command id is supported, by // overriding the acceptedCommandList with an empty list. TestClusterCommandHandler commandHandler; commandHandler.OverrideAcceptedCommands(); commandHandler.ClaimNoCommands(); - TestDataResponseHelper(apSuite, apContext, &testEndpoint1, false); + TestDataResponseHelper(&testEndpoint1, false); } -void TestCommandInteraction::TestDataResponseNoCommand2(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand2) { // Check what happens if we don't claim our command id is supported, by // having an acceptedCommandList that ends immediately. TestClusterCommandHandler commandHandler; - TestDataResponseHelper(apSuite, apContext, &testEndpoint2, false); + TestDataResponseHelper(&testEndpoint2, false); } -void TestCommandInteraction::TestDataResponseNoCommand3(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand3) { // Check what happens if we don't claim our command id is supported, by // having an acceptedCommandList that is null. TestClusterCommandHandler commandHandler; - TestDataResponseHelper(apSuite, apContext, &testEndpoint3, false); + TestDataResponseHelper(&testEndpoint3, false); } -void TestCommandInteraction::TestDataResponseHandlerOverride1(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponseHandlerOverride1) { TestClusterCommandHandler commandHandler; commandHandler.OverrideAcceptedCommands(); - TestDataResponseHelper(apSuite, apContext, &testEndpoint2, true); + TestDataResponseHelper(&testEndpoint2, true); } -void TestCommandInteraction::TestDataResponseHandlerOverride2(nlTestSuite * apSuite, void * apContext) +TEST_F(TestServerCommandDispatch, TestDataResponseHandlerOverride2) { TestClusterCommandHandler commandHandler; commandHandler.OverrideAcceptedCommands(); - TestDataResponseHelper(apSuite, apContext, &testEndpoint3, true); + TestDataResponseHelper(&testEndpoint3, true); } -// clang-format off -const nlTest sTests[] = -{ - NL_TEST_DEF("TestNoHandler", TestCommandInteraction::TestNoHandler), - NL_TEST_DEF("TestDataResponse", TestCommandInteraction::TestDataResponse), - NL_TEST_DEF("TestDataResponseNoCommand1", TestCommandInteraction::TestDataResponseNoCommand1), - NL_TEST_DEF("TestDataResponseNoCommand2", TestCommandInteraction::TestDataResponseNoCommand2), - NL_TEST_DEF("TestDataResponseNoCommand3", TestCommandInteraction::TestDataResponseNoCommand3), - NL_TEST_DEF("TestDataResponseHandlerOverride1", TestCommandInteraction::TestDataResponseHandlerOverride1), - NL_TEST_DEF("TestDataResponseHandlerOverride2", TestCommandInteraction::TestDataResponseHandlerOverride2), - NL_TEST_SENTINEL() -}; - -// clang-format on - -nlTestSuite sSuite = { - "TestCommands", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestCommandInteractionTest() -{ - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestCommandInteractionTest) diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index d8c748129a755d..60be2212aa00e8 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -16,6 +16,11 @@ * limitations under the License. */ +#include +#include + +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ConcreteAttributePath.h" @@ -32,14 +37,8 @@ #include #include #include -#include -#include #include #include -#include - -#include -#include using TestContext = chip::Test::AppContext; using namespace chip; @@ -49,7 +48,6 @@ using namespace chip::app::Clusters; namespace { uint32_t gIterationCount = 0; -nlTestSuite * gSuite = nullptr; // // The generated endpoint_config for the controller app has Endpoint 1 @@ -64,18 +62,41 @@ constexpr uint32_t kTestListLength = 5; // We don't really care about the content, we just need a buffer. uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes]; -class TestWriteChunking +class TestWriteChunking : public ::testing::Test { public: - TestWriteChunking() {} - static void TestListChunking(nlTestSuite * apSuite, void * apContext); - static void TestBadChunking(nlTestSuite * apSuite, void * apContext); - static void TestConflictWrite(nlTestSuite * apSuite, void * apContext); - static void TestNonConflictWrite(nlTestSuite * apSuite, void * apContext); - static void TestTransactionalList(nlTestSuite * apSuite, void * apContext); - -private: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each test in the suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; }; +TestContext * TestWriteChunking::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint) @@ -188,10 +209,9 @@ CHIP_ERROR TestAttrAccess::Write(const app::ConcreteDataAttributePath & aPath, a * This will cause all the various corner cases encountered of closing out the various containers within the write request and * thoroughly and definitely validate those edge cases. */ -void TestWriteChunking::TestListChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWriteChunking, TestListChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -218,16 +238,16 @@ void TestWriteChunking::TestListChunking(nlTestSuite * apSuite, void * apContext gIterationCount = i; - app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), + app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(minReservationSize + i) /* reserved buffer size */); ByteSpan list[kTestListLength]; err = writeClient.EncodeAttribute(attributePath, app::DataModel::List(list, kTestListLength)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Service the IO + Engine till we get a ReportEnd callback on the client. @@ -236,20 +256,19 @@ void TestWriteChunking::TestListChunking(nlTestSuite * apSuite, void * apContext // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); } - NL_TEST_ASSERT(apSuite, - writeCallback.mSuccessCount == kTestListLength + 1 /* an extra item for the empty list at the beginning */); - NL_TEST_ASSERT(apSuite, writeCallback.mErrorCount == 0); - NL_TEST_ASSERT(apSuite, writeCallback.mOnDoneCount == 1); + EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); + EXPECT_EQ(writeCallback.mErrorCount, 0u); + EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } @@ -260,10 +279,9 @@ void TestWriteChunking::TestListChunking(nlTestSuite * apSuite, void * apContext // We encode a pretty large write payload to test the corner cases related to message layer and secure session overheads. // The test should gurantee that if encode returns no error, the send should also success. // As the actual overhead may change, we will test over a few possible payload lengths, from 850 to MTU used in write clients. -void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWriteChunking, TestBadChunking) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool atLeastOneRequestSent = false; bool atLeastOneRequestFailed = false; @@ -288,7 +306,7 @@ void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) gIterationCount = (uint32_t) i; - app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing()); + app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing()); ByteSpan list[kTestListLength]; for (auto & item : list) @@ -308,7 +326,7 @@ void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) // If we successfully encoded the attribute, then we must be able to send the message. err = writeClient.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); // // Service the IO + Engine till we get a ReportEnd callback on the client. @@ -317,26 +335,25 @@ void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); } - NL_TEST_ASSERT(apSuite, - writeCallback.mSuccessCount == kTestListLength + 1 /* an extra item for the empty list at the beginning */); - NL_TEST_ASSERT(apSuite, writeCallback.mErrorCount == 0); - NL_TEST_ASSERT(apSuite, writeCallback.mOnDoneCount == 1); + EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); + EXPECT_EQ(writeCallback.mErrorCount, 0u); + EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. // - if (apSuite->flagError) + if (HasFailure()) { break; } } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); - NL_TEST_ASSERT(apSuite, atLeastOneRequestSent && atLeastOneRequestFailed); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed); emberAfClearDynamicEndpoint(0); } @@ -344,10 +361,9 @@ void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext) * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. In this case, we will reject * the latter write requests to the same attribute. */ -void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWriteChunking, TestConflictWrite) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -364,11 +380,11 @@ void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContex constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&ctx.GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&ctx.GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -376,17 +392,17 @@ void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContex CHIP_ERROR err = CHIP_NO_ERROR; err = writeClient1.EncodeAttribute(attributePath, app::DataModel::List(list, kTestListLength)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient2.EncodeAttribute(attributePath, app::DataModel::List(list, kTestListLength)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient1.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient2.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); { const TestWriteCallback * writeCallbackRef1 = &writeCallback1; @@ -400,19 +416,17 @@ void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContex writeCallbackRef1 = &writeCallback2; } - NL_TEST_ASSERT(apSuite, - writeCallbackRef1->mSuccessCount == - kTestListLength + 1 /* an extra item for the empty list at the beginning */); - NL_TEST_ASSERT(apSuite, writeCallbackRef1->mErrorCount == 0); - NL_TEST_ASSERT(apSuite, writeCallbackRef2->mSuccessCount == 0); - NL_TEST_ASSERT(apSuite, writeCallbackRef2->mErrorCount == kTestListLength + 1); - NL_TEST_ASSERT(apSuite, writeCallbackRef2->mLastErrorReason.mStatus == Protocols::InteractionModel::Status::Busy); + EXPECT_EQ(writeCallbackRef1->mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); + EXPECT_EQ(writeCallbackRef1->mErrorCount, 0u); + EXPECT_EQ(writeCallbackRef2->mSuccessCount, 0u); + EXPECT_EQ(writeCallbackRef2->mErrorCount, kTestListLength + 1); + EXPECT_EQ(writeCallbackRef2->mLastErrorReason.mStatus, Protocols::InteractionModel::Status::Busy); - NL_TEST_ASSERT(apSuite, writeCallbackRef1->mOnDoneCount == 1); - NL_TEST_ASSERT(apSuite, writeCallbackRef2->mOnDoneCount == 1); + EXPECT_EQ(writeCallbackRef1->mOnDoneCount, 1u); + EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u); } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -421,10 +435,9 @@ void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContex * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. However, we will allow such * change when writing to different attributes in parallel. */ -void TestWriteChunking::TestNonConflictWrite(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWriteChunking, TestNonConflictWrite) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -442,11 +455,11 @@ void TestWriteChunking::TestNonConflictWrite(nlTestSuite * apSuite, void * apCon constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&ctx.GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&ctx.GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -454,29 +467,29 @@ void TestWriteChunking::TestNonConflictWrite(nlTestSuite * apSuite, void * apCon CHIP_ERROR err = CHIP_NO_ERROR; err = writeClient1.EncodeAttribute(attributePath1, app::DataModel::List(list, kTestListLength)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient2.EncodeAttribute(attributePath2, app::DataModel::List(list, kTestListLength)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient1.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); err = writeClient2.SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); { - NL_TEST_ASSERT(apSuite, writeCallback1.mErrorCount == 0); - NL_TEST_ASSERT(apSuite, writeCallback1.mSuccessCount == kTestListLength + 1); - NL_TEST_ASSERT(apSuite, writeCallback2.mErrorCount == 0); - NL_TEST_ASSERT(apSuite, writeCallback2.mSuccessCount == kTestListLength + 1); + EXPECT_EQ(writeCallback1.mErrorCount, 0u); + EXPECT_EQ(writeCallback1.mSuccessCount, kTestListLength + 1); + EXPECT_EQ(writeCallback2.mErrorCount, 0u); + EXPECT_EQ(writeCallback2.mSuccessCount, kTestListLength + 1); - NL_TEST_ASSERT(apSuite, writeCallback1.mOnDoneCount == 1); - NL_TEST_ASSERT(apSuite, writeCallback2.mOnDoneCount == 1); + EXPECT_EQ(writeCallback1.mOnDoneCount, 1u); + EXPECT_EQ(writeCallback2.mOnDoneCount, 1u); } - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -510,14 +523,14 @@ struct Instructions std::vector expectedStatus; }; -void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions) +void RunTest(TestContext * pContext, Instructions instructions) { CHIP_ERROR err = CHIP_NO_ERROR; - auto sessionHandle = ctx.GetSessionBobToAlice(); + auto sessionHandle = pContext->GetSessionBobToAlice(); TestWriteCallback writeCallback; std::unique_ptr writeClient = std::make_unique( - &ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), + &pContext->GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(kMaxSecureSduLengthBytes - 128) /* use a smaller chunk so we only need a few attributes in the write request. */); @@ -525,7 +538,7 @@ void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions std::vector status; testServer.mOnListWriteBegin = [&](const ConcreteAttributePath & aPath) { - NL_TEST_ASSERT(apSuite, onGoingPath == ConcreteAttributePath()); + EXPECT_EQ(onGoingPath, ConcreteAttributePath()); onGoingPath = aPath; ChipLogProgress(Zcl, "OnListWriteBegin endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI, aPath.mEndpointId, ChipLogValueMEI(aPath.mClusterId), ChipLogValueMEI(aPath.mAttributeId)); @@ -542,7 +555,7 @@ void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions } }; testServer.mOnListWriteEnd = [&](const ConcreteAttributePath & aPath, bool aWasSuccessful) { - NL_TEST_ASSERT(apSuite, onGoingPath == aPath); + EXPECT_EQ(onGoingPath, aPath); status.push_back(PathStatus(aPath, aWasSuccessful)); onGoingPath = ConcreteAttributePath(); ChipLogProgress(Zcl, "OnListWriteEnd endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI, @@ -556,7 +569,7 @@ void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions { instructions.data = std::vector(instructions.paths.size(), ListData::kList); } - NL_TEST_ASSERT(apSuite, instructions.paths.size() == instructions.data.size()); + EXPECT_EQ(instructions.paths.size(), instructions.data.size()); for (size_t i = 0; i < instructions.paths.size(); i++) { @@ -579,22 +592,22 @@ void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions break; } } - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); } err = writeClient->SendWriteRequest(sessionHandle); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + EXPECT_EQ(err, CHIP_NO_ERROR); - ctx.GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + - System::Clock::Seconds16(1), - [&]() { return ctx.GetExchangeManager().GetNumActiveExchanges() == 0; }); + pContext->GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + + System::Clock::Seconds16(1), + [&]() { return pContext->GetExchangeManager().GetNumActiveExchanges() == 0; }); - NL_TEST_ASSERT(apSuite, onGoingPath == app::ConcreteAttributePath()); - NL_TEST_ASSERT(apSuite, status.size() == instructions.expectedStatus.size()); + EXPECT_EQ(onGoingPath, app::ConcreteAttributePath()); + EXPECT_EQ(status.size(), instructions.expectedStatus.size()); for (size_t i = 0; i < status.size(); i++) { - NL_TEST_ASSERT(apSuite, status[i] == PathStatus(instructions.paths[i], instructions.expectedStatus[i])); + EXPECT_EQ(status[i], PathStatus(instructions.paths[i], instructions.expectedStatus[i])); } testServer.mOnListWriteBegin = nullptr; @@ -603,12 +616,10 @@ void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions } // namespace TestTransactionalListInstructions -void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apContext) +TEST_F(TestWriteChunking, TestTransactionalList) { using namespace TestTransactionalListInstructions; - TestContext & ctx = *static_cast(apContext); - // Initialize the ember side server logic InitDataModelHandler(); @@ -620,7 +631,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo // Test 1: we should receive transaction notifications ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, .expectedStatus = { true }, @@ -628,7 +639,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations"); RunTest( - apSuite, ctx, + mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, @@ -636,7 +647,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo }); ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, @@ -644,7 +655,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo }); ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, @@ -662,7 +673,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo ChipLogProgress(Zcl, "Test 5: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value before non null values"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, @@ -673,7 +684,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo ChipLogProgress(Zcl, "Test 6: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value after non null values"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, @@ -684,7 +695,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo ChipLogProgress(Zcl, "Test 7: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value between non null values"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), @@ -694,7 +705,7 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo }); ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, .data = { ListData::kNull }, @@ -704,42 +715,16 @@ void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apCo ChipLogProgress(Zcl, "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred " "during processing the requests"); - RunTest(apSuite, ctx, + RunTest(mpContext, Instructions{ .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, .data = { ListData::kBadValue }, .expectedStatus = { false }, }); - NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -const nlTest sTests[] = { - NL_TEST_DEF("TestListChunking", TestWriteChunking::TestListChunking), - NL_TEST_DEF("TestBadChunking", TestWriteChunking::TestBadChunking), - NL_TEST_DEF("TestConflictWrite", TestWriteChunking::TestConflictWrite), - NL_TEST_DEF("TestNonConflictWrite", TestWriteChunking::TestNonConflictWrite), - NL_TEST_DEF("TestTransactionalList", TestWriteChunking::TestTransactionalList), - NL_TEST_SENTINEL(), -}; - -nlTestSuite sSuite = { - "TestWriteChunking", - &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, -}; - } // namespace - -int TestWriteChunkingTests() -{ - gSuite = &sSuite; - return chip::ExecuteTestsWithContext(&sSuite); -} - -CHIP_REGISTER_TEST_SUITE(TestWriteChunkingTests) From fb0394d64c4fddd3b1e830a264ebfb5d91140f4b Mon Sep 17 00:00:00 2001 From: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Date: Tue, 14 May 2024 10:35:09 +1200 Subject: [PATCH 07/10] Darwin: Add missing import (#33429) --- src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h b/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h index 6beb9671ab4210..5c90cb95d299fa 100644 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsType.h @@ -15,6 +15,8 @@ * limitations under the License. */ +#import + /** * This enum is used to specify the type of log requested from this device. * From c291336e996d79dfa3cc62e53218332b2014c879 Mon Sep 17 00:00:00 2001 From: Justin Wood Date: Mon, 13 May 2024 17:15:06 -0700 Subject: [PATCH 08/10] Updating documentation to make this more clear how to use CHIPTool (#33431) --- src/darwin/CHIPTool/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/darwin/CHIPTool/README.md b/src/darwin/CHIPTool/README.md index d31ab129972ffb..bf74d6bb14e5c2 100644 --- a/src/darwin/CHIPTool/README.md +++ b/src/darwin/CHIPTool/README.md @@ -6,9 +6,11 @@ control. --- - [CHIP Tool iOS Sample Commissioner App](#chip-tool-ios-sample-commissioner-app) + - [Prerequisites](#prerequisites) - [Building the Application](#building-the-application) - [Compilation Fixes](#compilation-fixes) - [Installing the Application](#installing-the-application) + - [Setting up the iPhone](#setting-up-the-iphone) - [Pairing an Accessory](#pairing-an-accessory) --- @@ -80,6 +82,15 @@ run. Now you can launch the application from the Home screen or from Xcode by hitting the run button once more. +## Setting up the iPhone + +To use CHIP Tool on iOS or macOS, enable Developer Mode during the development +phase of your app by following the steps at +[Enabling Developer Mode on a device](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device). +On the iOS or macOS device that initiates the pairing, +[download](https://developer.apple.com/services-account/download?path=/iOS/iOS_Logs/EnableBluetoothCentralMatterClientDeveloperMode.mobileconfig) +the developer profile, then install it. + ## Pairing an Accessory Once you have CHIPTool up and running, to pair an accessory simply: From b653e8e38c328be43c293962849af6fcb0b11268 Mon Sep 17 00:00:00 2001 From: Justin Wood Date: Mon, 13 May 2024 20:30:28 -0700 Subject: [PATCH 09/10] Updating code autocomplete (#33432) --- .vscode/settings.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bfac435030bb4f..415d4980d00d24 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,18 @@ "${workspaceFolder}/src/lib/**", "${workspaceFolder}/src/system/**", "${workspaceFolder}/third_party/nlassert/repo/include/**", - "${workspaceFolder}/third_party/nlio/repo/include/**" + "${workspaceFolder}/third_party/nlio/repo/include/**", + "${workspaceFolder}/darwin/Framework/CHIP/**", + "${workspaceFolder}/src/messaging/**", + "${workspaceFolder}/src/protocols/**", + "${workspaceFolder}/src/tracing/**", + "${workspaceFolder}/src/transport/**", + "${workspaceFolder}/src/inet/**", + "${workspaceFolder}/src/credentials/**", + "${workspaceFolder}/src/data_model/**", + "${workspaceFolder}/src/app/**", + "${workspaceFolder}/src/crytpo/**", + "${workspaceFolder}/src/platform/**" ], "[cpp]": { "editor.defaultFormatter": "xaver.clang-format" @@ -35,6 +46,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "files.associations": { + "*.mm": "cpp", "iostream": "cpp", "array": "cpp", "atomic": "cpp", From 3219a5ff449a0441202f918f27603d0a6215ad1c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 00:38:57 -0400 Subject: [PATCH 10/10] Ember compatibility layer - split out GlobalAttributeAccessInterface and the I/O buffer (#33396) * Split out ember-compatibiltiy-functions with some shareable bits. When looking to implement the ember/codegen data mode, some functionality in ember-compatibility-functions needs sharing to avoid extra copy&paste bloat: - Global attribute handling via AAI split into a separate file - Raw "data I/O" buffer split into a separate file Moved privilege-storage and implemented a few more mock ember methods. * Also update linter * Remove obsolete file * Fix odd include * Add files to Matter.xcodeproject * Update src/app/util/ember-io-storage.h Co-authored-by: Boris Zbarsky * Update ember-io-storage.h Add additional comments. * Restyle --------- Co-authored-by: Andrei Litvin Co-authored-by: Boris Zbarsky --- .github/workflows/lint.yml | 4 + src/app/chip_data_model.cmake | 2 + src/app/chip_data_model.gni | 2 + src/app/tests/BUILD.gn | 1 - src/app/tests/integration/BUILD.gn | 2 - .../util/ember-compatibility-functions.cpp | 286 ++---------------- ...mber-global-attribute-access-interface.cpp | 136 +++++++++ .../ember-global-attribute-access-interface.h | 56 ++++ src/app/util/ember-io-storage.cpp | 126 ++++++++ src/app/util/ember-io-storage.h | 55 ++++ src/app/util/mock/BUILD.gn | 1 + src/app/util/mock/attribute-storage.cpp | 45 +++ .../include/zap-generated/endpoint_config.h | 2 + .../mock/privilege-storage.cpp} | 11 +- .../Matter.xcodeproj/project.pbxproj | 12 + 15 files changed, 475 insertions(+), 266 deletions(-) create mode 100644 src/app/util/ember-global-attribute-access-interface.cpp create mode 100644 src/app/util/ember-global-attribute-access-interface.h create mode 100644 src/app/util/ember-io-storage.cpp create mode 100644 src/app/util/ember-io-storage.h rename src/app/{tests/integration/RequiredPrivilegeStubs.cpp => util/mock/privilege-storage.cpp} (82%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7336213ba81a30..4685948fd7bd12 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -119,6 +119,10 @@ jobs: --known-failure app/util/DataModelHandler.h \ --known-failure app/util/ember-compatibility-functions.cpp \ --known-failure app/util/ember-compatibility-functions.h \ + --known-failure app/util/ember-global-attribute-access-interface.cpp \ + --known-failure app/util/ember-global-attribute-access-interface.h \ + --known-failure app/util/ember-io-storage.cpp \ + --known-failure app/util/ember-io-storage.h \ --known-failure app/util/endpoint-config-api.h \ --known-failure app/util/generic-callbacks.h \ --known-failure app/util/generic-callback-stubs.cpp \ diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index 5573eeb275acd7..2d4149b66f6a8a 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -144,6 +144,8 @@ function(chip_configure_data_model APP_TARGET) ${CHIP_APP_BASE_DIR}/icd/server/ICDConfigurationData.cpp ${CHIP_APP_BASE_DIR}/util/DataModelHandler.cpp ${CHIP_APP_BASE_DIR}/util/ember-compatibility-functions.cpp + ${CHIP_APP_BASE_DIR}/util/ember-global-attribute-access-interface.cpp + ${CHIP_APP_BASE_DIR}/util/ember-io-storage.cpp ${CHIP_APP_BASE_DIR}/util/generic-callback-stubs.cpp ${CHIP_APP_BASE_DIR}/util/privilege-storage.cpp ${CHIP_APP_BASE_DIR}/util/util.cpp diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index c8a30b1b5bd36a..a68d193d241541 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -209,6 +209,8 @@ template("chip_data_model") { "${_app_root}/util/attribute-storage.cpp", "${_app_root}/util/attribute-table.cpp", "${_app_root}/util/ember-compatibility-functions.cpp", + "${_app_root}/util/ember-global-attribute-access-interface.cpp", + "${_app_root}/util/ember-io-storage.cpp", "${_app_root}/util/util.cpp", ] } diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 49196f2fdaaf8f..df6737a713e6e4 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -29,7 +29,6 @@ static_library("helpers") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "AppTestContext.cpp", "AppTestContext.h", - "integration/RequiredPrivilegeStubs.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index da0db88682f180..49aea9034e9766 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -38,7 +38,6 @@ source_set("common") { executable("chip-im-initiator") { sources = [ "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", - "RequiredPrivilegeStubs.cpp", "chip_im_initiator.cpp", ] @@ -61,7 +60,6 @@ executable("chip-im-responder") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "MockEvents.cpp", "MockEvents.h", - "RequiredPrivilegeStubs.cpp", "chip_im_responder.cpp", ] diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index e87e0f564a5ff7..cc3185f78a5df6 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -54,118 +56,18 @@ using chip::Protocols::InteractionModel::Status; using namespace chip; using namespace chip::app; using namespace chip::Access; +using namespace chip::app::Compatibility; +using namespace chip::app::Compatibility::Internal; namespace chip { namespace app { -namespace Compatibility { -namespace { -// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold -// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. -constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); - -// BasicType maps the type to basic int(8|16|32|64)(s|u) types. -EmberAfAttributeType BaseType(EmberAfAttributeType type) -{ - switch (type) - { - case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id - case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index - case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap - case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration - case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code - case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage - static_assert(std::is_same::value, - "chip::Percent is expected to be uint8_t, change this when necessary"); - return ZCL_INT8U_ATTRIBUTE_TYPE; - - case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number - case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id - case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id - case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration - case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap - case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent - static_assert(std::is_same::value, - "chip::EndpointId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::GroupId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::Percent100ths is expected to be uint16_t, change this when necessary"); - return ZCL_INT16U_ATTRIBUTE_TYPE; - - case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id - case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id - case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id - case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id - case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id - case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id - case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id - case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version - case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap - case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds - case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds - static_assert(std::is_same::value, - "chip::Cluster is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::EventId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::CommandId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::TransactionId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DataVersion is expected to be uint32_t, change this when necessary"); - return ZCL_INT32U_ATTRIBUTE_TYPE; - - case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps - case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours - case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts - case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts - return ZCL_INT64S_ATTRIBUTE_TYPE; - - case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number - case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id - case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id - case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap - case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds - case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds - case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds - case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds - static_assert(std::is_same::value, - "chip::EventNumber is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::FabricId is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::NodeId is expected to be uint64_t, change this when necessary"); - return ZCL_INT64U_ATTRIBUTE_TYPE; - - case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature - return ZCL_INT16S_ATTRIBUTE_TYPE; - - default: - return type; - } -} - -} // namespace - -} // namespace Compatibility - -using namespace chip::app::Compatibility; - namespace { -// Common buffer for ReadSingleClusterData & WriteSingleClusterData -uint8_t attributeData[kAttributeReadBufferSize]; template CHIP_ERROR attributeBufferToNumericTlvData(TLV::TLVWriter & writer, bool isNullable) { typename NumericAttributeTraits::StorageType value; - memcpy(&value, attributeData, sizeof(value)); + memcpy(&value, gEmberAttributeIOBufferSpan.data(), sizeof(value)); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); if (isNullable && NumericAttributeTraits::IsNullValue(value)) { @@ -285,143 +187,6 @@ CHIP_ERROR SendFailureStatus(const ConcreteAttributePath & aPath, AttributeRepor return aAttributeReports.EncodeAttributeStatus(aPath, StatusIB(aStatus)); } -// This reader should never actually be registered; we do manual dispatch to it -// for the one attribute it handles. -class MandatoryGlobalAttributeReader : public AttributeAccessInterface -{ -public: - MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : - AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) - {} - -protected: - const EmberAfCluster * mCluster; -}; - -class GlobalAttributeReader : public MandatoryGlobalAttributeReader -{ -public: - GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, - CommandHandlerInterface::CommandIdCallback callback, - void * context); - static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); -}; - -CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - using namespace Clusters::Globals::Attributes; - switch (aPath.mAttributeId) - { - case AttributeList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - const size_t count = mCluster->attributeCount; - bool addedExtraGlobals = false; - for (size_t i = 0; i < count; ++i) - { - AttributeId id = mCluster->attributes[i].attributeId; - constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, - "Ids in GlobalAttributesNotInMetadata not consecutive"); -#else - // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), - "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - if (!addedExtraGlobals && id > lastGlobalId) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - addedExtraGlobals = true; - } - ReturnErrorOnFailure(encoder.Encode(id)); - } - if (!addedExtraGlobals) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - } - return CHIP_NO_ERROR; - }); -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case EventList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - for (size_t i = 0; i < mCluster->eventCount; ++i) - { - ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); - } - return CHIP_NO_ERROR; - }); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case AcceptedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, - mCluster->acceptedCommandList); - case GeneratedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, - mCluster->generatedCommandList); - default: - // This function is only called if attributeCluster is non-null in - // ReadSingleClusterData, which only happens for attributes listed in - // GlobalAttributesNotInMetadata. If we reach this code, someone added - // a global attribute to that list but not the above switch. - VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, - ChipLogValueMEI(aPath.mAttributeId)); - return CHIP_NO_ERROR; - } -} - -CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - GlobalAttributeReader::CommandListEnumerator aEnumerator, - const CommandId * aClusterCommandList) -{ - return aEncoder.EncodeList([&](const auto & encoder) { - auto * commandHandler = - InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); - if (commandHandler) - { - struct Context - { - decltype(encoder) & commandIdEncoder; - CHIP_ERROR err; - } context{ encoder, CHIP_NO_ERROR }; - CHIP_ERROR err = (commandHandler->*aEnumerator)( - aClusterPath, - [](CommandId command, void * closure) -> Loop { - auto * ctx = static_cast(closure); - ctx->err = ctx->commandIdEncoder.Encode(command); - if (ctx->err != CHIP_NO_ERROR) - { - return Loop::Break; - } - return Loop::Continue; - }, - &context); - if (err != CHIP_ERROR_NOT_IMPLEMENTED) - { - return context.err; - } - // Else fall through to the list in aClusterCommandList. - } - - for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) - { - ReturnErrorOnFailure(encoder.Encode(*cmd)); - } - return CHIP_NO_ERROR; - }); -} - // Helper function for trying to read an attribute value via an // AttributeAccessInterface. On failure, the read has failed. On success, the // aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value. @@ -603,7 +368,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b record.endpoint = aPath.mEndpointId; record.clusterId = aPath.mClusterId; record.attributeId = aPath.mAttributeId; - Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeData, sizeof(attributeData), + Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), + static_cast(gEmberAttributeIOBufferSpan.size()), /* write = */ false); if (status == Status::Success) @@ -613,7 +379,7 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); - switch (BaseType(attributeType)) + switch (AttributeBaseType(attributeType)) { case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data ReturnErrorOnFailure(writer->PutNull(tag)); @@ -719,8 +485,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string { - char * actualData = reinterpret_cast(attributeData + 1); - uint8_t dataLength = attributeData[0]; + char * actualData = reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 1); + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -739,9 +505,10 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { - char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length + char * actualData = + reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 2); // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -761,8 +528,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string { - uint8_t * actualData = attributeData + 1; - uint8_t dataLength = attributeData[0]; + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 1; + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -781,9 +548,9 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { - uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 2; // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -821,7 +588,8 @@ template CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNullable, uint16_t & dataLen) { typename NumericAttributeTraits::StorageType value; - static_assert(sizeof(value) <= sizeof(attributeData), "Value cannot fit into attribute data"); + VerifyOrDie(sizeof(value) <= gEmberAttributeIOBufferSpan.size()); + if (isNullable && aReader.GetType() == TLV::kTLVType_Null) { NumericAttributeTraits::SetNull(value); @@ -834,7 +602,7 @@ CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNull NumericAttributeTraits::WorkingToStorage(val, value); } dataLen = sizeof(value); - memcpy(attributeData, &value, sizeof(value)); + memcpy(gEmberAttributeIOBufferSpan.data(), &value, sizeof(value)); return CHIP_NO_ERROR; } @@ -847,7 +615,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet { // Null is represented by an 0xFF or 0xFFFF length, respectively. len = std::numeric_limits::max(); - memcpy(&attributeData[0], &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); dataLen = sizeof(len); } else @@ -859,10 +627,10 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet ReturnErrorOnFailure(aReader.GetDataPtr(data)); len = static_cast(aReader.GetLength()); VerifyOrReturnError(len != std::numeric_limits::max(), CHIP_ERROR_MESSAGE_TOO_LONG); - VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= sizeof(attributeData), + VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= gEmberAttributeIOBufferSpan.size(), CHIP_ERROR_MESSAGE_TOO_LONG); - memcpy(&attributeData[0], &len, sizeof(len)); - memcpy(&attributeData[sizeof(len)], data, len); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data() + sizeof(len), data, len); dataLen = static_cast(len + sizeof(len)); } return CHIP_NO_ERROR; @@ -870,7 +638,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet CHIP_ERROR prepareWriteData(const EmberAfAttributeMetadata * attributeMetadata, TLV::TLVReader & aReader, uint16_t & dataLen) { - EmberAfAttributeType expectedType = BaseType(attributeMetadata->attributeType); + EmberAfAttributeType expectedType = AttributeBaseType(attributeMetadata->attributeType); bool isNullable = attributeMetadata->IsNullable(); switch (expectedType) { @@ -1031,8 +799,8 @@ CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::InvalidValue); } - auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, attributeData, - attributeMetadata->attributeType); + auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + gEmberAttributeIOBufferSpan.data(), attributeMetadata->attributeType); return apWriteHandler->AddStatus(aPath, status); } diff --git a/src/app/util/ember-global-attribute-access-interface.cpp b/src/app/util/ember-global-attribute-access-interface.cpp new file mode 100644 index 00000000000000..327ab09c479512 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-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 + +namespace chip { +namespace app { +namespace Compatibility { + +CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + using namespace Clusters::Globals::Attributes; + switch (aPath.mAttributeId) + { + case AttributeList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + const size_t count = mCluster->attributeCount; + bool addedExtraGlobals = false; + for (size_t i = 0; i < count; ++i) + { + AttributeId id = mCluster->attributes[i].attributeId; + constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, + "Ids in GlobalAttributesNotInMetadata not consecutive"); +#else + // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), + "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + if (!addedExtraGlobals && id > lastGlobalId) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + addedExtraGlobals = true; + } + ReturnErrorOnFailure(encoder.Encode(id)); + } + if (!addedExtraGlobals) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + } + return CHIP_NO_ERROR; + }); +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case EventList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + for (size_t i = 0; i < mCluster->eventCount; ++i) + { + ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); + } + return CHIP_NO_ERROR; + }); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case AcceptedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, + mCluster->acceptedCommandList); + case GeneratedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, + mCluster->generatedCommandList); + default: + // This function is only called if attributeCluster is non-null in + // ReadSingleClusterData, which only happens for attributes listed in + // GlobalAttributesNotInMetadata. If we reach this code, someone added + // a global attribute to that list but not the above switch. + VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, + ChipLogValueMEI(aPath.mAttributeId)); + return CHIP_NO_ERROR; + } +} + +CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + GlobalAttributeReader::CommandListEnumerator aEnumerator, + const CommandId * aClusterCommandList) +{ + return aEncoder.EncodeList([&](const auto & encoder) { + auto * commandHandler = + InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); + if (commandHandler) + { + struct Context + { + decltype(encoder) & commandIdEncoder; + CHIP_ERROR err; + } context{ encoder, CHIP_NO_ERROR }; + CHIP_ERROR err = (commandHandler->*aEnumerator)( + aClusterPath, + [](CommandId command, void * closure) -> Loop { + auto * ctx = static_cast(closure); + ctx->err = ctx->commandIdEncoder.Encode(command); + if (ctx->err != CHIP_NO_ERROR) + { + return Loop::Break; + } + return Loop::Continue; + }, + &context); + if (err != CHIP_ERROR_NOT_IMPLEMENTED) + { + return context.err; + } + // Else fall through to the list in aClusterCommandList. + } + + for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) + { + ReturnErrorOnFailure(encoder.Encode(*cmd)); + } + return CHIP_NO_ERROR; + }); +} + +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-global-attribute-access-interface.h b/src/app/util/ember-global-attribute-access-interface.h new file mode 100644 index 00000000000000..d17e1b286dbd81 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021-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. + */ +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { + +// This reader should never actually be registered; we do manual dispatch to it +// for the one attribute it handles. +class MandatoryGlobalAttributeReader : public AttributeAccessInterface +{ +public: + MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : + AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) + {} + +protected: + const EmberAfCluster * mCluster; +}; + +class GlobalAttributeReader : public MandatoryGlobalAttributeReader +{ +public: + GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, + CommandHandlerInterface::CommandIdCallback callback, + void * context); + static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); +}; + +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-io-storage.cpp b/src/app/util/ember-io-storage.cpp new file mode 100644 index 00000000000000..cc5eacf733480c --- /dev/null +++ b/src/app/util/ember-io-storage.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024 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 + +#include + +namespace chip { +namespace app { +namespace Compatibility { +namespace Internal { + +// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold +// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. +constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); +uint8_t attributeIOBuffer[kAttributeReadBufferSize]; + +MutableByteSpan gEmberAttributeIOBufferSpan(attributeIOBuffer); + +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type) +{ + switch (type) + { + case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id + case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index + case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap + case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration + case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code + case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage + static_assert(std::is_same::value, + "chip::Percent is expected to be uint8_t, change this when necessary"); + return ZCL_INT8U_ATTRIBUTE_TYPE; + + case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number + case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id + case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id + case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration + case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap + case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent + static_assert(std::is_same::value, + "chip::EndpointId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::GroupId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::Percent100ths is expected to be uint16_t, change this when necessary"); + return ZCL_INT16U_ATTRIBUTE_TYPE; + + case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id + case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id + case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id + case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id + case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id + case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id + case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id + case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version + case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap + case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds + case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds + static_assert(std::is_same::value, + "chip::Cluster is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::EventId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::CommandId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::TransactionId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DataVersion is expected to be uint32_t, change this when necessary"); + return ZCL_INT32U_ATTRIBUTE_TYPE; + + case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps + case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours + case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts + case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts + return ZCL_INT64S_ATTRIBUTE_TYPE; + + case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number + case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id + case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id + case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap + case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds + case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds + case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds + case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds + static_assert(std::is_same::value, + "chip::EventNumber is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::FabricId is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::NodeId is expected to be uint64_t, change this when necessary"); + return ZCL_INT64U_ATTRIBUTE_TYPE; + + case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature + return ZCL_INT16S_ATTRIBUTE_TYPE; + + default: + return type; + } +} + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-io-storage.h b/src/app/util/ember-io-storage.h new file mode 100644 index 00000000000000..4297bc73d0c176 --- /dev/null +++ b/src/app/util/ember-io-storage.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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 + +namespace chip { +namespace app { +namespace Compatibility { +namespace Internal { + +/// A buffer guaranteed to be sized sufficiently large to contain any individual value from +/// the ember attribute/data store (i.e. a buffer that can be used to read ember data into +/// or as a temporary buffer to place data before asking ember to store it). +/// +/// This buffer is intended to be used for calls to `emAfReadOrWriteAttribute` and +/// `emAfWriteAttributeExternal`: it is sufficiently sized to be able to handle any +/// max-sized data that ember is aware of. +extern MutableByteSpan gEmberAttributeIOBufferSpan; + +/// Maps an attribute type that is not an integer but can be represented as an integer to the +/// corresponding basic int(8|16|32|64)(s|u) type +/// +/// For example: +/// ZCL_ENUM8_ATTRIBUTE_TYPE maps to ZCL_INT8U_ATTRIBUTE_TYPE +/// ZCL_VENDOR_ID_ATTRIBUTE_TYPE maps to ZCL_INT16U_ATTRIBUTE_TYPE +/// ZCL_BITMAP32_ATTRIBUTE_TYPE maps to ZCL_INT32U_ATTRIBUTE_TYPE +/// ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE maps to ZCL_INT64S_ATTRIBUTE_TYPE +/// ... +/// +/// If the `type` cannot be mapped to a basic type (or is already a basic type) its value +/// is returned unchanged. +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type); + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/mock/BUILD.gn b/src/app/util/mock/BUILD.gn index da44fb84e5a809..b63c49e68f2494 100644 --- a/src/app/util/mock/BUILD.gn +++ b/src/app/util/mock/BUILD.gn @@ -25,6 +25,7 @@ source_set("mock_ember") { "MockNodeConfig.cpp", "MockNodeConfig.h", "attribute-storage.cpp", + "privilege-storage.cpp", ] public_deps = [ diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 2293a48a8403e4..e4c965e98905f4 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -152,6 +152,50 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpointId) return static_cast(endpoint->clusters.size()); } +const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId) +{ + auto ep = GetMockNodeConfig().endpointById(endpointId); + VerifyOrReturnValue(ep != nullptr, nullptr); + + auto cluster = ep->clusterById(clusterId); + VerifyOrReturnValue(cluster != nullptr, nullptr); + + auto attr = cluster->attributeById(attributeId); + VerifyOrReturnValue(attr != nullptr, nullptr); + + return &attr->attributeMetaData; +} + +const EmberAfCluster * emberAfFindClusterInType(const EmberAfEndpointType * endpointType, ClusterId clusterId, + EmberAfClusterMask mask, uint8_t * index) +{ + // This is a copy & paste implementation from ember attribute storage + // TODO: this hard-codes ember logic and is duplicated code. + uint8_t scopedIndex = 0; + + for (uint8_t i = 0; i < endpointType->clusterCount; i++) + { + const EmberAfCluster * cluster = &(endpointType->cluster[i]); + + if (mask == 0 || ((cluster->mask & mask) != 0)) + { + if (cluster->clusterId == clusterId) + { + if (index) + { + *index = scopedIndex; + } + + return cluster; + } + + scopedIndex++; + } + } + + return nullptr; +} + uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server) { return (server) ? emberAfGetClusterCountForEndpoint(endpoint) : 0; @@ -414,6 +458,7 @@ void SetMockNodeConfig(const MockNodeConfig & config) mockConfig = &config; } +/// Resets the mock attribute storage to the default configuration. void ResetMockNodeConfig() { mockConfig = nullptr; diff --git a/src/app/util/mock/include/zap-generated/endpoint_config.h b/src/app/util/mock/include/zap-generated/endpoint_config.h index 33d1e87e320896..620e6e29880f36 100644 --- a/src/app/util/mock/include/zap-generated/endpoint_config.h +++ b/src/app/util/mock/include/zap-generated/endpoint_config.h @@ -1,2 +1,4 @@ // Number of fixed endpoints #define FIXED_ENDPOINT_COUNT (3) + +#define ATTRIBUTE_LARGEST (1003) diff --git a/src/app/tests/integration/RequiredPrivilegeStubs.cpp b/src/app/util/mock/privilege-storage.cpp similarity index 82% rename from src/app/tests/integration/RequiredPrivilegeStubs.cpp rename to src/app/util/mock/privilege-storage.cpp index 2cc23056e5d34a..26ff8967944e12 100644 --- a/src/app/tests/integration/RequiredPrivilegeStubs.cpp +++ b/src/app/util/mock/privilege-storage.cpp @@ -1,6 +1,5 @@ -/* - * - * Copyright (c) 2022 Project CHIP Authors +/** + * 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. @@ -14,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include +#include -#include +// Privilege mocks here are MUCH more strict so that +// testing code can generally validatate access without something +// being permissive like kView. chip::Access::Privilege MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute) { diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 4cb4edf4a8edd3..36aa65dfaa2752 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -364,6 +364,10 @@ B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E04AC67D2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */; }; + E04AC67E2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */; }; + E04AC67F2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */; }; + E04AC6802BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -788,6 +792,8 @@ D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestKeys.h; sourceTree = ""; }; D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestStorage.h; sourceTree = ""; }; D4772A45285AE98300383630 /* MTRClusterConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRClusterConstants.h; sourceTree = ""; }; + E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ember-io-storage.cpp"; path = "util/ember-io-storage.cpp"; sourceTree = ""; }; + E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ember-global-attribute-access-interface.cpp"; path = "util/ember-global-attribute-access-interface.cpp"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1033,6 +1039,8 @@ 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */, 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */, 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */, + E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */, + E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */, 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */, 514C79F52B62F0B900DD6D7B /* util.cpp */, 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */, @@ -1845,6 +1853,7 @@ B45373FE2A9FEC4F00807602 /* unix-fds.c in Sources */, B45374002A9FEC4F00807602 /* unix-init.c in Sources */, B45373FF2A9FEC4F00807602 /* unix-misc.c in Sources */, + E04AC67E2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */, B45373FD2A9FEC4F00807602 /* unix-pipe.c in Sources */, B45373FB2A9FEC4F00807602 /* unix-service.c in Sources */, B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */, @@ -1891,6 +1900,7 @@ 037C3DB62991BD5000B7EEE2 /* ModelCommandBridge.mm in Sources */, 516411322B6BF75700E67C05 /* MTRIMDispatch.mm in Sources */, 037C3DB42991BD5000B7EEE2 /* DeviceControllerDelegateBridge.mm in Sources */, + E04AC6802BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */, 039547012992D461006D42A8 /* generic-callback-stubs.cpp in Sources */, 514C79F12B62ADDA00DD6D7B /* descriptor.cpp in Sources */, ); @@ -1926,6 +1936,7 @@ 7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */, B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */, 2C5EEEF7268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm in Sources */, + E04AC67D2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */, 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */, 2C222AD1255C620600E446B9 /* MTRBaseDevice.mm in Sources */, 1EC3238D271999E2002A8BF0 /* cluster-objects.cpp in Sources */, @@ -1967,6 +1978,7 @@ 5178E67E2AE098210069DF72 /* MTRCommandTimedCheck.mm in Sources */, 7596A84928762783004DAE0E /* MTRAsyncCallbackWorkQueue.mm in Sources */, B2E0D7B9245B0B5C003C5B48 /* MTRSetupPayload.mm in Sources */, + E04AC67F2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */, B2E0D7B6245B0B5C003C5B48 /* MTRManualSetupPayloadParser.mm in Sources */, 7596A85528788557004DAE0E /* MTRClusters.mm in Sources */, 88EBF8CF27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm in Sources */,