Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #pragma once
18 :
19 : #include <app/icd/server/ICDServerConfig.h>
20 :
21 : #include <app/AppConfig.h>
22 : #include <app/SubscriptionsInfoProvider.h>
23 : #include <app/TestEventTriggerDelegate.h>
24 : #include <app/icd/server/ICDConfigurationData.h>
25 : #include <app/icd/server/ICDNotifier.h>
26 : #include <app/icd/server/ICDStateObserver.h>
27 : #include <credentials/FabricTable.h>
28 : #include <crypto/SessionKeystore.h>
29 : #include <lib/support/BitFlags.h>
30 : #include <messaging/ExchangeMgr.h>
31 : #include <platform/CHIPDeviceConfig.h>
32 : #include <platform/internal/CHIPDeviceLayerInternal.h>
33 : #include <system/SystemClock.h>
34 :
35 : #if CHIP_CONFIG_ENABLE_ICD_CIP
36 : #include <app/icd/server/ICDCheckInBackOffStrategy.h> // nogncheck
37 : #include <app/icd/server/ICDCheckInSender.h> // nogncheck
38 : #include <app/icd/server/ICDMonitoringTable.h> // nogncheck
39 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
40 :
41 : namespace chip {
42 : namespace Crypto {
43 : using SymmetricKeystore = SessionKeystore;
44 : }
45 : } // namespace chip
46 :
47 : namespace chip {
48 : namespace app {
49 :
50 : // Forward declaration of TestICDManager tests to allow it to be friend with ICDManager
51 : // Used in unit tests
52 : class TestICDManager_TestShouldCheckInMsgsBeSentAtActiveModeFunction_Test;
53 :
54 : /**
55 : * @brief ICD Manager is responsible of processing the events and triggering the correct action for an ICD
56 : */
57 : class ICDManager : public ICDListener, public TestEventTriggerHandler
58 : {
59 : public:
60 : /**
61 : * @brief This structure is used for the creation an ObjectPool of ICDStateObserver pointers
62 : */
63 : struct ObserverPointer
64 : {
65 10 : ObserverPointer(ICDStateObserver * obs) : mObserver(obs) {}
66 10 : ~ObserverPointer() { mObserver = nullptr; }
67 : ICDStateObserver * mObserver;
68 : };
69 :
70 : enum class OperationalState : uint8_t
71 : {
72 : IdleMode,
73 : ActiveMode,
74 : };
75 :
76 : /**
77 : * @brief This enum class represents all ICDStateObserver callbacks available from the
78 : * mStateObserverPool for the ICDManager.
79 : *
80 : * EnterActiveMode, TransitionToIdle and EnterIdleMode will always be called as a trio in the same order.
81 : * Each event will only be called once per cycle.
82 : * EnterActiveMode will always be called first, when the ICD has transitioned to ActiveMode.
83 : * TransitionToIdle will always be second. This event will only be called the first time there is
84 : * `ICD_ACTIVE_TIME_JITTER_MS` remaining to the ActiveMode timer.
85 : * When this event is called, the ICD is still in ActiveMode.
86 : * If the ActiveMode timer is increased due to the TransitionToIdle event, the event will not be called a second time in
87 : * a given cycle.
88 : * OnEnterIdleMode will always the third event and indicates that the ICD has transitioned to IdleMode.
89 : *
90 : * The ICDModeChange event can occur independently from the EnterActiveMode, TransitionToIdle and EnterIdleMode.
91 : * It will typically happen at the ICDManager init when a client is already registered with the ICD before the
92 : * OnEnterIdleMode event or when a client sends a register command after the OnEnterActiveMode event. Nothing prevents
93 : * the ICDModeChange event from happening multiple times per cycle or while the ICD is in IdleMode.
94 : *
95 : * See src/app/icd/server/ICDStateObserver.h for more information on the APIs each event triggers
96 : */
97 : enum class ObserverEventType : uint8_t
98 : {
99 : EnterActiveMode,
100 : EnterIdleMode,
101 : TransitionToIdle,
102 : ICDModeChange,
103 : };
104 :
105 : /**
106 : * @brief Verifier template function
107 : * This type can be used to implement specific verifiers that can be used in the CheckInMessagesWouldBeSent function.
108 : * The goal is to avoid having multiple functions that implement the iterator loop with only the check changing.
109 : *
110 : * @return true: if at least one Check-In message would be sent
111 : * false: No Check-In messages would be sent
112 : */
113 : using ShouldCheckInMsgsBeSentFunction = bool(FabricIndex aFabricIndex, NodeId subjectID);
114 :
115 10 : ICDManager() = default;
116 10 : ~ICDManager() = default;
117 :
118 : /*
119 : Builder function to set all necessary members for the ICDManager class
120 : */
121 :
122 : #if CHIP_CONFIG_ENABLE_ICD_CIP
123 : ICDManager & SetPersistentStorageDelegate(PersistentStorageDelegate * storage)
124 : {
125 : mStorage = storage;
126 : return *this;
127 : };
128 :
129 : ICDManager & SetFabricTable(FabricTable * fabricTable)
130 : {
131 : mFabricTable = fabricTable;
132 : return *this;
133 : };
134 :
135 : ICDManager & SetSymmetricKeyStore(Crypto::SymmetricKeystore * symmetricKeystore)
136 : {
137 : mSymmetricKeystore = symmetricKeystore;
138 : return *this;
139 : };
140 :
141 : ICDManager & SetExchangeManager(Messaging::ExchangeManager * exchangeManager)
142 : {
143 : mExchangeManager = exchangeManager;
144 : return *this;
145 : };
146 :
147 : ICDManager & SetSubscriptionsInfoProvider(SubscriptionsInfoProvider * subInfoProvider)
148 : {
149 : mSubInfoProvider = subInfoProvider;
150 : return *this;
151 : };
152 :
153 : ICDManager & SetICDCheckInBackOffStrategy(ICDCheckInBackOffStrategy * strategy)
154 : {
155 : mICDCheckInBackOffStrategy = strategy;
156 : return *this;
157 : };
158 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
159 :
160 : /**
161 : * @brief Validates that the ICDManager has all the necessary members to function and initializes the class
162 : */
163 : void Init();
164 : void Shutdown();
165 :
166 : /**
167 : * @brief SupportsFeature verifies if a given FeatureMap bit is enabled
168 : *
169 : * @param[in] feature FeatureMap bit to verify
170 : *
171 : * @return true: if the FeatureMap bit is enabled in the ICDM cluster attribute.
172 : * false: if the FeatureMap bit is not enabled in the ICDM cluster attribute.
173 : * if we failed to read the FeatureMap attribute.
174 : */
175 : bool SupportsFeature(Clusters::IcdManagement::Feature feature);
176 :
177 : // See ICDConfigurationData::SetModeDurations
178 6 : CHIP_ERROR SetModeDurations(Optional<System::Clock::Milliseconds32> activeModeDuration,
179 : Optional<System::Clock::Milliseconds32> idleModeDuration)
180 : {
181 6 : return ICDConfigurationData::GetInstance().SetModeDurations(activeModeDuration, idleModeDuration);
182 : };
183 :
184 : CHIP_ERROR SetModeDurations(std::optional<System::Clock::Milliseconds32> activeModeDuration,
185 : std::optional<System::Clock::Seconds32> idleModeDuration,
186 : std::optional<System::Clock::Seconds32> shortIdleModeDuration)
187 : {
188 : return ICDConfigurationData::GetInstance().SetModeDurations(activeModeDuration, idleModeDuration, shortIdleModeDuration);
189 : };
190 :
191 : ICDConfigurationData::ICDMode GetICDMode() { return ICDConfigurationData::GetInstance().GetICDMode(); };
192 :
193 30 : OperationalState GetOperaionalState() { return mOperationalState; };
194 :
195 : /**
196 : * @brief Adds the referenced observer in parameters to the mStateObserverPool
197 : * A maximum of CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE observers can be concurrently registered
198 : *
199 : * @return The pointer to the pool object, or null if it could not be added.
200 : */
201 : ObserverPointer * RegisterObserver(ICDStateObserver * observer);
202 :
203 : /**
204 : * @brief Remove the referenced observer in parameters from the mStateObserverPool
205 : * If the observer is not present in the object pool, we do nothing
206 : */
207 : void ReleaseObserver(ICDStateObserver * observer);
208 :
209 : /**
210 : * @brief Ensures that the remaining Active Mode duration is at least the smaller of 30000 milliseconds and stayActiveDuration.
211 : *
212 : * @param[in] stayActiveDuration The duration (in milliseconds) requested by the client to stay in Active Mode
213 : * @return The duration (in milliseconds) the device will stay in Active Mode
214 : */
215 : uint32_t StayActiveRequest(uint32_t stayActiveDuration);
216 :
217 : /**
218 : * @brief TestEventTriggerHandler for the ICD feature set
219 : *
220 : * @param[in] eventTrigger Event trigger to handle.
221 : *
222 : * @return CHIP_ERROR CHIP_NO_ERROR - No erros during the processing
223 : * CHIP_ERROR_INVALID_ARGUMENT - eventTrigger isn't a valid value
224 : */
225 : CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override;
226 :
227 : #if CHIP_CONFIG_ENABLE_ICD_CIP
228 : /**
229 : * @brief Trigger the ICDManager to send Check-In message if necessary
230 : *
231 : * @param[in] function to use to determine if we need to send check-in messages
232 : */
233 : void TriggerCheckInMessages(const std::function<ShouldCheckInMsgsBeSentFunction> & function);
234 :
235 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
236 : /**
237 : * @brief Set mSubCheckInBootCheckExecuted to true
238 : * Function allows the InteractionModelEngine to notify the ICDManager that the boot up subscription resumption has been
239 : * completed.
240 : */
241 : void SetBootUpResumeSubscriptionExecuted() { mIsBootUpResumeSubscriptionExecuted = true; };
242 : #endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
243 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
244 :
245 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
246 : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
247 : bool GetIsBootUpResumeSubscriptionExecuted() { return mIsBootUpResumeSubscriptionExecuted; };
248 : #endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
249 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
250 :
251 : // Implementation of ICDListener functions.
252 : // Callers must origin from the chip task context or hold the ChipStack lock.
253 :
254 : void OnNetworkActivity() override;
255 : void OnKeepActiveRequest(KeepActiveFlags request) override;
256 : void OnActiveRequestWithdrawal(KeepActiveFlags request) override;
257 :
258 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
259 : void OnSITModeRequest() override;
260 : void OnSITModeRequestWithdrawal() override;
261 : #endif
262 :
263 : void OnICDManagementServerEvent(ICDManagementEvents event) override;
264 : void OnSubscriptionReport() override;
265 :
266 : #if CHIP_CONFIG_ENABLE_ICD_SERVER && CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_ENABLE_ICD_CHECK_IN_ON_REPORT_TIMEOUT
267 : void OnSendCheckIn(const Access::SubjectDescriptor & subject) override;
268 : #endif // CHIP_CONFIG_ENABLE_ICD_SERVER && CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_ENABLE_ICD_CHECK_IN_ON_REPORT_TIMEOUT
269 :
270 : private:
271 : // TODO : Once <gtest/gtest_prod.h> can be included, use FRIEND_TEST for the friend class.
272 : friend class TestICDManager_TestShouldCheckInMsgsBeSentAtActiveModeFunction_Test;
273 :
274 : /**
275 : * @brief UpdateICDMode evaluates in which mode the ICD can be in; SIT or LIT mode.
276 : * If the current operating mode does not match the evaluated operating mode, function updates the ICDMode and triggers
277 : * all necessary operations.
278 : * For a SIT ICD, this function does nothing.
279 : * For a LIT ICD, the function checks if the ICD has a registration in the ICDMonitoringTable to determine which ICDMode
280 : * the ICD must be in.
281 : */
282 : void UpdateICDMode();
283 :
284 : /**
285 : * @brief UpdateOperationState updates the OperationState of the ICD to the requested one.
286 : * IdleMode -> IdleMode : No actions are necessary, do nothing.
287 : * IdleMode -> ActiveMode : Transition the device to ActiveMode, start the ActiveMode timer and trigger all necessary
288 : * operations. These operations could be : Send Check-In messages
289 : * Send subscription reports
290 : * Process user actions
291 : * ActiveMode -> ActiveMode : Increase remaining ActiveMode timer to one ActiveModeThreshold.
292 : * If ActiveModeThreshold is 0, do nothing.
293 : * ActiveMode -> IdleMode : Transition ICD to IdleMode and start the IdleMode timer.
294 : *
295 : * @param state requested OperationalState for the ICD to transition to
296 : */
297 : void UpdateOperationState(OperationalState state);
298 :
299 : /**
300 : * @brief Set or Remove a keep ActiveMode requirement for the given flag
301 : * If state is true and the ICD is in IdleMode, transition the ICD to ActiveMode
302 : * If state is false and the ICD is in ActiveMode, check whether we can transition the ICD to IdleMode.
303 : * If we can, transition the ICD to IdleMode.
304 : *
305 : * @param flag KeepActiveFlag to remove or add
306 : * @param state true: adding a flag requirement
307 : * false: removing a flag requirement
308 : */
309 : void SetKeepActiveModeRequirements(KeepActiveFlags flag, bool state);
310 :
311 : /**
312 : * @brief Associates the ObserverEventType parameters to the correct
313 : * ICDStateObservers function and calls it for all observers in the mStateObserverPool
314 : */
315 : void postObserverEvent(ObserverEventType event);
316 :
317 : /**
318 : * @brief Hepler function that extends the ActiveMode timer as well as the Active Mode Jitter timer for the transition to
319 : * idle mode event.
320 : */
321 : void ExtendActiveMode(System::Clock::Milliseconds16 extendDuration);
322 :
323 : /**
324 : * @brief Timer callback function for when the IdleMode timer expires
325 : *
326 : * @param appState pointer to the ICDManager
327 : */
328 : static void OnIdleModeDone(System::Layer * aLayer, void * appState);
329 :
330 : /**
331 : * @brief Timer callback function for when the ActiveMode timer expires
332 : *
333 : * @param appState pointer to the ICDManager
334 : */
335 : static void OnActiveModeDone(System::Layer * aLayer, void * appState);
336 :
337 : /**
338 : * @brief Timer Callback function called shortly before the device enters idle mode to allow checks to be made.
339 : * This is currently only called once to prevent entering in a loop if some events re-trigger this check (for instance if
340 : * a check for subscriptions before entering idle mode leads to emiting a report, we will re-enter UpdateOperationState
341 : * and check again for subscription, etc.)
342 : *
343 : * @param appState pointer to the ICDManager
344 : */
345 : static void OnTransitionToIdle(System::Layer * aLayer, void * appState);
346 :
347 : #if CHIP_CONFIG_ENABLE_ICD_CIP
348 : /**
349 : * @brief Function triggers all necessary Check-In messages to be sent.
350 : *
351 : * @note For each ICDMonitoring entry, we check if should send a Check-In message with
352 : * ShouldCheckInMsgsBeSentAtActiveModeFunction. If we should, we allocate an ICDCheckInSender which tries to send a
353 : * Check-In message to the registered client.
354 : */
355 : void SendCheckInMsgs(Optional<Access::SubjectDescriptor> specificSubject = Optional<Access::SubjectDescriptor>());
356 : bool ShouldSendCheckInMessageForSpecificSubject(const ICDMonitoringEntry & entry,
357 : const Access::SubjectDescriptor & specificSubject);
358 :
359 : /**
360 : * @brief See function implementation in .cpp for details on this function.
361 : */
362 : bool ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID);
363 :
364 : /**
365 : * @brief Function checks if at least one client registration would require a Check-In message
366 : *
367 : * @param[in] function function to use to determine if a Check-In message would be sent for a given registration
368 : *
369 : * @return true At least one registration would require an Check-In message if we were entering ActiveMode.
370 : * @return false None of the registration would require a Check-In message either because there are no registration or
371 : * because they all have associated subscriptions.
372 : */
373 : bool CheckInMessagesWouldBeSent(const std::function<ShouldCheckInMsgsBeSentFunction> & function);
374 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
375 :
376 : KeepActiveFlags mKeepActiveFlags{ 0 };
377 :
378 : // Initialize mOperationalState to ActiveMode so the init sequence at bootup triggers the IdleMode behaviour first.
379 : OperationalState mOperationalState = OperationalState::ActiveMode;
380 : bool mTransitionToIdleCalled = false;
381 : ObjectPool<ObserverPointer, CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE> mStateObserverPool;
382 : uint8_t mOpenExchangeContextCount = 0;
383 :
384 : #if CHIP_CONFIG_ENABLE_ICD_DSLS
385 : bool mSITModeRequested = false;
386 : #endif
387 :
388 : #if CHIP_CONFIG_ENABLE_ICD_CIP
389 : uint8_t mCheckInRequestCount = 0;
390 :
391 : #if !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
392 : bool mIsBootUpResumeSubscriptionExecuted = false;
393 : #endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
394 :
395 : PersistentStorageDelegate * mStorage = nullptr;
396 : FabricTable * mFabricTable = nullptr;
397 : Messaging::ExchangeManager * mExchangeManager = nullptr;
398 : Crypto::SymmetricKeystore * mSymmetricKeystore = nullptr;
399 : SubscriptionsInfoProvider * mSubInfoProvider = nullptr;
400 : ICDCheckInBackOffStrategy * mICDCheckInBackOffStrategy = nullptr;
401 : ObjectPool<ICDCheckInSender, (CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC * CHIP_CONFIG_MAX_FABRICS)> mICDSenderPool;
402 : #endif // CHIP_CONFIG_ENABLE_ICD_CIP
403 : };
404 :
405 : } // namespace app
406 : } // namespace chip
|