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