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/CASEClient.h>
30 : #include <app/CASEClientPool.h>
31 : #include <app/DeviceProxy.h>
32 : #include <app/util/basic-types.h>
33 : #include <credentials/GroupDataProvider.h>
34 : #include <lib/address_resolve/AddressResolve.h>
35 : #include <messaging/ExchangeContext.h>
36 : #include <messaging/ExchangeDelegate.h>
37 : #include <messaging/ExchangeMgr.h>
38 : #include <messaging/Flags.h>
39 : #include <messaging/ReliableMessageProtocolConfig.h>
40 : #include <platform/CHIPDeviceConfig.h>
41 : #include <protocols/secure_channel/CASESession.h>
42 : #include <system/SystemClock.h>
43 : #include <system/SystemLayer.h>
44 : #include <transport/SessionManager.h>
45 : #include <transport/TransportMgr.h>
46 : #include <transport/raw/MessageHeader.h>
47 : #include <transport/raw/UDP.h>
48 :
49 : namespace chip {
50 :
51 : class OperationalSessionSetup;
52 :
53 : /**
54 : * @brief Delegate provided when creating OperationalSessionSetup.
55 : *
56 : * Once OperationalSessionSetup establishes a connection (or errors out) and has notified all
57 : * registered application callbacks via OnDeviceConnected/OnDeviceConnectionFailure, this delegate
58 : * is used to deallocate the OperationalSessionSetup.
59 : */
60 : class OperationalSessionReleaseDelegate
61 : {
62 : public:
63 1 : virtual ~OperationalSessionReleaseDelegate() = default;
64 : virtual void ReleaseSession(OperationalSessionSetup * sessionSetup) = 0;
65 : };
66 :
67 : /**
68 : * @brief Minimal implementation of DeviceProxy that encapsulates a SessionHolder to track a CASE session.
69 : *
70 : * Deprecated - Avoid using this object.
71 : *
72 : * OperationalDeviceProxy is a minimal implementation of DeviceProxy. It is meant to provide a transition
73 : * for existing consumers of OperationalDeviceProxy that were delivered a reference to that object in
74 : * their respective OnDeviceConnected callback, but were incorrectly holding onto that object past
75 : * the function call. OperationalDeviceProxy can be held on for as long as is desired, while still
76 : * minimizing the code changes needed to transition to a more final solution by virtue of
77 : * implementing DeviceProxy.
78 : */
79 : class OperationalDeviceProxy : public DeviceProxy
80 : {
81 : public:
82 0 : OperationalDeviceProxy(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle) :
83 0 : mExchangeMgr(exchangeMgr), mSecureSession(sessionHandle), mPeerScopedNodeId(sessionHandle->GetPeer())
84 0 : {}
85 0 : OperationalDeviceProxy() {}
86 :
87 0 : void Disconnect() override
88 : {
89 0 : if (IsSecureConnected())
90 : {
91 0 : GetSecureSession().Value()->AsSecureSession()->MarkAsDefunct();
92 : }
93 0 : mSecureSession.Release();
94 0 : mExchangeMgr = nullptr;
95 0 : mPeerScopedNodeId = ScopedNodeId();
96 0 : }
97 0 : Messaging::ExchangeManager * GetExchangeManager() const override { return mExchangeMgr; }
98 0 : chip::Optional<SessionHandle> GetSecureSession() const override { return mSecureSession.Get(); }
99 0 : NodeId GetDeviceId() const override { return mPeerScopedNodeId.GetNodeId(); }
100 : ScopedNodeId GetPeerScopedNodeId() const { return mPeerScopedNodeId; }
101 :
102 : bool ConnectionReady() const { return (mExchangeMgr != nullptr && IsSecureConnected()); }
103 :
104 : private:
105 0 : bool IsSecureConnected() const override { return static_cast<bool>(mSecureSession); }
106 :
107 : Messaging::ExchangeManager * mExchangeMgr = nullptr;
108 : SessionHolder mSecureSession;
109 : ScopedNodeId mPeerScopedNodeId;
110 : };
111 :
112 : /**
113 : * @brief Callback prototype when secure session is established.
114 : *
115 : * Callback implementations are not supposed to store the exchangeMgr or the sessionHandle. Older
116 : * application code does incorrectly hold onto this information so do not follow those incorrect
117 : * implementations as an example.
118 : */
119 : typedef void (*OnDeviceConnected)(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle);
120 :
121 : /**
122 : * Callback prototype when secure session establishment fails.
123 : */
124 : typedef void (*OnDeviceConnectionFailure)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error);
125 :
126 : /**
127 : * Callback prototype when secure session establishement has failed and will be
128 : * retried. retryTimeout indicates how much time will pass before we know
129 : * whether the retry has timed out waiting for a response to our Sigma1 message.
130 : */
131 : typedef void (*OnDeviceConnectionRetry)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error,
132 : System::Clock::Seconds16 retryTimeout);
133 :
134 : /**
135 : * Object used to either establish a connection to peer or performing address lookup to a peer.
136 : *
137 : * OperationalSessionSetup is capable of either:
138 : * 1. Establishing a CASE session connection to a peer. Upon success or failure, the OnDeviceConnected or
139 : * OnDeviceConnectionFailure callback will be called to notify the caller the results of trying to
140 : * estblish a CASE session. Some additional details about the steps to establish a connection are:
141 : * - Discover the device using DNSSD (find out what IP address to use and what
142 : * communication parameters are appropriate for it)
143 : * - Establish a secure channel to it via CASE
144 : * - Expose to consumers the secure session for talking to the device via OnDeviceConnected
145 : * callback.
146 : * 2. Performing an address lookup for given a scoped nodeid. On success, it will call into
147 : * SessionManager to update the addresses for all matching sessions in the session table.
148 : *
149 : * OperationalSessionSetup has a very limited lifetime. Once it has completed its purpose outlined above,
150 : * it will use `releaseDelegate` to release itself.
151 : *
152 : * It is possible to determine which of the two purposes the OperationalSessionSetup is for by calling
153 : * IsForAddressUpdate().
154 : */
155 : class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate, public AddressResolve::NodeListener
156 : {
157 : public:
158 : struct ConnnectionFailureInfo
159 : {
160 : const ScopedNodeId peerId;
161 : CHIP_ERROR error;
162 : SessionEstablishmentStage sessionStage;
163 :
164 0 : ConnnectionFailureInfo(const ScopedNodeId & peer, CHIP_ERROR err, SessionEstablishmentStage stage) :
165 0 : peerId(peer), error(err), sessionStage(stage)
166 0 : {}
167 : };
168 :
169 : using OnSetupFailure = void (*)(void * context, const ConnnectionFailureInfo & failureInfo);
170 :
171 : ~OperationalSessionSetup() override;
172 :
173 0 : OperationalSessionSetup(const CASEClientInitParams & params, CASEClientPoolDelegate * clientPool, ScopedNodeId peerId,
174 : OperationalSessionReleaseDelegate * releaseDelegate)
175 0 : {
176 0 : mInitParams = params;
177 0 : if (params.Validate() != CHIP_NO_ERROR || clientPool == nullptr || releaseDelegate == nullptr)
178 : {
179 0 : mState = State::Uninitialized;
180 0 : return;
181 : }
182 :
183 0 : mClientPool = clientPool;
184 0 : mPeerId = peerId;
185 0 : mReleaseDelegate = releaseDelegate;
186 0 : mState = State::NeedsAddress;
187 0 : mAddressLookupHandle.SetListener(this);
188 : }
189 :
190 : /*
191 : * This function can be called to establish a secure session with the device.
192 : *
193 : * The device is expected to have been commissioned, A CASE session
194 : * setup will be triggered.
195 : *
196 : * If session setup succeeds, the callback function `onConnection` will be called.
197 : * If session setup fails, `onFailure` will be called.
198 : *
199 : * If the session already exists, `onConnection` will be called immediately,
200 : * before the Connect call returns.
201 : *
202 : * `onFailure` may be called before the Connect call returns, for error
203 : * cases that are detected synchronously (e.g. inability to start an address
204 : * lookup).
205 : */
206 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure);
207 :
208 : /*
209 : * This function can be called to establish a secure session with the device.
210 : *
211 : * The device is expected to have been commissioned, A CASE session
212 : * setup will be triggered.
213 : *
214 : * If session setup succeeds, the callback function `onConnection` will be called.
215 : * If session setup fails, `onSetupFailure` will be called.
216 : *
217 : * If the session already exists, `onConnection` will be called immediately,
218 : * before the Connect call returns.
219 : *
220 : * `onSetupFailure` may be called before the Connect call returns, for error cases that are detected synchronously
221 : * (e.g. inability to start an address lookup).
222 : */
223 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnSetupFailure> * onSetupFailure);
224 :
225 0 : bool IsForAddressUpdate() const { return mPerformingAddressUpdate; }
226 :
227 : //////////// SessionEstablishmentDelegate Implementation ///////////////
228 : void OnSessionEstablished(const SessionHandle & session) override;
229 : void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage) override;
230 :
231 0 : ScopedNodeId GetPeerId() const { return mPeerId; }
232 :
233 : static Transport::PeerAddress ToPeerAddress(const Dnssd::ResolvedNodeData & nodeData)
234 : {
235 : Inet::InterfaceId interfaceId = Inet::InterfaceId::Null();
236 :
237 : // TODO - Revisit usage of InterfaceID only for addresses that are IPv6 LLA
238 : // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA.
239 : // For all other addresses, we should rely on the device's routing table to route messages sent.
240 : // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread,
241 : // mDNS advertisements are not usually received on the same interface the peer is reachable on.
242 : if (nodeData.resolutionData.ipAddress[0].IsIPv6LinkLocal())
243 : {
244 : interfaceId = nodeData.resolutionData.interfaceId;
245 : }
246 :
247 : return Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.port, interfaceId);
248 : }
249 :
250 : /**
251 : * @brief Get the fabricIndex
252 : */
253 0 : FabricIndex GetFabricIndex() const { return mPeerId.GetFabricIndex(); }
254 :
255 : void PerformAddressUpdate();
256 :
257 : // AddressResolve::NodeListener - notifications when dnssd finds a node IP address
258 : void OnNodeAddressResolved(const PeerId & peerId, const AddressResolve::ResolveResult & result) override;
259 : void OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason) override;
260 :
261 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
262 : // Update our remaining attempt count to be at least the given value.
263 : void UpdateAttemptCount(uint8_t attemptCount);
264 :
265 : // Add a retry handler for this session setup.
266 : void AddRetryHandler(Callback::Callback<OnDeviceConnectionRetry> * onRetry);
267 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
268 :
269 : private:
270 : enum class State : uint8_t
271 : {
272 : Uninitialized, // Error state: OperationalSessionSetup is useless
273 : NeedsAddress, // No address known, lookup not started yet.
274 : ResolvingAddress, // Address lookup in progress.
275 : HasAddress, // Have an address, CASE handshake not started yet.
276 : Connecting, // CASE handshake in progress.
277 : SecureConnected, // CASE session established.
278 : WaitingForRetry, // No address known, but a retry is pending. Added at
279 : // end to make logs easier to understand.
280 : };
281 :
282 : CASEClientInitParams mInitParams;
283 : CASEClientPoolDelegate * mClientPool = nullptr;
284 :
285 : // mCASEClient is only non-null if we are in State::Connecting or just
286 : // allocated it as part of an attempt to enter State::Connecting.
287 : CASEClient * mCASEClient = nullptr;
288 :
289 : ScopedNodeId mPeerId;
290 :
291 : Transport::PeerAddress mDeviceAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
292 :
293 : SessionHolder mSecureSession;
294 :
295 : Callback::CallbackDeque mConnectionSuccess;
296 : Callback::CallbackDeque mConnectionFailure;
297 : Callback::CallbackDeque mSetupFailure;
298 :
299 : OperationalSessionReleaseDelegate * mReleaseDelegate;
300 :
301 : /// This is used when a node address is required.
302 : chip::AddressResolve::NodeLookupHandle mAddressLookupHandle;
303 :
304 : State mState = State::Uninitialized;
305 :
306 : bool mPerformingAddressUpdate = false;
307 :
308 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
309 : // When we TryNextResult on the resolver, it will synchronously call back
310 : // into our OnNodeAddressResolved when it succeeds. We need to track
311 : // whether the OnNodeAddressResolved is coming from handling a session
312 : // establishment error or whether it's happening because we didn't even
313 : // manage to start a session establishment at all. Use this member to keep
314 : // track of that.
315 : bool mTryingNextResultDueToSessionEstablishmentError = false;
316 :
317 : uint8_t mRemainingAttempts = 0;
318 : uint8_t mAttemptsDone = 0;
319 :
320 : uint8_t mResolveAttemptsAllowed = 0;
321 :
322 : Callback::CallbackDeque mConnectionRetry;
323 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
324 :
325 : void MoveToState(State aTargetState);
326 :
327 : CHIP_ERROR EstablishConnection(const ReliableMessageProtocolConfig & config);
328 :
329 : /*
330 : * This checks to see if an existing CASE session exists to the peer within the SessionManager
331 : * and if one exists, to load that into mSecureSession.
332 : *
333 : * Returns true if a valid session was found, false otherwise.
334 : *
335 : */
336 : bool AttachToExistingSecureSession();
337 :
338 : void CleanupCASEClient();
339 :
340 : void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
341 : Callback::Callback<OnSetupFailure> * onSetupFailure);
342 :
343 : void EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
344 : Callback::Callback<OnDeviceConnectionFailure> * onFailure,
345 : Callback::Callback<OnSetupFailure> * onSetupFailure);
346 :
347 : enum class ReleaseBehavior
348 : {
349 : Release,
350 : DoNotRelease
351 : };
352 :
353 : /*
354 : * This dequeues all failure and success callbacks and appropriately invokes either set depending
355 : * on the value of error.
356 : *
357 : * If error == CHIP_NO_ERROR, only success callbacks are invoked. Otherwise, only failure callbacks are invoked.
358 : *
359 : * The state offers additional context regarding the failure, indicating the specific state in which
360 : * the error occurs. It is only relayed through failure callbacks when the error is not equal to CHIP_NO_ERROR.
361 : *
362 : * If releaseBehavior is Release, this uses mReleaseDelegate to release
363 : * ourselves (aka `this`). As a result any caller should return right away
364 : * without touching `this`.
365 : *
366 : * Setting releaseBehavior to DoNotRelease is meant for use from the destructor
367 : */
368 : void DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
369 : ReleaseBehavior releaseBehavior = ReleaseBehavior::Release);
370 :
371 0 : void DequeueConnectionCallbacks(CHIP_ERROR error, ReleaseBehavior releaseBehavior = ReleaseBehavior::Release)
372 : {
373 0 : this->DequeueConnectionCallbacks(error, SessionEstablishmentStage::kNotInKeyExchange, releaseBehavior);
374 0 : }
375 :
376 : /**
377 : * Helper for DequeueConnectionCallbacks that handles the actual callback
378 : * notifications. This happens after the object has been released, if it's
379 : * being released.
380 : */
381 : static void NotifyConnectionCallbacks(Callback::Cancelable & failureReady, Callback::Cancelable & setupFailureReady,
382 : Callback::Cancelable & successReady, CHIP_ERROR error, SessionEstablishmentStage stage,
383 : const ScopedNodeId & peerId, bool performingAddressUpdate,
384 : Messaging::ExchangeManager * exchangeMgr,
385 : const Optional<SessionHandle> & optionalSessionHandle);
386 :
387 : /**
388 : * Triggers a DNSSD lookup to find a usable peer address.
389 : */
390 : CHIP_ERROR LookupPeerAddress();
391 :
392 : /**
393 : * This function will set new IP address, port and MRP retransmission intervals of the device.
394 : */
395 : void UpdateDeviceData(const Transport::PeerAddress & addr, const ReliableMessageProtocolConfig & config);
396 :
397 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
398 : /**
399 : * Schedule a setup reattempt, if possible. The outparam indicates how long
400 : * it will be before the reattempt happens.
401 : */
402 : CHIP_ERROR ScheduleSessionSetupReattempt(System::Clock::Seconds16 & timerDelay);
403 :
404 : /**
405 : * Cancel a scheduled setup reattempt, if we can (i.e. if we still have
406 : * access to the SystemLayer).
407 : */
408 : void CancelSessionSetupReattempt();
409 :
410 : /**
411 : * Helper for our backoff retry timer.
412 : */
413 : static void TrySetupAgain(System::Layer * systemLayer, void * state);
414 :
415 : /**
416 : * Helper to notify our retry callbacks that a setup error occurred and we
417 : * will retry.
418 : */
419 : void NotifyRetryHandlers(CHIP_ERROR error, const ReliableMessageProtocolConfig & remoteMrpConfig,
420 : System::Clock::Seconds16 retryDelay);
421 :
422 : /**
423 : * A version of NotifyRetryHandlers that passes in a retry timeout estimate
424 : * directly.
425 : */
426 : void NotifyRetryHandlers(CHIP_ERROR error, System::Clock::Seconds16 timeoutEstimate);
427 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
428 : };
429 :
430 : } // namespace chip
|