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 1 : 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 0 : 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 : struct ConnectionFailureInfo
161 : {
162 : const ScopedNodeId peerId;
163 : CHIP_ERROR error;
164 : SessionEstablishmentStage sessionStage;
165 :
166 : // When the response was BUSY, error will be CHIP_ERROR_BUSY and
167 : // requestedBusyDelay will be set, if handling of BUSY responses is
168 : // enabled.
169 : #if CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
170 : Optional<System::Clock::Milliseconds16> requestedBusyDelay;
171 : #endif // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
172 :
173 0 : ConnectionFailureInfo(const ScopedNodeId & peer, CHIP_ERROR err, SessionEstablishmentStage stage) :
174 0 : peerId(peer), error(err), sessionStage(stage)
175 0 : {}
176 : };
177 :
178 : using OnSetupFailure = void (*)(void * context, const ConnectionFailureInfo & failureInfo);
179 :
180 : ~OperationalSessionSetup() override;
181 :
182 0 : OperationalSessionSetup(const CASEClientInitParams & params, CASEClientPoolDelegate * clientPool, ScopedNodeId peerId,
183 : OperationalSessionReleaseDelegate * releaseDelegate)
184 0 : {
185 0 : mInitParams = params;
186 0 : if (params.Validate() != CHIP_NO_ERROR || clientPool == nullptr || releaseDelegate == nullptr)
187 : {
188 0 : mState = State::Uninitialized;
189 0 : return;
190 : }
191 :
192 0 : mClientPool = clientPool;
193 0 : mPeerId = peerId;
194 0 : mReleaseDelegate = releaseDelegate;
195 0 : mState = State::NeedsAddress;
196 0 : mAddressLookupHandle.SetListener(this);
197 : }
198 :
199 : /*
200 : * This function can be called to establish a secure session with the device.
201 : *
202 : * The device is expected to have been commissioned, A CASE session
203 : * setup will be triggered.
204 : *
205 : * If session setup succeeds, the callback function `onConnection` will be called.
206 : * If session setup fails, `onFailure` will be called.
207 : *
208 : * If the session already exists, `onConnection` will be called immediately,
209 : * before the Connect call returns.
210 : *
211 : * `onFailure` may be called before the Connect call returns, for error
212 : * cases that are detected synchronously (e.g. inability to start an address
213 : * lookup).
214 : *
215 : * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
216 : * over a transport that allows large payloads to be transferred, e.g., TCP.
217 : */
218 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
219 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
220 :
221 : /*
222 : * This function can be called to establish a secure session with the device.
223 : *
224 : * The device is expected to have been commissioned, A CASE session
225 : * setup will be triggered.
226 : *
227 : * If session setup succeeds, the callback function `onConnection` will be called.
228 : * If session setup fails, `onSetupFailure` will be called.
229 : *
230 : * If the session already exists, `onConnection` will be called immediately,
231 : * before the Connect call returns.
232 : *
233 : * `onSetupFailure` may be called before the Connect call returns, for error cases that are detected synchronously
234 : * (e.g. inability to start an address lookup).
235 : *
236 : * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
237 : * over a transport that allows large payloads to be transferred, e.g., TCP.
238 : */
239 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnSetupFailure> * onSetupFailure,
240 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
241 :
242 0 : bool IsForAddressUpdate() const { return mPerformingAddressUpdate; }
243 :
244 : //////////// SessionEstablishmentDelegate Implementation ///////////////
245 : void OnSessionEstablished(const SessionHandle & session) override;
246 : void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage) override;
247 : void OnResponderBusy(System::Clock::Milliseconds16 requestedDelay) override;
248 :
249 0 : ScopedNodeId GetPeerId() const { return mPeerId; }
250 :
251 : static Transport::PeerAddress ToPeerAddress(const Dnssd::ResolvedNodeData & nodeData)
252 : {
253 : Inet::InterfaceId interfaceId = Inet::InterfaceId::Null();
254 :
255 : // TODO - Revisit usage of InterfaceID only for addresses that are IPv6 LLA
256 : // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA.
257 : // For all other addresses, we should rely on the device's routing table to route messages sent.
258 : // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread,
259 : // mDNS advertisements are not usually received on the same interface the peer is reachable on.
260 : if (nodeData.resolutionData.ipAddress[0].IsIPv6LinkLocal())
261 : {
262 : interfaceId = nodeData.resolutionData.interfaceId;
263 : }
264 :
265 : return Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.port, interfaceId);
266 : }
267 :
268 : /**
269 : * @brief Get the fabricIndex
270 : */
271 0 : FabricIndex GetFabricIndex() const { return mPeerId.GetFabricIndex(); }
272 :
273 : void PerformAddressUpdate();
274 :
275 : // AddressResolve::NodeListener - notifications when dnssd finds a node IP address
276 : void OnNodeAddressResolved(const PeerId & peerId, const AddressResolve::ResolveResult & result) override;
277 : void OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason) override;
278 :
279 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
280 : // Update our remaining attempt count to be at least the given value.
281 : void UpdateAttemptCount(uint8_t attemptCount);
282 :
283 : // Add a retry handler for this session setup.
284 : void AddRetryHandler(Callback::Callback<OnDeviceConnectionRetry> * onRetry);
285 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
286 :
287 : private:
288 : enum class State : uint8_t
289 : {
290 : Uninitialized, // Error state: OperationalSessionSetup is useless
291 : NeedsAddress, // No address known, lookup not started yet.
292 : ResolvingAddress, // Address lookup in progress.
293 : HasAddress, // Have an address, CASE handshake not started yet.
294 : Connecting, // CASE handshake in progress.
295 : SecureConnected, // CASE session established.
296 : WaitingForRetry, // No address known, but a retry is pending. Added at
297 : // end to make logs easier to understand.
298 : };
299 :
300 : CASEClientInitParams mInitParams;
301 : CASEClientPoolDelegate * mClientPool = nullptr;
302 :
303 : // mCASEClient is only non-null if we are in State::Connecting or just
304 : // allocated it as part of an attempt to enter State::Connecting.
305 : CASEClient * mCASEClient = nullptr;
306 :
307 : ScopedNodeId mPeerId;
308 :
309 : Transport::PeerAddress mDeviceAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
310 :
311 : SessionHolder mSecureSession;
312 :
313 : typedef Callback::GroupedCallbackList<OnDeviceConnected, OnDeviceConnectionFailure, OnSetupFailure> SuccessFailureCallbackList;
314 : SuccessFailureCallbackList mCallbacks;
315 :
316 : OperationalSessionReleaseDelegate * mReleaseDelegate;
317 :
318 : /// This is used when a node address is required.
319 : chip::AddressResolve::NodeLookupHandle mAddressLookupHandle;
320 :
321 : State mState = State::Uninitialized;
322 :
323 : bool mPerformingAddressUpdate = false;
324 :
325 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
326 : System::Clock::Milliseconds16 mRequestedBusyDelay = System::Clock::kZero;
327 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
328 :
329 : TransportPayloadCapability mTransportPayloadCapability = TransportPayloadCapability::kMRPPayload;
330 :
331 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
332 : // When we TryNextResult on the resolver, it will synchronously call back
333 : // into our OnNodeAddressResolved when it succeeds. We need to track
334 : // whether the OnNodeAddressResolved is coming from handling a session
335 : // establishment error or whether it's happening because we didn't even
336 : // manage to start a session establishment at all. Use this member to keep
337 : // track of that.
338 : bool mTryingNextResultDueToSessionEstablishmentError = false;
339 :
340 : uint8_t mRemainingAttempts = 0;
341 : uint8_t mAttemptsDone = 0;
342 :
343 : uint8_t mResolveAttemptsAllowed = 0;
344 :
345 : Callback::CallbackDeque mConnectionRetry;
346 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
347 :
348 : void MoveToState(State aTargetState);
349 :
350 : CHIP_ERROR EstablishConnection(const AddressResolve::ResolveResult & result);
351 :
352 : /*
353 : * This checks to see if an existing CASE session exists to the peer within the SessionManager
354 : * and if one exists, to load that into mSecureSession.
355 : *
356 : * Returns true if a valid session was found, false otherwise.
357 : *
358 : */
359 : bool AttachToExistingSecureSession();
360 :
361 : void CleanupCASEClient();
362 :
363 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
364 : Callback::Callback<OnSetupFailure> * onSetupFailure,
365 : TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
366 :
367 : void EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
368 : Callback::Callback<OnDeviceConnectionFailure> * onFailure,
369 : Callback::Callback<OnSetupFailure> * onSetupFailure);
370 :
371 : enum class ReleaseBehavior
372 : {
373 : Release,
374 : DoNotRelease
375 : };
376 :
377 : /*
378 : * This dequeues all failure and success callbacks and appropriately invokes either set depending
379 : * on the value of error.
380 : *
381 : * If error == CHIP_NO_ERROR, only success callbacks are invoked. Otherwise, only failure callbacks are invoked.
382 : *
383 : * The state offers additional context regarding the failure, indicating the specific state in which
384 : * the error occurs. It is only relayed through failure callbacks when the error is not equal to CHIP_NO_ERROR.
385 : *
386 : * If releaseBehavior is Release, this uses mReleaseDelegate to release
387 : * ourselves (aka `this`). As a result any caller should return right away
388 : * without touching `this`.
389 : *
390 : * Setting releaseBehavior to DoNotRelease is meant for use from the destructor
391 : */
392 : void DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
393 : ReleaseBehavior releaseBehavior = ReleaseBehavior::Release);
394 :
395 0 : void DequeueConnectionCallbacks(CHIP_ERROR error, ReleaseBehavior releaseBehavior = ReleaseBehavior::Release)
396 : {
397 0 : this->DequeueConnectionCallbacks(error, SessionEstablishmentStage::kNotInKeyExchange, releaseBehavior);
398 0 : }
399 :
400 : /**
401 : * Helper for DequeueConnectionCallbacks that handles the actual callback
402 : * notifications. This happens after the object has been released, if it's
403 : * being released.
404 : */
405 : static void NotifyConnectionCallbacks(SuccessFailureCallbackList & ready, CHIP_ERROR error, SessionEstablishmentStage stage,
406 : const ScopedNodeId & peerId, Messaging::ExchangeManager * exchangeMgr,
407 : const Optional<SessionHandle> & optionalSessionHandle,
408 : // requestedBusyDelay will be 0 if not
409 : // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP,
410 : // and only has a meaningful value
411 : // when the error is CHIP_ERROR_BUSY.
412 : System::Clock::Milliseconds16 requestedBusyDelay);
413 :
414 : /**
415 : * Triggers a DNSSD lookup to find a usable peer address.
416 : */
417 : CHIP_ERROR LookupPeerAddress();
418 :
419 : /**
420 : * This function will set new IP address, port and MRP retransmission intervals of the device.
421 : */
422 : void UpdateDeviceData(const AddressResolve::ResolveResult & result);
423 :
424 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
425 : /**
426 : * Schedule a setup reattempt, if possible. The outparam indicates how long
427 : * it will be before the reattempt happens.
428 : */
429 : CHIP_ERROR ScheduleSessionSetupReattempt(System::Clock::Seconds16 & timerDelay);
430 :
431 : /**
432 : * Cancel a scheduled setup reattempt, if we can (i.e. if we still have
433 : * access to the SystemLayer).
434 : */
435 : void CancelSessionSetupReattempt();
436 :
437 : /**
438 : * Helper for our backoff retry timer.
439 : */
440 : static void TrySetupAgain(System::Layer * systemLayer, void * state);
441 :
442 : /**
443 : * Helper to notify our retry callbacks that a setup error occurred and we
444 : * will retry.
445 : */
446 : void NotifyRetryHandlers(CHIP_ERROR error, const ReliableMessageProtocolConfig & remoteMrpConfig,
447 : System::Clock::Seconds16 retryDelay);
448 :
449 : /**
450 : * A version of NotifyRetryHandlers that passes in a retry timeout estimate
451 : * directly.
452 : */
453 : void NotifyRetryHandlers(CHIP_ERROR error, System::Clock::Seconds16 timeoutEstimate);
454 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
455 : };
456 :
457 : } // namespace chip
|