| /* |
| * |
| * 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 <app/TestEventTriggerDelegate.h> |
| #include <app/clusters/administrator-commissioning-server/AdministratorCommissioningCluster.h> |
| #include <app/reporting/ReportSchedulerImpl.h> |
| #include <app/server/CommissioningWindowManager.h> |
| #include <app/server/Server.h> |
| #include <clusters/AdministratorCommissioning/Enums.h> |
| #include <clusters/AdministratorCommissioning/Metadata.h> |
| #include <crypto/RandUtils.h> |
| #include <data-model-providers/codegen/CodegenDataModelProvider.h> |
| #include <lib/dnssd/Advertiser.h> |
| #include <lib/support/Span.h> |
| #include <messaging/tests/echo/common.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <platform/CommissionableDataProvider.h> |
| #include <platform/ConfigurationManager.h> |
| #include <platform/DefaultTimerDelegate.h> |
| #include <platform/PlatformManager.h> |
| #include <platform/TestOnlyCommissionableDataProvider.h> |
| #include <protocols/secure_channel/PASESession.h> |
| #include <system/RAIIMockClock.h> |
| |
| #include <lib/core/StringBuilderAdapters.h> |
| #include <lib/support/tests/ExtraPwTestMacros.h> |
| #include <pw_unit_test/framework.h> |
| |
| #include <lib/support/UnitTestUtils.h> |
| #include <messaging/tests/MessagingContext.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::Crypto; |
| using namespace chip::Messaging; |
| using namespace chip::Protocols; |
| using namespace System::Clock::Literals; |
| |
| using chip::CommissioningWindowAdvertisement; |
| using chip::CommissioningWindowManager; |
| using chip::Server; |
| |
| namespace { |
| |
| // Test Set #01 of Spake2p Parameters (PIN Code, Iteration Count, Salt, and matching Verifier): |
| constexpr uint32_t sTestSpake2p01_PinCode = 20202021; |
| constexpr uint32_t sTestSpake2p01_IterationCount = 1000; |
| constexpr uint8_t sTestSpake2p01_Salt[] = { 0x53, 0x50, 0x41, 0x4B, 0x45, 0x32, 0x50, 0x20, |
| 0x4B, 0x65, 0x79, 0x20, 0x53, 0x61, 0x6C, 0x74 }; |
| Spake2pVerifier sTestSpake2p01_PASEVerifier = { .mW0 = { |
| 0xB9, 0x61, 0x70, 0xAA, 0xE8, 0x03, 0x34, 0x68, 0x84, 0x72, 0x4F, 0xE9, 0xA3, 0xB2, 0x87, 0xC3, |
| 0x03, 0x30, 0xC2, 0xA6, 0x60, 0x37, 0x5D, 0x17, 0xBB, 0x20, 0x5A, 0x8C, 0xF1, 0xAE, 0xCB, 0x35, |
| }, |
| .mL = { |
| 0x04, 0x57, 0xF8, 0xAB, 0x79, 0xEE, 0x25, 0x3A, 0xB6, 0xA8, 0xE4, 0x6B, 0xB0, 0x9E, 0x54, 0x3A, |
| 0xE4, 0x22, 0x73, 0x6D, 0xE5, 0x01, 0xE3, 0xDB, 0x37, 0xD4, 0x41, 0xFE, 0x34, 0x49, 0x20, 0xD0, |
| 0x95, 0x48, 0xE4, 0xC1, 0x82, 0x40, 0x63, 0x0C, 0x4F, 0xF4, 0x91, 0x3C, 0x53, 0x51, 0x38, 0x39, |
| 0xB7, 0xC0, 0x7F, 0xCC, 0x06, 0x27, 0xA1, 0xB8, 0x57, 0x3A, 0x14, 0x9F, 0xCD, 0x1F, 0xA4, 0x66, |
| 0xCF |
| } }; |
| |
| bool sSimulateFailedSessionEstablishmentTaskCalled = false; |
| |
| bool sAdminFabricIndexDirty = false; |
| bool sAdminVendorIdDirty = false; |
| bool sWindowStatusDirty = false; |
| |
| void ResetDirtyFlags() |
| { |
| sAdminFabricIndexDirty = false; |
| sAdminVendorIdDirty = false; |
| sWindowStatusDirty = false; |
| } |
| |
| class TestCommissioningWindowManagerDataModelProvider : public chip::app::CodegenDataModelProvider |
| { |
| public: |
| TestCommissioningWindowManagerDataModelProvider() = default; |
| ~TestCommissioningWindowManagerDataModelProvider() = default; |
| |
| void Temporary_ReportAttributeChanged(const chip::app::AttributePathParams & path) override |
| { |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::AdministratorCommissioning::Attributes; |
| if (path.mEndpointId != chip::kRootEndpointId || path.mClusterId != AdministratorCommissioning::Id) |
| { |
| return; |
| } |
| |
| switch (path.mAttributeId) |
| { |
| case WindowStatus::Id: |
| sWindowStatusDirty = true; |
| break; |
| case AdminVendorId::Id: |
| sAdminVendorIdDirty = true; |
| break; |
| case AdminFabricIndex::Id: |
| sAdminFabricIndexDirty = true; |
| break; |
| default: |
| break; |
| } |
| } |
| }; |
| |
| chip::app::DataModel::Provider * TestDataModelProviderInstance(chip::PersistentStorageDelegate * delegate) |
| { |
| static TestCommissioningWindowManagerDataModelProvider gTestModel; |
| |
| if (delegate != nullptr) |
| { |
| gTestModel.SetPersistentStorageDelegate(delegate); |
| } |
| |
| return &gTestModel; |
| } |
| |
| } // namespace |
| namespace { |
| |
| void TearDownTask(intptr_t context) |
| { |
| chip::Server::GetInstance().Shutdown(); |
| } |
| |
| static void StopEventLoop(intptr_t context) |
| { |
| EXPECT_SUCCESS(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); |
| } |
| |
| class TestSecurePairingDelegate : public SessionEstablishmentDelegate |
| { |
| public: |
| void OnSessionEstablishmentError(CHIP_ERROR error) override { mNumPairingErrors++; } |
| |
| void OnSessionEstablished(const SessionHandle & session) override { mNumPairingComplete++; } |
| |
| uint32_t mNumPairingErrors = 0; |
| uint32_t mNumPairingComplete = 0; |
| }; |
| |
| class MockAppDelegate : public AppDelegate |
| { |
| public: |
| void OnCommissioningWindowOpened() override { mOnCommissioningWindowOpenedCount++; } |
| void OnCommissioningWindowClosed() override { mOnCommissioningWindowClosedCount++; } |
| |
| void OnCommissioningSessionEstablishmentError(CHIP_ERROR error) override |
| { |
| mOnCommissioningSessionEstablishmentErrorCount++; |
| mError = error; |
| } |
| |
| uint8_t mOnCommissioningWindowOpenedCount = 0; |
| uint8_t mOnCommissioningWindowClosedCount = 0; |
| uint8_t mOnCommissioningSessionEstablishmentErrorCount = 0; |
| |
| CHIP_ERROR mError = CHIP_NO_ERROR; |
| }; |
| |
| class TestCommissioningWindowManager : public chip::Testing::LoopbackMessagingContext |
| { |
| public: |
| static void SetUpTestSuite() |
| { |
| LoopbackMessagingContext::SetUpTestSuite(); |
| |
| ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); |
| ASSERT_EQ(chip::DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); |
| |
| static chip::DeviceLayer::TestOnlyCommissionableDataProvider commissionableDataProvider; |
| chip::DeviceLayer::SetCommissionableDataProvider(&commissionableDataProvider); |
| |
| static chip::CommonCaseDeviceServerInitParams initParams; |
| // Report scheduler and timer delegate instance |
| static chip::app::DefaultTimerDelegate sTimerDelegate; |
| static chip::app::reporting::ReportSchedulerImpl sReportScheduler(&sTimerDelegate); |
| initParams.reportScheduler = &sReportScheduler; |
| static chip::SimpleTestEventTriggerDelegate sSimpleTestEventTriggerDelegate; |
| initParams.testEventTriggerDelegate = &sSimpleTestEventTriggerDelegate; |
| (void) initParams.InitializeStaticResourcesBeforeServerInit(); |
| initParams.dataModelProvider = TestDataModelProviderInstance(initParams.persistentStorageDelegate); |
| // Use whatever server port the kernel decides to give us. |
| initParams.operationalServicePort = 0; |
| |
| ASSERT_EQ(chip::Server::GetInstance().Init(initParams), CHIP_NO_ERROR); |
| |
| Server::GetInstance().GetCommissioningWindowManager().CloseCommissioningWindow(); |
| } |
| static void TearDownTestSuite() |
| { |
| |
| // TODO: The platform memory was intentionally left not deinitialized so that minimal mdns can destruct |
| EXPECT_SUCCESS(chip::DeviceLayer::PlatformMgr().ScheduleWork(TearDownTask, 0)); |
| EXPECT_SUCCESS(chip::DeviceLayer::PlatformMgr().ScheduleWork(StopEventLoop)); |
| chip::DeviceLayer::PlatformMgr().RunEventLoop(); |
| |
| chip::DeviceLayer::PlatformMgr().Shutdown(); |
| |
| auto & mdnsAdvertiser = chip::Dnssd::ServiceAdvertiser::Instance(); |
| RETURN_SAFELY_IGNORED mdnsAdvertiser.RemoveServices(); |
| mdnsAdvertiser.Shutdown(); |
| |
| LoopbackMessagingContext::TearDownTestSuite(); |
| |
| // Server shutdown will be called in TearDownTask |
| |
| // TODO: At this point UDP endpoits still seem leaked and the sanitizer |
| // builds will attempt a memory free. As a result, we keep Memory initialized |
| // so that the global UDPManager can still be destructed without a coredump. |
| // |
| // This is likely either a missing shutdown or an actual UDP endpoint leak |
| // which I have not been able to track down yet. |
| // |
| // chip::Platform::MemoryShutdown(); |
| } |
| |
| void SetUp() override |
| { |
| ConfigInitializeNodes(false); |
| chip::Testing::LoopbackMessagingContext::SetUp(); |
| |
| sSimulateFailedSessionEstablishmentTaskCalled = false; |
| } |
| |
| void EstablishPASEHandshake(SessionManager & sessionManager, PASESession & pairingCommissioner, |
| TestSecurePairingDelegate & delegateCommissioner); |
| |
| void ServiceEvents(); |
| }; |
| |
| void TestCommissioningWindowManager::ServiceEvents() |
| { |
| DrainAndServiceIO(); |
| |
| ASSERT_SUCCESS(chip::DeviceLayer::PlatformMgr().ScheduleWork( |
| [](intptr_t) -> void { EXPECT_SUCCESS(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); }, (intptr_t) nullptr)); |
| chip::DeviceLayer::PlatformMgr().RunEventLoop(); |
| } |
| |
| Clusters::AdministratorCommissioningLogic::Context CreateContext() |
| { |
| return Clusters::AdministratorCommissioningLogic::Context{ |
| .commissioningWindowManager = Server::GetInstance().GetCommissioningWindowManager(), |
| .fabricTable = Server::GetInstance().GetFabricTable(), |
| .failSafeContext = Server::GetInstance().GetFailSafeContext(), |
| }; |
| } |
| |
| void TestCommissioningWindowManager::EstablishPASEHandshake(SessionManager & sessionManager, PASESession & pairingCommissioner, |
| TestSecurePairingDelegate & delegateCommissioner) |
| { |
| |
| auto & loopback = GetLoopback(); |
| loopback.mSentMessageCount = 0; |
| |
| ExchangeContext * contextCommissioner = NewUnauthenticatedExchangeToBob(&pairingCommissioner); |
| |
| EXPECT_EQ(GetExchangeManager().RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::PBKDFParamRequest, |
| &Server::GetInstance().GetCommissioningWindowManager()), |
| CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(pairingCommissioner.Pair(sessionManager, sTestSpake2p01_PinCode, Optional<ReliableMessageProtocolConfig>::Missing(), |
| contextCommissioner, &delegateCommissioner), |
| CHIP_NO_ERROR); |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(delegateCommissioner.mNumPairingErrors, 0u); |
| EXPECT_EQ(delegateCommissioner.mNumPairingComplete, 1u); |
| } |
| |
| class TemporarySessionManager |
| { |
| public: |
| TemporarySessionManager(TestCommissioningWindowManager & ctx) : mCtx(ctx) |
| { |
| EXPECT_EQ(CHIP_NO_ERROR, |
| mSessionManager.Init(&ctx.GetSystemLayer(), &ctx.GetTransportMgr(), &ctx.GetMessageCounterManager(), &mStorage, |
| &ctx.GetFabricTable(), ctx.GetSessionKeystore())); |
| // The setup here is really weird: we are using one session manager for |
| // the actual messages we send (the PASE handshake, so the |
| // unauthenticated sessions) and a different one for allocating the PASE |
| // sessions. Since our Init() set us up as the thing to handle messages |
| // on the transport manager, undo that. |
| mCtx.GetTransportMgr().SetSessionManager(&mCtx.GetSecureSessionManager()); |
| } |
| |
| ~TemporarySessionManager() |
| { |
| mSessionManager.Shutdown(); |
| // Reset the session manager on the transport again, just in case |
| // shutdown messed with it. |
| mCtx.GetTransportMgr().SetSessionManager(&mCtx.GetSecureSessionManager()); |
| } |
| |
| operator SessionManager &() { return mSessionManager; } |
| |
| TestCommissioningWindowManager & mCtx; |
| |
| private: |
| TestPersistentStorageDelegate mStorage; |
| SessionManager mSessionManager; |
| }; |
| |
| TEST_F(TestCommissioningWindowManager, TestCheckCommissioningWindowManagerBasicWindowOpenClose) |
| { |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| |
| EXPECT_EQ(commissionMgr.OpenBasicCommissioningWindow(commissionMgr.MaxCommissioningTimeout(), |
| CommissioningWindowAdvertisement::kDnssdOnly), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_TRUE(commissionMgr.GetOpenerFabricIndex().IsNull()); |
| EXPECT_TRUE(commissionMgr.GetOpenerVendorId().IsNull()); |
| EXPECT_FALSE(chip::DeviceLayer::ConnectivityMgr().IsBLEAdvertisingEnabled()); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| commissionMgr.CloseCommissioningWindow(); |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| } |
| |
| TEST_F(TestCommissioningWindowManager, TestCheckCommissioningWindowManagerBasicWindowOpenCloseFromCluster) |
| { |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| constexpr auto fabricIndex = static_cast<chip::FabricIndex>(1); |
| constexpr auto vendorId = static_cast<chip::VendorId>(0xFFF3); |
| EXPECT_EQ(commissionMgr.OpenBasicCommissioningWindowForAdministratorCommissioningCluster( |
| commissionMgr.MaxCommissioningTimeout(), fabricIndex, vendorId), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kBasicWindowOpen); |
| EXPECT_FALSE(commissionMgr.GetOpenerFabricIndex().IsNull()); |
| EXPECT_EQ(commissionMgr.GetOpenerFabricIndex().Value(), fabricIndex); |
| EXPECT_FALSE(commissionMgr.GetOpenerVendorId().IsNull()); |
| EXPECT_EQ(commissionMgr.GetOpenerVendorId().Value(), vendorId); |
| EXPECT_FALSE(chip::DeviceLayer::ConnectivityMgr().IsBLEAdvertisingEnabled()); |
| EXPECT_TRUE(sWindowStatusDirty); |
| EXPECT_TRUE(sAdminFabricIndexDirty); |
| EXPECT_TRUE(sAdminVendorIdDirty); |
| |
| ResetDirtyFlags(); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| commissionMgr.CloseCommissioningWindow(); |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_TRUE(commissionMgr.GetOpenerFabricIndex().IsNull()); |
| EXPECT_TRUE(commissionMgr.GetOpenerVendorId().IsNull()); |
| EXPECT_TRUE(sWindowStatusDirty); |
| EXPECT_TRUE(sAdminFabricIndexDirty); |
| EXPECT_TRUE(sAdminVendorIdDirty); |
| |
| ResetDirtyFlags(); |
| } |
| |
| void VerifyCommissioningWindowManagerWindowClosed() |
| { |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| } |
| |
| TEST_F(TestCommissioningWindowManager, TestCheckCommissioningWindowManagerWindowTimeout) |
| { |
| System::Clock::Internal::RAIIMockClock clock; |
| |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| MockAppDelegate delegateApp; |
| commissionMgr.SetAppDelegate(&delegateApp); |
| |
| constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds32(1); |
| constexpr uint16_t kTimeoutMs = 1000; |
| constexpr unsigned kSleepPadding = 100; |
| commissionMgr.OverrideMinCommissioningTimeout(kTimeoutSeconds); |
| EXPECT_SUCCESS(commissionMgr.OpenBasicCommissioningWindow(kTimeoutSeconds, CommissioningWindowAdvertisement::kDnssdOnly)); |
| |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowOpenedCount, 1u); |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowClosedCount, 0u); |
| |
| EXPECT_FALSE(chip::DeviceLayer::ConnectivityMgr().IsBLEAdvertisingEnabled()); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| // Advance time so that the commissioning window times out |
| clock.AdvanceMonotonic(chip::System::Clock::Milliseconds64(kTimeoutMs + kSleepPadding)); |
| ServiceEvents(); |
| |
| VerifyCommissioningWindowManagerWindowClosed(); |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowClosedCount, 1u); |
| |
| commissionMgr.SetAppDelegate(nullptr); |
| } |
| |
| void SimulateFailedSessionEstablishmentTask(chip::System::Layer *, void *) |
| { |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| commissionMgr.OnSessionEstablishmentStarted(); |
| commissionMgr.OnSessionEstablishmentError(CHIP_ERROR_INTERNAL); |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| sSimulateFailedSessionEstablishmentTaskCalled = true; |
| } |
| |
| TEST_F(TestCommissioningWindowManager, CheckCommissioningWindowManagerWindowTimeoutWithSessionEstablishmentErrors) |
| { |
| System::Clock::Internal::RAIIMockClock clock; |
| |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| EXPECT_FALSE(sSimulateFailedSessionEstablishmentTaskCalled); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| MockAppDelegate delegateApp; |
| commissionMgr.SetAppDelegate(&delegateApp); |
| |
| constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds16(1); |
| constexpr uint16_t kTimeoutMs = 1000; |
| constexpr unsigned kSleepPadding = 100; |
| constexpr uint16_t kFailedSessionEstablishmentTimeoutMs = kTimeoutMs / 4 * 3; |
| |
| commissionMgr.OverrideMinCommissioningTimeout(kTimeoutSeconds); |
| EXPECT_EQ(commissionMgr.OpenBasicCommissioningWindow(kTimeoutSeconds, CommissioningWindowAdvertisement::kDnssdOnly), |
| CHIP_NO_ERROR); |
| |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| |
| EXPECT_FALSE(chip::DeviceLayer::ConnectivityMgr().IsBLEAdvertisingEnabled()); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowOpenedCount, 1u); |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowClosedCount, 0u); |
| EXPECT_EQ(delegateApp.mOnCommissioningSessionEstablishmentErrorCount, 0u); |
| |
| // Simulate a session establishment error during that window, such that the |
| // delay for the error plus the window size exceeds the "timeout + padding" needed to result in Commissioning Window Closing. |
| EXPECT_SUCCESS(DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kFailedSessionEstablishmentTimeoutMs), |
| SimulateFailedSessionEstablishmentTask, nullptr)); |
| |
| // Advance time so that SimulateFailedSessionEstablishmentTask is fired |
| clock.AdvanceMonotonic(chip::System::Clock::Milliseconds64(kFailedSessionEstablishmentTimeoutMs)); |
| ServiceEvents(); |
| |
| EXPECT_TRUE(sSimulateFailedSessionEstablishmentTaskCalled); |
| EXPECT_EQ(delegateApp.mOnCommissioningSessionEstablishmentErrorCount, 1u); |
| |
| // Advance time so that the Commissioning Window times out and closes |
| clock.AdvanceMonotonic(chip::System::Clock::Milliseconds64(kTimeoutMs + kSleepPadding - kFailedSessionEstablishmentTimeoutMs)); |
| ServiceEvents(); |
| |
| VerifyCommissioningWindowManagerWindowClosed(); |
| EXPECT_EQ(delegateApp.mOnCommissioningWindowClosedCount, 1u); |
| |
| commissionMgr.SetAppDelegate(nullptr); |
| } |
| |
| TEST_F(TestCommissioningWindowManager, TestCheckCommissioningWindowManagerEnhancedWindow) |
| { |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| uint16_t originDiscriminator; |
| EXPECT_EQ(chip::DeviceLayer::GetCommissionableDataProvider()->GetSetupDiscriminator(originDiscriminator), CHIP_NO_ERROR); |
| uint16_t newDiscriminator = static_cast<uint16_t>(originDiscriminator + 1); |
| Spake2pVerifier verifier; |
| constexpr uint32_t kIterations = kSpake2p_Min_PBKDF_Iterations; |
| uint8_t salt[kSpake2p_Min_PBKDF_Salt_Length]; |
| chip::ByteSpan saltData(salt); |
| |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| constexpr auto fabricIndex = static_cast<chip::FabricIndex>(1); |
| constexpr auto vendorId = static_cast<chip::VendorId>(0xFFF3); |
| EXPECT_EQ(commissionMgr.OpenEnhancedCommissioningWindow(commissionMgr.MaxCommissioningTimeout(), newDiscriminator, verifier, |
| kIterations, saltData, fabricIndex, vendorId), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kEnhancedWindowOpen); |
| EXPECT_FALSE(chip::DeviceLayer::ConnectivityMgr().IsBLEAdvertisingEnabled()); |
| EXPECT_FALSE(commissionMgr.GetOpenerFabricIndex().IsNull()); |
| EXPECT_EQ(commissionMgr.GetOpenerFabricIndex().Value(), fabricIndex); |
| EXPECT_FALSE(commissionMgr.GetOpenerVendorId().IsNull()); |
| EXPECT_EQ(commissionMgr.GetOpenerVendorId().Value(), vendorId); |
| EXPECT_TRUE(sWindowStatusDirty); |
| EXPECT_TRUE(sAdminFabricIndexDirty); |
| EXPECT_TRUE(sAdminVendorIdDirty); |
| |
| ResetDirtyFlags(); |
| EXPECT_FALSE(sWindowStatusDirty); |
| EXPECT_FALSE(sAdminFabricIndexDirty); |
| EXPECT_FALSE(sAdminVendorIdDirty); |
| |
| commissionMgr.CloseCommissioningWindow(); |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| EXPECT_EQ(commissionMgr.CommissioningWindowStatusForCluster(), |
| chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); |
| EXPECT_TRUE(commissionMgr.GetOpenerFabricIndex().IsNull()); |
| EXPECT_TRUE(commissionMgr.GetOpenerVendorId().IsNull()); |
| EXPECT_TRUE(sWindowStatusDirty); |
| EXPECT_TRUE(sAdminFabricIndexDirty); |
| EXPECT_TRUE(sAdminVendorIdDirty); |
| |
| ResetDirtyFlags(); |
| } |
| |
| TEST_F(TestCommissioningWindowManager, RevokeCommissioningClearsPASESession) |
| { |
| TemporarySessionManager sessionManager(*this); |
| |
| TestSecurePairingDelegate delegateCommissioner; |
| PASESession pairingCommissioner; |
| auto & loopback = GetLoopback(); |
| loopback.Reset(); |
| |
| // Open an Enhanced Commissioning Window |
| uint16_t originDiscriminator; |
| EXPECT_EQ(chip::DeviceLayer::GetCommissionableDataProvider()->GetSetupDiscriminator(originDiscriminator), CHIP_NO_ERROR); |
| uint16_t newDiscriminator = static_cast<uint16_t>(originDiscriminator + 1); |
| |
| constexpr auto fabricIndex = static_cast<chip::FabricIndex>(1); |
| constexpr auto vendorId = static_cast<chip::VendorId>(0xFFF3); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| EXPECT_EQ(commissionMgr.OpenEnhancedCommissioningWindow(commissionMgr.MaxCommissioningTimeout(), newDiscriminator, |
| sTestSpake2p01_PASEVerifier, sTestSpake2p01_IterationCount, |
| ByteSpan(sTestSpake2p01_Salt), fabricIndex, vendorId), |
| CHIP_NO_ERROR); |
| |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| |
| // Establish PASE Handshake to the server's CommissioningWindowManager |
| EstablishPASEHandshake(sessionManager, pairingCommissioner, delegateCommissioner); |
| |
| // Ensure that a PASE Session exists for pairingCommissioner |
| auto commissionerSession = pairingCommissioner.CopySecureSession(); |
| EXPECT_TRUE(commissionerSession.HasValue()); |
| EXPECT_TRUE(commissionerSession.Value()->AsSecureSession()->IsPASESession()); |
| |
| // Ensure that a PASE Session exists for the CommissioningWindowManager |
| EXPECT_TRUE(commissionMgr.GetPASESession().HasValue()); |
| EXPECT_TRUE(commissionMgr.GetPASESession().Value()->AsSecureSession()->IsPASESession()); |
| |
| Clusters::AdministratorCommissioningLogic logic(CreateContext()); |
| Clusters::AdministratorCommissioning::Commands::RevokeCommissioning::DecodableType unused; |
| |
| ASSERT_EQ(logic.RevokeCommissioning(unused), Protocols::InteractionModel::Status::Success); |
| |
| // We need to service events here to allow the Async Events to be processed and make sure that the CommissioningWindowManager |
| // successfully shutdown the PASESession |
| ServiceEvents(); |
| |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| |
| // This asserts that the CommissioningWindowManager has cleared the PASESession |
| EXPECT_FALSE(commissionMgr.GetPASESession().HasValue()); |
| |
| // Asserting that PASESession is still present on the Commissioner side |
| commissionerSession = pairingCommissioner.CopySecureSession(); |
| EXPECT_TRUE(commissionerSession.HasValue()); |
| } |
| |
| // In this test-case RevokeCommissioning is called after commissioning Window times out but BEFORE fail-safe timer expires. |
| // The aim is to ensure that Revoke Commissioning forces Fail-Safe expiry and clears PASESession EVEN when Commissioning Window is |
| // Closed. |
| |
| // This Test will fail if RevokeCommissioning ONLY forces Fail-safe expiry when Commissioning Window is Open. |
| // This is a corner case that was not covered in Spec, that could happen if we establish PASE towards the end of the commissioning |
| // window, and then the administrator calls RevokeCommissioning; in that case, we would need to wait for fail-safe timer to expire |
| // --> The call to RevokeCommissioning should still clear the PASESession without having to wait for fail-safe timer expiry. |
| TEST_F(TestCommissioningWindowManager, RevokeCommissioningAfterCommissioningTimeoutClearsPASESession) |
| { |
| System::Clock::Internal::RAIIMockClock clock; |
| |
| TemporarySessionManager sessionManager(*this); |
| |
| TestSecurePairingDelegate delegateCommissioner; |
| PASESession pairingCommissioner; |
| auto & loopback = GetLoopback(); |
| loopback.Reset(); |
| |
| // Open an Enhanced Commissioning Window |
| uint16_t originDiscriminator; |
| EXPECT_EQ(chip::DeviceLayer::GetCommissionableDataProvider()->GetSetupDiscriminator(originDiscriminator), CHIP_NO_ERROR); |
| uint16_t newDiscriminator = static_cast<uint16_t>(originDiscriminator + 1); |
| |
| constexpr auto fabricIndex = static_cast<chip::FabricIndex>(1); |
| constexpr auto vendorId = static_cast<chip::VendorId>(0xFFF3); |
| |
| CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); |
| EXPECT_EQ(commissionMgr.OpenEnhancedCommissioningWindow(commissionMgr.MinCommissioningTimeout(), newDiscriminator, |
| sTestSpake2p01_PASEVerifier, sTestSpake2p01_IterationCount, |
| ByteSpan(sTestSpake2p01_Salt), fabricIndex, vendorId), |
| CHIP_NO_ERROR); |
| |
| EXPECT_TRUE(commissionMgr.IsCommissioningWindowOpen()); |
| |
| auto commissioningTimeout = commissionMgr.MinCommissioningTimeout(); |
| |
| // Advance time to just before the commissioning window times out (300 ms remaining) |
| clock.AdvanceMonotonic(commissioningTimeout - 300_ms); |
| ServiceEvents(); |
| |
| // Establish PASE Handshake to the server's CommissioningWindowManager |
| EstablishPASEHandshake(sessionManager, pairingCommissioner, delegateCommissioner); |
| |
| // Ensure that a PASE Session exists for pairingCommissioner |
| auto commissionerSession = pairingCommissioner.CopySecureSession(); |
| EXPECT_TRUE(commissionerSession.HasValue()); |
| EXPECT_TRUE(commissionerSession.Value()->AsSecureSession()->IsPASESession()); |
| |
| // Ensure that a PASE Session exists for the CommissioningWindowManager |
| EXPECT_TRUE(commissionMgr.GetPASESession().HasValue()); |
| EXPECT_TRUE(commissionMgr.GetPASESession().Value()->AsSecureSession()->IsPASESession()); |
| |
| // Advance time to 1000 ms after the commissioning window times out, while still before fail-safe timer expiry |
| clock.AdvanceMonotonic(1300_ms); |
| ServiceEvents(); |
| |
| // Ensuring that commissioning window did time out and that fail-safe did not expire yet |
| ASSERT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| ASSERT_TRUE(Server::GetInstance().GetFailSafeContext().IsFailSafeArmed()); |
| |
| Clusters::AdministratorCommissioningLogic logic(CreateContext()); |
| Clusters::AdministratorCommissioning::Commands::RevokeCommissioning::DecodableType unused; |
| |
| // RevokeCommissioning is invoked after the commissioning window has timed out and therefore returns StatusCode::kWindowNotOpen. |
| // However, it still explicitly forces fail-safe timer expiry regardless of the commissioning window state. |
| ASSERT_EQ(logic.RevokeCommissioning(unused), |
| InteractionModel::ClusterStatusCode::ClusterSpecificFailure( |
| Clusters::AdministratorCommissioning::StatusCode::kWindowNotOpen)); |
| |
| // We need to service events here to allow the Async Events to be processed and make sure that the CommissioningWindowManager |
| // successfully shutdown the PASESession |
| ServiceEvents(); |
| |
| EXPECT_FALSE(Server::GetInstance().GetFailSafeContext().IsFailSafeArmed()); |
| EXPECT_FALSE(commissionMgr.IsCommissioningWindowOpen()); |
| // This asserts that the CommissioningWindowManager has cleared the PASESession |
| EXPECT_FALSE(commissionMgr.GetPASESession().HasValue()); |
| |
| // Asserting that PASESession is still present on the Commissioner side |
| commissionerSession = pairingCommissioner.CopySecureSession(); |
| EXPECT_TRUE(commissionerSession.HasValue()); |
| } |
| |
| } // namespace |