/* * * Copyright (c) 2023 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 { class AutoDeleteEstablisher { public: AutoDeleteEstablisher(SubscriptionResumptionSessionEstablisher * sessionEstablisher) : mSessionEstablisher(sessionEstablisher) {} ~AutoDeleteEstablisher() { chip::Platform::Delete(mSessionEstablisher); } SubscriptionResumptionSessionEstablisher * operator->() const { return mSessionEstablisher; } SubscriptionResumptionSessionEstablisher & operator*() const { return *mSessionEstablisher; } private: SubscriptionResumptionSessionEstablisher * mSessionEstablisher; }; SubscriptionResumptionSessionEstablisher::SubscriptionResumptionSessionEstablisher() : mOnConnectedCallback(HandleDeviceConnected, this), mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this) {} CHIP_ERROR SubscriptionResumptionSessionEstablisher::ResumeSubscription( CASESessionManager & caseSessionManager, const SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo) { mSubscriptionInfo.mNodeId = subscriptionInfo.mNodeId; mSubscriptionInfo.mFabricIndex = subscriptionInfo.mFabricIndex; mSubscriptionInfo.mSubscriptionId = subscriptionInfo.mSubscriptionId; mSubscriptionInfo.mMinInterval = subscriptionInfo.mMinInterval; mSubscriptionInfo.mMaxInterval = subscriptionInfo.mMaxInterval; mSubscriptionInfo.mFabricFiltered = subscriptionInfo.mFabricFiltered; #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION mSubscriptionInfo.mResumptionRetries = subscriptionInfo.mResumptionRetries; #endif // Copy the Attribute Paths and Event Paths if (subscriptionInfo.mAttributePaths.AllocatedSize() > 0) { mSubscriptionInfo.mAttributePaths.Alloc(subscriptionInfo.mAttributePaths.AllocatedSize()); if (!mSubscriptionInfo.mAttributePaths.Get()) { return CHIP_ERROR_NO_MEMORY; } for (size_t i = 0; i < mSubscriptionInfo.mAttributePaths.AllocatedSize(); ++i) { mSubscriptionInfo.mAttributePaths[i] = subscriptionInfo.mAttributePaths[i]; } } if (subscriptionInfo.mEventPaths.AllocatedSize() > 0) { mSubscriptionInfo.mEventPaths.Alloc(subscriptionInfo.mEventPaths.AllocatedSize()); if (!mSubscriptionInfo.mEventPaths.Get()) { return CHIP_ERROR_NO_MEMORY; } for (size_t i = 0; i < mSubscriptionInfo.mEventPaths.AllocatedSize(); ++i) { mSubscriptionInfo.mEventPaths[i] = subscriptionInfo.mEventPaths[i]; } } ScopedNodeId peerNode = ScopedNodeId(mSubscriptionInfo.mNodeId, mSubscriptionInfo.mFabricIndex); caseSessionManager.FindOrEstablishSession(peerNode, &mOnConnectedCallback, &mOnConnectionFailureCallback); return CHIP_NO_ERROR; } void SubscriptionResumptionSessionEstablisher::HandleDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) { AutoDeleteEstablisher establisher(static_cast(context)); SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo; InteractionModelEngine * imEngine = InteractionModelEngine::GetInstance(); // Decrement the number of subscriptions to resume since we have completed our retry attempt for a given subscription. // We do this before the readHandler creation since we do not care if the subscription has successfully been resumed or // not. Counter only tracks the number of individual subscriptions we will try to resume. imEngine->DecrementNumSubscriptionsToResume(); if (!imEngine->EnsureResourceForSubscription(subscriptionInfo.mFabricIndex, subscriptionInfo.mAttributePaths.AllocatedSize(), subscriptionInfo.mEventPaths.AllocatedSize())) { // TODO - Should we keep the subscription here? ChipLogProgress(InteractionModel, "no resource for subscription resumption"); return; } ReadHandler * readHandler = imEngine->mReadHandlers.CreateObject(*imEngine, imEngine->GetReportScheduler(), imEngine->GetDataModelProvider()); if (readHandler == nullptr) { // TODO - Should we keep the subscription here? ChipLogProgress(InteractionModel, "no resource for ReadHandler creation"); return; } readHandler->OnSubscriptionResumed(sessionHandle, *establisher); #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION // Reset the resumption retries to 0 if subscription is resumed subscriptionInfo.mResumptionRetries = 0; auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage(); if (subscriptionResumptionStorage) { subscriptionResumptionStorage->Save(subscriptionInfo); } #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION } void SubscriptionResumptionSessionEstablisher::HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) { AutoDeleteEstablisher establisher(static_cast(context)); InteractionModelEngine * imEngine = InteractionModelEngine::GetInstance(); SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo; ChipLogError(DataManagement, "Failed to establish CASE for subscription-resumption with error '%" CHIP_ERROR_FORMAT "'", error.Format()); // Decrement the number of subscriptions to resume since we have completed our retry attempt for a given subscription. // We do this here since we were not able to connect to the subscriber thus we have completed our resumption attempt. // Counter only tracks the number of individual subscriptions we will try to resume. imEngine->DecrementNumSubscriptionsToResume(); auto * subscriptionResumptionStorage = imEngine->GetSubscriptionResumptionStorage(); if (!subscriptionResumptionStorage) { ChipLogError(DataManagement, "Failed to get subscription resumption storage"); return; } #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION if (subscriptionInfo.mResumptionRetries <= CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX) { InteractionModelEngine::GetInstance()->TryToResumeSubscriptions(); subscriptionInfo.mResumptionRetries++; subscriptionResumptionStorage->Save(subscriptionInfo); } else #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION { // If the device fails to establish the session several times, the subscriber might be offline and its subscription // read client will be deleted when the device reconnects to the subscriber. This subscription will be never used again. // Clean up the persistent subscription information storage. subscriptionResumptionStorage->Delete(subscriptionInfo.mNodeId, subscriptionInfo.mFabricIndex, subscriptionInfo.mSubscriptionId); } } } // namespace app } // namespace chip