Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020-2021 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : /**
20 : * @file
21 : * This file contains definitions for Device class. The objects of this
22 : * class will be used by Controller applications to interact with CHIP
23 : * devices. The class provides mechanism to construct, send and receive
24 : * messages to and from the corresponding CHIP devices.
25 : */
26 :
27 : #pragma once
28 :
29 : #include <app/AppConfig.h>
30 : #include <app/CASEClient.h>
31 : #include <app/CASEClientPool.h>
32 : #include <app/DeviceProxy.h>
33 : #include <app/util/basic-types.h>
34 : #include <credentials/GroupDataProvider.h>
35 : #include <lib/address_resolve/AddressResolve.h>
36 : #include <lib/core/GroupedCallbackList.h>
37 : #include <messaging/ExchangeContext.h>
38 : #include <messaging/ExchangeDelegate.h>
39 : #include <messaging/ExchangeMgr.h>
40 : #include <messaging/Flags.h>
41 : #include <messaging/ReliableMessageProtocolConfig.h>
42 : #include <platform/CHIPDeviceConfig.h>
43 : #include <protocols/secure_channel/CASESession.h>
44 : #include <system/SystemClock.h>
45 : #include <system/SystemLayer.h>
46 : #include <transport/SessionManager.h>
47 : #include <transport/TransportMgr.h>
48 : #include <transport/raw/MessageHeader.h>
49 : #include <transport/raw/UDP.h>
50 :
51 : namespace chip {
52 :
53 : class OperationalSessionSetup;
54 :
55 : /**
56 : * @brief Delegate provided when creating OperationalSessionSetup.
57 : *
58 : * Once OperationalSessionSetup establishes a connection (or errors out) and has notified all
59 : * registered application callbacks via OnDeviceConnected/OnDeviceConnectionFailure, this delegate
60 : * is used to deallocate the OperationalSessionSetup.
61 : */
62 : class OperationalSessionReleaseDelegate
63 : {
64 : public:
65 159 : virtual ~OperationalSessionReleaseDelegate() = default;
66 : virtual void ReleaseSession(OperationalSessionSetup * sessionSetup) = 0;
67 : };
68 :
69 : /**
70 : * @brief Minimal implementation of DeviceProxy that encapsulates a SessionHolder to track a CASE session.
71 : *
72 : * Deprecated - Avoid using this object.
73 : *
74 : * OperationalDeviceProxy is a minimal implementation of DeviceProxy. It is meant to provide a transition
75 : * for existing consumers of OperationalDeviceProxy that were delivered a reference to that object in
76 : * their respective OnDeviceConnected callback, but were incorrectly holding onto that object past
77 : * the function call. OperationalDeviceProxy can be held on for as long as is desired, while still
78 : * minimizing the code changes needed to transition to a more final solution by virtue of
79 : * implementing DeviceProxy.
80 : */
81 : class OperationalDeviceProxy : public DeviceProxy
82 : {
83 : public:
84 0 : OperationalDeviceProxy(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle) :
85 0 : mExchangeMgr(exchangeMgr), mSecureSession(sessionHandle), mPeerScopedNodeId(sessionHandle->GetPeer())
86 0 : {}
87 43 : OperationalDeviceProxy() {}
88 :
89 0 : void Disconnect() override
90 : {
91 0 : if (IsSecureConnected())
92 : {
93 0 : GetSecureSession().Value()->AsSecureSession()->MarkAsDefunct();
94 : }
95 0 : mSecureSession.Release();
96 0 : mExchangeMgr = nullptr;
97 0 : mPeerScopedNodeId = ScopedNodeId();
98 0 : }
99 0 : Messaging::ExchangeManager * GetExchangeManager() const override { return mExchangeMgr; }
100 0 : chip::Optional<SessionHandle> GetSecureSession() const override { return mSecureSession.Get(); }
101 0 : NodeId GetDeviceId() const override { return mPeerScopedNodeId.GetNodeId(); }
102 : ScopedNodeId GetPeerScopedNodeId() const { return mPeerScopedNodeId; }
103 :
104 : bool ConnectionReady() const { return (mExchangeMgr != nullptr && IsSecureConnected()); }
105 :
106 : private:
107 0 : bool IsSecureConnected() const override { return static_cast<bool>(mSecureSession); }
108 :
109 : Messaging::ExchangeManager * mExchangeMgr = nullptr;
110 : SessionHolder mSecureSession;
111 : ScopedNodeId mPeerScopedNodeId;
112 : };
113 :
114 : /**
115 : * @brief Callback prototype when secure session is established.
116 : *
117 : * Callback implementations are not supposed to store the exchangeMgr or the sessionHandle. Older
118 : * application code does incorrectly hold onto this information so do not follow those incorrect
119 : * implementations as an example.
120 : */
121 : typedef void (*OnDeviceConnected)(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle);
122 :
123 : /**
124 : * Callback prototype when secure session establishment fails.
125 : */
126 : typedef void (*OnDeviceConnectionFailure)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error);
127 :
128 : /**
129 : * Callback prototype when secure session establishement has failed and will be
130 : * retried. retryTimeout indicates how much time will pass before we know
131 : * whether the retry has timed out waiting for a response to our Sigma1 message.
132 : */
133 : typedef void (*OnDeviceConnectionRetry)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error,
134 : System::Clock::Seconds16 retryTimeout);
135 :
136 : /**
137 : * Object used to either establish a connection to peer or performing address lookup to a peer.
138 : *
139 : * OperationalSessionSetup is capable of either:
140 : * 1. Establishing a CASE session connection to a peer. Upon success or failure, the OnDeviceConnected or
141 : * OnDeviceConnectionFailure callback will be called to notify the caller the results of trying to
142 : * estblish a CASE session. Some additional details about the steps to establish a connection are:
143 : * - Discover the device using DNSSD (find out what IP address to use and what
144 : * communication parameters are appropriate for it)
145 : * - Establish a secure channel to it via CASE
146 : * - Expose to consumers the secure session for talking to the device via OnDeviceConnected
147 : * callback.
148 : * 2. Performing an address lookup for given a scoped nodeid. On success, it will call into
149 : * SessionManager to update the addresses for all matching sessions in the session table.
150 : *
151 : * OperationalSessionSetup has a very limited lifetime. Once it has completed its purpose outlined above,
152 : * it will use `releaseDelegate` to release itself.
153 : *
154 : * It is possible to determine which of the two purposes the OperationalSessionSetup is for by calling
155 : * IsForAddressUpdate().
156 : */
157 : class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate, public AddressResolve::NodeListener
158 : {
159 : public:
160 : // Grants tests controlled access to private state (e.g. forcing mState)
161 : // so they can exercise state-dependent behavior such as
162 : // IsEstablishingSession() without driving a full CASE handshake.
163 : friend class TestOperationalSessionSetupAccess;
164 :
165 : struct ConnectionFailureInfo
166 : {
167 : const ScopedNodeId peerId;
168 : CHIP_ERROR error;
169 : SessionEstablishmentStage sessionStage;
170 :
171 : // When the response was BUSY, error will be CHIP_ERROR_BUSY and
172 : // requestedBusyDelay will be set, if handling of BUSY responses is
173 : // enabled.
174 : #if CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
175 : Optional<System::Clock::Milliseconds16> requestedBusyDelay;
176 : #endif // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
177 :
178 0 : ConnectionFailureInfo(const ScopedNodeId & peer, CHIP_ERROR err, SessionEstablishmentStage stage) :
179 0 : peerId(peer), error(err), sessionStage(stage)
180 0 : {}
181 : };
182 :
183 : using OnSetupFailure = void (*)(void * context, const ConnectionFailureInfo & failureInfo);
184 :
185 : ~OperationalSessionSetup() override;
186 :
187 20 : OperationalSessionSetup(const CASEClientInitParams & params, CASEClientPoolDelegate * clientPool, ScopedNodeId peerId,
188 : OperationalSessionReleaseDelegate * releaseDelegate)
189 20 : {
190 20 : mInitParams = params;
191 40 : if (params.Validate() != CHIP_NO_ERROR || clientPool == nullptr || releaseDelegate == nullptr)
192 : {
193 20 : mState = State::Uninitialized;
194 20 : return;
195 : }
196 :
197 0 : mClientPool = clientPool;
198 0 : mPeerId = peerId;
199 0 : mReleaseDelegate = releaseDelegate;
200 0 : mState = State::NeedsAddress;
201 0 : mAddressLookupHandle.SetListener(this);
202 : }
203 :
204 : /*
205 : * This function can be called to establish a secure session with the device.
206 : *
207 : * The device is expected to have been commissioned, A CASE session
208 : * setup will be triggered.
209 : *
210 : * If session setup succeeds, the callback function `onConnection` will be called.
211 : * If session setup fails, `onFailure` will be called.
212 : *
213 : * If the session already exists, `onConnection` will be called immediately,
214 : * before the Connect call returns.
215 : *
216 : * `onFailure` may be called before the Connect call returns, for error
217 : * cases that are detected synchronously (e.g. inability to start an address
218 : * lookup).
219 : *
220 : * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
221 : * over a transport that allows large payloads to be transferred, e.g., TCP.
222 : */
223 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
224 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
225 :
226 : /*
227 : * This function can be called to establish a secure session with the device.
228 : *
229 : * The device is expected to have been commissioned, A CASE session
230 : * setup will be triggered.
231 : *
232 : * If session setup succeeds, the callback function `onConnection` will be called.
233 : * If session setup fails, `onSetupFailure` will be called.
234 : *
235 : * If the session already exists, `onConnection` will be called immediately,
236 : * before the Connect call returns.
237 : *
238 : * `onSetupFailure` may be called before the Connect call returns, for error cases that are detected synchronously
239 : * (e.g. inability to start an address lookup).
240 : *
241 : * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
242 : * over a transport that allows large payloads to be transferred, e.g., TCP.
243 : */
244 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnSetupFailure> * onSetupFailure,
245 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
246 :
247 40 : bool IsForAddressUpdate() const { return mPerformingAddressUpdate; }
248 :
249 : /**
250 : * Returns true iff this setup object is actively trying to establish a
251 : * session (as opposed to idle, uninitialized, or fully connected).
252 : */
253 40 : bool IsEstablishingSession() const
254 : {
255 38 : return mState == State::NeedsAddress || mState == State::ResolvingAddress || mState == State::HasAddress ||
256 78 : mState == State::Connecting || mState == State::WaitingForRetry;
257 : }
258 :
259 : //////////// SessionEstablishmentDelegate Implementation ///////////////
260 : void OnSessionEstablished(const SessionHandle & session) override;
261 : void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage) override;
262 : void OnResponderBusy(System::Clock::Milliseconds16 requestedDelay) override;
263 :
264 47 : ScopedNodeId GetPeerId() const { return mPeerId; }
265 :
266 : static Transport::PeerAddress ToPeerAddress(const Dnssd::ResolvedNodeData & nodeData)
267 : {
268 : Inet::InterfaceId interfaceId = Inet::InterfaceId::Null();
269 :
270 : // TODO - Revisit usage of InterfaceID only for addresses that are IPv6 LLA
271 : // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA.
272 : // For all other addresses, we should rely on the device's routing table to route messages sent.
273 : // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread,
274 : // mDNS advertisements are not usually received on the same interface the peer is reachable on.
275 : if (nodeData.resolutionData.ipAddress[0].IsIPv6LinkLocal())
276 : {
277 : interfaceId = nodeData.resolutionData.interfaceId;
278 : }
279 :
280 : return Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.port, interfaceId);
281 : }
282 :
283 : /**
284 : * @brief Get the fabricIndex
285 : */
286 15 : FabricIndex GetFabricIndex() const { return mPeerId.GetFabricIndex(); }
287 :
288 : void PerformAddressUpdate();
289 :
290 : // AddressResolve::NodeListener - notifications when dnssd finds a node IP address
291 : void OnNodeAddressResolved(const PeerId & peerId, const AddressResolve::ResolveResult & result) override;
292 : void OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason) override;
293 :
294 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
295 : // Update our remaining attempt count to be at least the given value.
296 : void UpdateAttemptCount(uint8_t attemptCount);
297 :
298 : // Add a retry handler for this session setup.
299 : void AddRetryHandler(Callback::Callback<OnDeviceConnectionRetry> * onRetry);
300 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
301 :
302 : #if CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
303 : /**
304 : * Set a fallback resolve result to use if address resolution times out.
305 : * This should be called before Connect() to enable the fallback mechanism.
306 : */
307 : void SetFallbackResolveResult(const AddressResolve::ResolveResult & result);
308 : #endif // CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
309 :
310 : private:
311 : enum class State : uint8_t
312 : {
313 : Uninitialized, // Error state: OperationalSessionSetup is useless
314 : NeedsAddress, // No address known, lookup not started yet.
315 : ResolvingAddress, // Address lookup in progress.
316 : HasAddress, // Have an address, CASE handshake not started yet.
317 : Connecting, // CASE handshake in progress.
318 : SecureConnected, // CASE session established.
319 : WaitingForRetry, // No address known, but a retry is pending. Added at
320 : // end to make logs easier to understand.
321 : };
322 :
323 : CASEClientInitParams mInitParams;
324 : CASEClientPoolDelegate * mClientPool = nullptr;
325 :
326 : // mCASEClient is only non-null if we are in State::Connecting or just
327 : // allocated it as part of an attempt to enter State::Connecting.
328 : CASEClient * mCASEClient = nullptr;
329 :
330 : ScopedNodeId mPeerId;
331 :
332 : Transport::PeerAddress mDeviceAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
333 :
334 : SessionHolder mSecureSession;
335 :
336 : typedef Callback::GroupedCallbackList<OnDeviceConnected, OnDeviceConnectionFailure, OnSetupFailure> SuccessFailureCallbackList;
337 : SuccessFailureCallbackList mCallbacks;
338 :
339 : OperationalSessionReleaseDelegate * mReleaseDelegate;
340 :
341 : /// This is used when a node address is required.
342 : chip::AddressResolve::NodeLookupHandle mAddressLookupHandle;
343 :
344 : State mState = State::Uninitialized;
345 :
346 : bool mPerformingAddressUpdate = false;
347 :
348 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
349 : System::Clock::Milliseconds16 mRequestedBusyDelay = System::Clock::kZero;
350 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
351 :
352 : TransportPayloadCapability mTransportPayloadCapability = TransportPayloadCapability::kMRPPayload;
353 :
354 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
355 : // When we TryNextResult on the resolver, it will synchronously call back
356 : // into our OnNodeAddressResolved when it succeeds. We need to track
357 : // whether the OnNodeAddressResolved is coming from handling a session
358 : // establishment error or whether it's happening because we didn't even
359 : // manage to start a session establishment at all. Use this member to keep
360 : // track of that.
361 : bool mTryingNextResultDueToSessionEstablishmentError = false;
362 :
363 : uint8_t mRemainingAttempts = 0;
364 : uint8_t mAttemptsDone = 0;
365 :
366 : uint8_t mResolveAttemptsAllowed = 0;
367 :
368 : Callback::CallbackDeque mConnectionRetry;
369 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
370 :
371 : #if CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
372 : // Fallback address resolve result to use if normal address resolution times out
373 : Optional<AddressResolve::ResolveResult> mFallbackResolveResult;
374 : System::Clock::Timeout mFallbackTimeout = System::Clock::Seconds16(CHIP_CONFIG_ADDRESS_RESOLVE_FALLBACK_TIMEOUT_SECONDS);
375 : #endif // CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
376 :
377 : void MoveToState(State aTargetState);
378 :
379 : CHIP_ERROR EstablishConnection(const AddressResolve::ResolveResult & result);
380 :
381 : /*
382 : * This checks to see if an existing CASE session exists to the peer within the SessionManager
383 : * and if one exists, to load that into mSecureSession.
384 : *
385 : * Returns true if a valid session was found, false otherwise.
386 : *
387 : */
388 : bool AttachToExistingSecureSession();
389 :
390 : void CleanupCASEClient();
391 :
392 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
393 : Callback::Callback<OnSetupFailure> * onSetupFailure,
394 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
395 :
396 : void EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
397 : Callback::Callback<OnDeviceConnectionFailure> * onFailure,
398 : Callback::Callback<OnSetupFailure> * onSetupFailure);
399 :
400 : enum class ReleaseBehavior
401 : {
402 : Release,
403 : DoNotRelease
404 : };
405 :
406 : /*
407 : * This dequeues all failure and success callbacks and appropriately invokes either set depending
408 : * on the value of error.
409 : *
410 : * If error == CHIP_NO_ERROR, only success callbacks are invoked. Otherwise, only failure callbacks are invoked.
411 : *
412 : * The state offers additional context regarding the failure, indicating the specific state in which
413 : * the error occurs. It is only relayed through failure callbacks when the error is not equal to CHIP_NO_ERROR.
414 : *
415 : * If releaseBehavior is Release, this uses mReleaseDelegate to release
416 : * ourselves (aka `this`). As a result any caller should return right away
417 : * without touching `this`.
418 : *
419 : * Setting releaseBehavior to DoNotRelease is meant for use from the destructor
420 : */
421 : void DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
422 : ReleaseBehavior releaseBehavior = ReleaseBehavior::Release);
423 :
424 20 : void DequeueConnectionCallbacks(CHIP_ERROR error, ReleaseBehavior releaseBehavior = ReleaseBehavior::Release)
425 : {
426 : // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape): TODO #41631
427 20 : this->DequeueConnectionCallbacks(error, SessionEstablishmentStage::kNotInKeyExchange, releaseBehavior);
428 20 : }
429 :
430 : /**
431 : * Helper for DequeueConnectionCallbacks that handles the actual callback
432 : * notifications. This happens after the object has been released, if it's
433 : * being released.
434 : */
435 : static void NotifyConnectionCallbacks(SuccessFailureCallbackList & ready, CHIP_ERROR error, SessionEstablishmentStage stage,
436 : const ScopedNodeId & peerId, Messaging::ExchangeManager * exchangeMgr,
437 : const Optional<SessionHandle> & optionalSessionHandle,
438 : // requestedBusyDelay will be 0 if not
439 : // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP,
440 : // and only has a meaningful value
441 : // when the error is CHIP_ERROR_BUSY.
442 : System::Clock::Milliseconds16 requestedBusyDelay);
443 :
444 : /**
445 : * Triggers a DNSSD lookup to find a usable peer address.
446 : */
447 : CHIP_ERROR LookupPeerAddress();
448 :
449 : /**
450 : * This function will set new IP address, port and MRP retransmission intervals of the device.
451 : */
452 : void UpdateDeviceData(const AddressResolve::ResolveResult & result);
453 :
454 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
455 : /**
456 : * Schedule a setup reattempt, if possible. The outparam indicates how long
457 : * it will be before the reattempt happens.
458 : */
459 : CHIP_ERROR ScheduleSessionSetupReattempt(System::Clock::Seconds16 & timerDelay);
460 :
461 : /**
462 : * Cancel a scheduled setup reattempt, if we can (i.e. if we still have
463 : * access to the SystemLayer).
464 : */
465 : void CancelSessionSetupReattempt();
466 :
467 : /**
468 : * Helper for our backoff retry timer.
469 : */
470 : static void TrySetupAgain(System::Layer * systemLayer, void * state);
471 :
472 : /**
473 : * Helper to notify our retry callbacks that a setup error occurred and we
474 : * will retry.
475 : */
476 : void NotifyRetryHandlers(CHIP_ERROR error, const ReliableMessageProtocolConfig & remoteMrpConfig,
477 : System::Clock::Seconds16 retryDelay);
478 :
479 : /**
480 : * A version of NotifyRetryHandlers that passes in a retry timeout estimate
481 : * directly.
482 : */
483 : void NotifyRetryHandlers(CHIP_ERROR error, System::Clock::Seconds16 timeoutEstimate);
484 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
485 :
486 : #if CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
487 : /**
488 : * Timer callback invoked when DNS-SD resolution doesn't complete within the configured timeout.
489 : *
490 : * When this fires:
491 : * - Cancels the ongoing DNS-SD lookup to prevent race conditions
492 : * - Uses the cached IP/port from the successful PASE session seconds ago
493 : * - Assumes the cached address is still valid (IP hasn't changed)
494 : */
495 : static void OnFallbackTimeout(System::Layer * systemLayer, void * appState);
496 :
497 : /**
498 : * Cancel the fallback timeout timer if it's running.
499 : */
500 : void CancelFallbackTimer();
501 :
502 : /**
503 : * Start the fallback timeout timer.
504 : */
505 : CHIP_ERROR StartFallbackTimer();
506 :
507 : /**
508 : * Helper to get the System::Layer from the session manager.
509 : * Returns nullptr if not available.
510 : */
511 : System::Layer * GetSystemLayer();
512 : #endif // CHIP_CONFIG_ENABLE_ADDRESS_RESOLVE_FALLBACK
513 : };
514 :
515 : } // namespace chip
|