diff --git a/src/app/clusters/bindings/BindingManager.cpp b/src/app/clusters/bindings/BindingManager.cpp index ce52f52867920d..26304df7a5e88b 100644 --- a/src/app/clusters/bindings/BindingManager.cpp +++ b/src/app/clusters/bindings/BindingManager.cpp @@ -48,6 +48,9 @@ class BindingFabricTableDelegate : public chip::FabricTable::Delegate // Intentionally left blank void OnFabricPersistedToStorage(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override {} + + // Intentionally left blank + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override {} }; BindingFabricTableDelegate gFabricTableDelegate; diff --git a/src/app/clusters/door-lock-server/door-lock-server.cpp b/src/app/clusters/door-lock-server/door-lock-server.cpp index 93f83a529953b5..f8d1fe8634f821 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.cpp +++ b/src/app/clusters/door-lock-server/door-lock-server.cpp @@ -73,6 +73,9 @@ class DoorLockClusterFabricDelegate : public chip::FabricTable::Delegate // Intentionally left blank void OnFabricPersistedToStorage(FabricTable & fabricTable, FabricIndex fabricIndex) override {} + + // Intentionally left blank + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override {} }; static DoorLockClusterFabricDelegate gFabricDelegate; diff --git a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp index 3c361a020ba9a0..41058931c2801e 100644 --- a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp +++ b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp @@ -460,6 +460,9 @@ class OpCredsFabricTableDelegate : public chip::FabricTable::Delegate ChipLogValueX64(fabric->GetNodeId()), fabric->GetVendorId()); fabricListChanged(); } + + // This is triggered by operation credential server so there is nothing additional that we need to do. + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override {} }; OpCredsFabricTableDelegate gFabricDelegate; diff --git a/src/app/server/Server.h b/src/app/server/Server.h index e8604f875ecd62..1048be81fe2690 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -436,6 +436,12 @@ class Server (void) fabricIndex; } + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override + { + (void) fabricTable; + (void) fabricIndex; + } + private: Server * mServer = nullptr; }; diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index e090926811d94b..5acbd03fb1193b 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -200,6 +200,12 @@ class DeviceControllerFactory (void) fabricIndex; } + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override + { + (void) fabricTable; + (void) fabricIndex; + } + private: SessionManager * mSessionManager = nullptr; Credentials::GroupDataProvider * mGroupDataProvider = nullptr; diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index 222ea1c278defb..418e97566965e9 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -754,6 +754,20 @@ class NotBeforeCollector : public Credentials::CertificateValidityPolicy System::Clock::Seconds32 mLatestNotBefore; }; +CHIP_ERROR FabricTable::NotifyNOCUpdatedOnFabric(FabricIndex fabricIndex) +{ + FabricTable::Delegate * delegate = mDelegateListRoot; + while (delegate) + { + // It is possible that delegate will remove itself from the list in OnFabricNOCUpdated, + // so we grab the next delegate in the list now. + FabricTable::Delegate * nextDelegate = delegate->next; + delegate->OnFabricNOCUpdated(*this, fabricIndex); + delegate = nextDelegate; + } + return CHIP_NO_ERROR; +} + CHIP_ERROR FabricTable::UpdateFabric(FabricIndex fabricIndex, FabricInfo & newFabricInfo) { FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); @@ -766,6 +780,9 @@ CHIP_ERROR FabricTable::UpdateFabric(FabricIndex fabricIndex, FabricInfo & newFa // for CASE and current time is also unknown, the certificate // validity policy will see this condition and can act appropriately. mLastKnownGoodTime.UpdateLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore); + + NotifyNOCUpdatedOnFabric(fabricIndex); + return CHIP_NO_ERROR; } @@ -892,8 +909,11 @@ CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex) FabricTable::Delegate * delegate = mDelegateListRoot; while (delegate) { + // It is possible that delegate will remove itself from the list in OnFabricDeletedFromStorage, + // so we grab the next delegate in the list now. + FabricTable::Delegate * nextDelegate = delegate->next; delegate->OnFabricDeletedFromStorage(*this, fabricIndex); - delegate = delegate->next; + delegate = nextDelegate; } } return CHIP_NO_ERROR; diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index 23968dc684093c..a17882cd08a8a3 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -364,6 +364,11 @@ class DLL_EXPORT FabricTable **/ virtual void OnFabricPersistedToStorage(FabricTable & fabricTable, FabricIndex fabricIndex) = 0; + /** + * Gets called when operational credentials are changed. + **/ + virtual void OnFabricNOCUpdated(FabricTable & fabricTable, FabricIndex fabricIndex) = 0; + // Intrusive list pointer for FabricTable to manage the entries. Delegate * next = nullptr; }; @@ -404,6 +409,12 @@ class DLL_EXPORT FabricTable */ CHIP_ERROR UpdateFabric(FabricIndex fabricIndex, FabricInfo & fabricInfo); + // TODO this #if CONFIG_IM_BUILD_FOR_UNIT_TEST is temporary. There is a change incoming soon + // that will allow triggering NOC update directly. +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + void SendUpdateFabricNotificationForTest(FabricIndex fabricIndex) { NotifyNOCUpdatedOnFabric(fabricIndex); } +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST + FabricInfo * FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId); FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex); const FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex) const; @@ -594,6 +605,8 @@ class DLL_EXPORT FabricTable CHIP_ERROR AddNewFabricInner(FabricInfo & fabric, FabricIndex * assignedIndex); + CHIP_ERROR NotifyNOCUpdatedOnFabric(FabricIndex fabricIndex); + FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS]; PersistentStorageDelegate * mStorage = nullptr; Crypto::OperationalKeystore * mOperationalKeystore = nullptr; diff --git a/src/protocols/secure_channel/CASESession.cpp b/src/protocols/secure_channel/CASESession.cpp index 2c26c89039a6d5..af21b52d4e194d 100644 --- a/src/protocols/secure_channel/CASESession.cpp +++ b/src/protocols/secure_channel/CASESession.cpp @@ -144,12 +144,30 @@ void CASESession::Clear() mState = State::kInitialized; Crypto::ClearSecretData(mIPK); + if (mFabricsTable) + { + mFabricsTable->RemoveFabricDelegate(&mFabricDelegate); + } + mLocalNodeId = kUndefinedNodeId; mPeerNodeId = kUndefinedNodeId; mFabricsTable = nullptr; mFabricIndex = kUndefinedFabricIndex; } +void CASESession::InvalidateIfPendingEstablishmentOnFabric(FabricIndex fabricIndex) +{ + if (mFabricIndex != fabricIndex) + { + return; + } + if (!IsSessionEstablishmentInProgress()) + { + return; + } + AbortPendingEstablish(CHIP_ERROR_CANCELLED); +} + CHIP_ERROR CASESession::Init(SessionManager & sessionManager, Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, const ScopedNodeId & sessionEvictionHint) { @@ -172,16 +190,19 @@ CHIP_ERROR CASESession::Init(SessionManager & sessionManager, Credentials::Certi } CHIP_ERROR -CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, FabricTable * fabrics, +CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, FabricTable * fabricTable, SessionResumptionStorage * sessionResumptionStorage, Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, ScopedNodeId previouslyEstablishedPeer, Optional mrpConfig) { - VerifyOrReturnError(fabrics != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(fabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(Init(sessionManager, policy, delegate, previouslyEstablishedPeer)); - mFabricsTable = fabrics; + CHIP_ERROR err = CHIP_NO_ERROR; + SuccessOrExit(err = fabricTable->AddFabricDelegate(&mFabricDelegate)); + + mFabricsTable = fabricTable; mRole = CryptoContext::SessionRole::kResponder; mSessionResumptionStorage = sessionResumptionStorage; mLocalMRPConfig = mrpConfig; @@ -189,7 +210,12 @@ CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, Fab ChipLogDetail(SecureChannel, "Allocated SecureSession (%p) - waiting for Sigma1 msg", mSecureSessionHolder.Get().Value()->AsSecureSession()); - return CHIP_NO_ERROR; +exit: + if (err != CHIP_NO_ERROR) + { + Clear(); + } + return err; } CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, ScopedNodeId peerScopedNodeId, @@ -222,6 +248,8 @@ CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, Fabric // been initialized SuccessOrExit(err); + SuccessOrExit(err = fabricTable->AddFabricDelegate(&mFabricDelegate)); + mFabricsTable = fabricTable; mFabricIndex = fabricInfo->GetFabricIndex(); mSessionResumptionStorage = sessionResumptionStorage; @@ -254,9 +282,14 @@ void CASESession::OnResponseTimeout(ExchangeContext * ec) // Discard the exchange so that Clear() doesn't try closing it. The // exchange will handle that. DiscardExchange(); + AbortPendingEstablish(CHIP_ERROR_TIMEOUT); +} + +void CASESession::AbortPendingEstablish(CHIP_ERROR err) +{ Clear(); // Do this last in case the delegate frees us. - mDelegate->OnSessionEstablishmentError(CHIP_ERROR_TIMEOUT); + mDelegate->OnSessionEstablishmentError(err); } CHIP_ERROR CASESession::DeriveSecureSession(CryptoContext & session) const @@ -1692,6 +1725,22 @@ CHIP_ERROR CASESession::OnMessageReceived(ExchangeContext * ec, const PayloadHea Protocols::SecureChannel::MsgType msgType = static_cast(payloadHeader.GetMessageType()); SuccessOrExit(err); +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + if (mStopHandshakeAtState.HasValue() && mState == mStopHandshakeAtState.Value()) + { + mStopHandshakeAtState = Optional::Missing(); + // For testing purposes we are trying to stop a successful CASESession from happening by dropping part of the + // handshake in the middle. We are trying to keep both sides of the CASESession establishment in an active + // pending state. In order to keep this side open we have to tell the exchange context that we will send an + // async message. + // + // Should you need to resume the CASESession you could theortically pass along msg to a callback that gets + // registered when setting mStopHandshakeAtState. + mExchangeCtxt->WillSendMessage(); + return CHIP_NO_ERROR; + } +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST + #if CHIP_CONFIG_SLOW_CRYPTO if (msgType == Protocols::SecureChannel::MsgType::CASE_Sigma1 || msgType == Protocols::SecureChannel::MsgType::CASE_Sigma2 || msgType == Protocols::SecureChannel::MsgType::CASE_Sigma2Resume || @@ -1791,9 +1840,7 @@ CHIP_ERROR CASESession::OnMessageReceived(ExchangeContext * ec, const PayloadHea // Discard the exchange so that Clear() doesn't try closing it. The // exchange will handle that. DiscardExchange(); - Clear(); - // Do this last in case the delegate frees us. - mDelegate->OnSessionEstablishmentError(err); + AbortPendingEstablish(err); } return err; } diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h index 2e8bf3f61f8ee7..2214948e1bdaa6 100644 --- a/src/protocols/secure_channel/CASESession.h +++ b/src/protocols/secure_channel/CASESession.h @@ -61,6 +61,19 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, public PairingSession { public: + // Public so it is accessible to unit test + enum class State : uint8_t + { + kInitialized = 0, + kSentSigma1 = 1, + kSentSigma2 = 2, + kSentSigma3 = 3, + kSentSigma1Resume = 4, + kSentSigma2Resume = 5, + kFinished = 6, + kFinishedViaResume = 7, + }; + ~CASESession() override; Transport::SecureSession::Type GetSecureSessionType() const override { return Transport::SecureSession::Type::kCASE; } @@ -73,7 +86,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, * Initialize using configured fabrics and wait for session establishment requests. * * @param sessionManager session manager from which to allocate a secure session object - * @param fabrics Table of fabrics that are currently configured on the device + * @param fabricTable Table of fabrics that are currently configured on the device * @param policy Optional application-provided certificate validity policy * @param delegate Callback object * @param previouslyEstablishedPeer If a session had previously been established successfully to a peer, this should @@ -84,7 +97,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, * @return CHIP_ERROR The result of initialization */ CHIP_ERROR PrepareForSessionEstablishment( - SessionManager & sessionManager, FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage, + SessionManager & sessionManager, FabricTable * fabricTable, SessionResumptionStorage * sessionResumptionStorage, Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, ScopedNodeId previouslyEstablishedPeer, Optional mrpConfig = Optional::Missing()); @@ -173,17 +186,44 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, **/ void Clear(); + void InvalidateIfPendingEstablishmentOnFabric(FabricIndex fabricIndex); + +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + void SetStopSigmaHandshakeAt(Optional state) { mStopHandshakeAtState = state; } +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST + private: - enum class State : uint8_t + class CASESessionFabricDelegate final : public chip::FabricTable::Delegate { - kInitialized = 0, - kSentSigma1 = 1, - kSentSigma2 = 2, - kSentSigma3 = 3, - kSentSigma1Resume = 4, - kSentSigma2Resume = 5, - kFinished = 6, - kFinishedViaResume = 7, + public: + CASESessionFabricDelegate(CASESession * caseSession) : mCASESession(caseSession) {} + + void OnFabricDeletedFromStorage(FabricTable & fabricTable, FabricIndex fabricIndex) override + { + (void) fabricTable; + mCASESession->InvalidateIfPendingEstablishmentOnFabric(fabricIndex); + } + + void OnFabricRetrievedFromStorage(FabricTable & fabricTable, FabricIndex fabricIndex) override + { + (void) fabricTable; + (void) fabricIndex; + } + + void OnFabricPersistedToStorage(FabricTable & fabricTable, FabricIndex fabricIndex) override + { + (void) fabricTable; + (void) fabricIndex; + } + + void OnFabricNOCUpdated(chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override + { + (void) fabricTable; + mCASESession->InvalidateIfPendingEstablishmentOnFabric(fabricIndex); + } + + private: + CASESession * mCASESession; }; /* @@ -237,6 +277,8 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, void OnSuccessStatusReport() override; CHIP_ERROR OnFailureStatusReport(Protocols::SecureChannel::GeneralStatusCode generalCode, uint16_t protocolCode) override; + void AbortPendingEstablish(CHIP_ERROR err); + CHIP_ERROR GetHardcodedTime(); CHIP_ERROR SetEffectiveTime(); @@ -271,7 +313,12 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, // Sigma1 initiator random, maintained to be reused post-Sigma1, such as when generating Sigma2 S2RK key uint8_t mInitiatorRandom[kSigmaParamRandomNumberSize]; + CASESessionFabricDelegate mFabricDelegate{ this }; State mState; + +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + Optional mStopHandshakeAtState = Optional::Missing(); +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST }; } // namespace chip diff --git a/src/protocols/secure_channel/PairingSession.cpp b/src/protocols/secure_channel/PairingSession.cpp index b3721ca8533a45..8b12fa69974b80 100644 --- a/src/protocols/secure_channel/PairingSession.cpp +++ b/src/protocols/secure_channel/PairingSession.cpp @@ -138,6 +138,17 @@ CHIP_ERROR PairingSession::DecodeMRPParametersIfPresent(TLV::Tag expectedTag, TL return tlvReader.ExitContainer(containerType); } +bool PairingSession::IsSessionEstablishmentInProgress() +{ + if (!mSecureSessionHolder) + { + return false; + } + + Transport::SecureSession * secureSession = mSecureSessionHolder->AsSecureSession(); + return secureSession->IsPairing(); +} + void PairingSession::Clear() { // Clear acts like the destructor if PairingSession, if it is call during diff --git a/src/protocols/secure_channel/PairingSession.h b/src/protocols/secure_channel/PairingSession.h index bc9d75892b2439..8df5f09ffbdacf 100644 --- a/src/protocols/secure_channel/PairingSession.h +++ b/src/protocols/secure_channel/PairingSession.h @@ -189,6 +189,8 @@ class DLL_EXPORT PairingSession : public SessionDelegate */ CHIP_ERROR DecodeMRPParametersIfPresent(TLV::Tag expectedTag, TLV::ContiguousBufferTLVReader & tlvReader); + bool IsSessionEstablishmentInProgress(); + // TODO: remove Clear, we should create a new instance instead reset the old instance. void Clear(); diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index 86626ae08a2fb7..731e782e8f3249 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -236,21 +236,25 @@ void CASE_SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext) // Test all combinations of invalid parameters TestCASESecurePairingDelegate delegate; - CASESession pairing; FabricTable fabrics; + // In normal operation scope of FabricTable outlives CASESession. Without this scoping we hit + // ASAN test issue since FabricTable is not normally on the stack. + { + CASESession caseSession; - NL_TEST_ASSERT(inSuite, pairing.GetSecureSessionType() == SecureSession::Type::kCASE); + NL_TEST_ASSERT(inSuite, caseSession.GetSecureSessionType() == SecureSession::Type::kCASE); - pairing.SetGroupDataProvider(&gDeviceGroupDataProvider); - NL_TEST_ASSERT(inSuite, - pairing.PrepareForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, nullptr, ScopedNodeId()) == - CHIP_ERROR_INVALID_ARGUMENT); - NL_TEST_ASSERT(inSuite, - pairing.PrepareForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, &delegate, ScopedNodeId()) == - CHIP_ERROR_INVALID_ARGUMENT); - NL_TEST_ASSERT(inSuite, - pairing.PrepareForSessionEstablishment(sessionManager, &fabrics, nullptr, nullptr, &delegate, ScopedNodeId()) == - CHIP_NO_ERROR); + caseSession.SetGroupDataProvider(&gDeviceGroupDataProvider); + NL_TEST_ASSERT(inSuite, + caseSession.PrepareForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, nullptr, + ScopedNodeId()) == CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, + caseSession.PrepareForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, &delegate, + ScopedNodeId()) == CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, + caseSession.PrepareForSessionEstablishment(sessionManager, &fabrics, nullptr, nullptr, &delegate, + ScopedNodeId()) == CHIP_NO_ERROR); + } } void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) @@ -344,11 +348,26 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte NL_TEST_ASSERT(inSuite, loopback.mSentMessageCount == sTestCaseMessageCount); NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 1); NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 1); + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 0); NL_TEST_ASSERT(inSuite, pairingAccessory.GetRemoteMRPConfig().mIdleRetransTimeout == System::Clock::Milliseconds32(5000)); NL_TEST_ASSERT(inSuite, pairingAccessory.GetRemoteMRPConfig().mActiveRetransTimeout == System::Clock::Milliseconds32(300)); NL_TEST_ASSERT(inSuite, pairingCommissioner.GetRemoteMRPConfig().mIdleRetransTimeout == System::Clock::Milliseconds32(360000)); NL_TEST_ASSERT(inSuite, pairingCommissioner.GetRemoteMRPConfig().mActiveRetransTimeout == System::Clock::Milliseconds32(100000)); +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + // Confirming that FabricTable sending a notification that fabric was updated doesn't affect + // already established connections. + // + // This is compiled for host tests which is enough test coverage + gCommissionerFabrics.SendUpdateFabricNotificationForTest(gCommissionerFabricIndex); + gDeviceFabrics.SendUpdateFabricNotificationForTest(gDeviceFabricIndex); + NL_TEST_ASSERT(inSuite, loopback.mSentMessageCount == sTestCaseMessageCount); + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 1); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 1); + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 0); +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST } void CASE_SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) @@ -824,6 +843,67 @@ static void CASE_SessionResumptionStorage(nlTestSuite * inSuite, void * inContex } } +#if CONFIG_IM_BUILD_FOR_UNIT_TEST +static void CASE_SimulateUpdateNOCInvalidatePendingEstablishment(nlTestSuite * inSuite, void * inContext) +{ + SessionManager sessionManager; + TestCASESecurePairingDelegate delegateCommissioner; + CASESession pairingCommissioner; + pairingCommissioner.SetGroupDataProvider(&gCommissionerGroupDataProvider); + + TestContext & ctx = *reinterpret_cast(inContext); + + TestCASESecurePairingDelegate delegateAccessory; + CASESession pairingAccessory; + + auto & loopback = ctx.GetLoopback(); + loopback.mSentMessageCount = 0; + + NL_TEST_ASSERT(inSuite, + ctx.GetExchangeManager().RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::CASE_Sigma1, + &pairingAccessory) == CHIP_NO_ERROR); + + pairingCommissioner.SetStopSigmaHandshakeAt(MakeOptional(CASESession::State::kSentSigma1)); + + ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(&pairingCommissioner); + + pairingAccessory.SetGroupDataProvider(&gDeviceGroupDataProvider); + NL_TEST_ASSERT(inSuite, + pairingAccessory.PrepareForSessionEstablishment(sessionManager, &gDeviceFabrics, nullptr, nullptr, + &delegateAccessory, ScopedNodeId()) == CHIP_NO_ERROR); + + gDeviceFabrics.SendUpdateFabricNotificationForTest(gDeviceFabricIndex); + ctx.DrainAndServiceIO(); + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0); + + NL_TEST_ASSERT(inSuite, + pairingCommissioner.EstablishSession(sessionManager, &gCommissionerFabrics, + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, contextCommissioner, + nullptr, nullptr, &delegateCommissioner) == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 0); + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 0); + + gCommissionerFabrics.SendUpdateFabricNotificationForTest(gCommissionerFabricIndex); + ctx.DrainAndServiceIO(); + + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 1); + + gDeviceFabrics.SendUpdateFabricNotificationForTest(gDeviceFabricIndex); + ctx.DrainAndServiceIO(); + + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 1); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 1); + + NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 0); + NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 0); +} +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST + // Test Suite /** @@ -839,6 +919,11 @@ static const nlTest sTests[] = NL_TEST_DEF("Sigma1Parsing", CASE_Sigma1ParsingTest), NL_TEST_DEF("DestinationId", CASE_DestinationIdTest), NL_TEST_DEF("SessionResumptionStorage", CASE_SessionResumptionStorage), +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + // This is compiled for host tests which is enough test coverage to ensure updating NOC invalidates + // CASESession that are in the process of establishing. + NL_TEST_DEF("InvalidatePendingSessionEstablishment", CASE_SimulateUpdateNOCInvalidatePendingEstablishment), +#endif // CONFIG_IM_BUILD_FOR_UNIT_TEST NL_TEST_SENTINEL() };