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 implementation of 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 : #include <app/OperationalSessionSetup.h>
28 :
29 : #include <app/CASEClient.h>
30 : #include <app/InteractionModelEngine.h>
31 : #include <transport/SecureSession.h>
32 :
33 : #include <lib/address_resolve/AddressResolve.h>
34 : #include <lib/core/CHIPCore.h>
35 : #include <lib/core/CHIPEncoding.h>
36 : #include <lib/dnssd/Resolver.h>
37 : #include <lib/support/CodeUtils.h>
38 : #include <lib/support/logging/CHIPLogging.h>
39 : #include <system/SystemClock.h>
40 : #include <system/SystemLayer.h>
41 :
42 : using namespace chip::Callback;
43 : using chip::AddressResolve::NodeLookupRequest;
44 : using chip::AddressResolve::Resolver;
45 : using chip::AddressResolve::ResolveResult;
46 :
47 : namespace chip {
48 :
49 0 : void OperationalSessionSetup::MoveToState(State aTargetState)
50 : {
51 0 : if (mState != aTargetState)
52 : {
53 0 : ChipLogDetail(Discovery, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: State change %d --> %d",
54 : mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), to_underlying(mState),
55 : to_underlying(aTargetState));
56 :
57 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
58 0 : if (mState == State::WaitingForRetry)
59 : {
60 0 : CancelSessionSetupReattempt();
61 : }
62 : #endif
63 :
64 0 : mState = aTargetState;
65 :
66 0 : if (aTargetState != State::Connecting)
67 : {
68 0 : CleanupCASEClient();
69 : }
70 : }
71 0 : }
72 :
73 0 : bool OperationalSessionSetup::AttachToExistingSecureSession()
74 : {
75 0 : VerifyOrReturnError(mState == State::NeedsAddress || mState == State::ResolvingAddress || mState == State::HasAddress ||
76 : mState == State::WaitingForRetry,
77 : false);
78 :
79 : auto sessionHandle =
80 0 : mInitParams.sessionManager->FindSecureSessionForNode(mPeerId, MakeOptional(Transport::SecureSession::Type::kCASE));
81 0 : if (!sessionHandle.HasValue())
82 0 : return false;
83 :
84 0 : ChipLogProgress(Discovery, "Found an existing secure session to [%u:" ChipLogFormatX64 "]!", mPeerId.GetFabricIndex(),
85 : ChipLogValueX64(mPeerId.GetNodeId()));
86 :
87 0 : mDeviceAddress = sessionHandle.Value()->AsSecureSession()->GetPeerAddress();
88 0 : if (!mSecureSession.Grab(sessionHandle.Value()))
89 0 : return false;
90 :
91 0 : return true;
92 0 : }
93 :
94 0 : void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
95 : Callback::Callback<OnDeviceConnectionFailure> * onFailure,
96 : Callback::Callback<OnSetupFailure> * onSetupFailure)
97 : {
98 0 : CHIP_ERROR err = CHIP_NO_ERROR;
99 0 : bool isConnected = false;
100 :
101 : //
102 : // Always enqueue our user provided callbacks into our callback list.
103 : // If anything goes wrong below, we'll trigger failures (including any queued from
104 : // a previous iteration which in theory shouldn't happen, but this is written to be more defensive)
105 : //
106 0 : EnqueueConnectionCallbacks(onConnection, onFailure, onSetupFailure);
107 :
108 0 : switch (mState)
109 : {
110 0 : case State::Uninitialized:
111 0 : err = CHIP_ERROR_INCORRECT_STATE;
112 0 : break;
113 :
114 0 : case State::NeedsAddress:
115 0 : isConnected = AttachToExistingSecureSession();
116 0 : if (!isConnected)
117 : {
118 : // LookupPeerAddress could perhaps call back with a result
119 : // synchronously, so do our state update first.
120 0 : MoveToState(State::ResolvingAddress);
121 0 : err = LookupPeerAddress();
122 0 : if (err != CHIP_NO_ERROR)
123 : {
124 : // Roll back the state change, since we are presumably not in
125 : // the middle of a lookup.
126 0 : MoveToState(State::NeedsAddress);
127 : }
128 : }
129 :
130 0 : break;
131 :
132 0 : case State::ResolvingAddress:
133 : case State::WaitingForRetry:
134 0 : isConnected = AttachToExistingSecureSession();
135 0 : break;
136 :
137 0 : case State::HasAddress:
138 0 : isConnected = AttachToExistingSecureSession();
139 0 : if (!isConnected)
140 : {
141 : // We should not actually every be in be in State::HasAddress. This
142 : // is because in the same call that we moved to State::HasAddress
143 : // we either move to State::Connecting or call
144 : // DequeueConnectionCallbacks with an error thus releasing
145 : // ourselves before any call would reach this section of code.
146 0 : err = CHIP_ERROR_INCORRECT_STATE;
147 : }
148 :
149 0 : break;
150 :
151 0 : case State::Connecting:
152 0 : break;
153 :
154 0 : case State::SecureConnected:
155 0 : isConnected = true;
156 0 : break;
157 :
158 0 : default:
159 0 : err = CHIP_ERROR_INCORRECT_STATE;
160 : }
161 :
162 0 : if (isConnected)
163 : {
164 0 : MoveToState(State::SecureConnected);
165 : }
166 :
167 : //
168 : // Dequeue all our callbacks on either encountering an error
169 : // or if we successfully connected. Both should not be set
170 : // simultaneously.
171 : //
172 0 : if (err != CHIP_NO_ERROR || isConnected)
173 : {
174 0 : DequeueConnectionCallbacks(err);
175 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
176 : // While it is odd to have an explicit return here at the end of the function, we do so
177 : // as a precaution in case someone later on adds something to the end of this function.
178 0 : return;
179 : }
180 : }
181 :
182 0 : void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
183 : Callback::Callback<OnDeviceConnectionFailure> * onFailure)
184 : {
185 0 : Connect(onConnection, onFailure, nullptr);
186 0 : }
187 :
188 0 : void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
189 : Callback::Callback<OnSetupFailure> * onSetupFailure)
190 : {
191 0 : Connect(onConnection, nullptr, onSetupFailure);
192 0 : }
193 :
194 0 : void OperationalSessionSetup::UpdateDeviceData(const Transport::PeerAddress & addr, const ReliableMessageProtocolConfig & config)
195 : {
196 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
197 : // Make sure to clear out our reason for trying the next result first thing,
198 : // so it does not stick around in various error cases.
199 0 : bool tryingNextResultDueToSessionEstablishmentError = mTryingNextResultDueToSessionEstablishmentError;
200 0 : mTryingNextResultDueToSessionEstablishmentError = false;
201 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
202 :
203 0 : if (mState == State::Uninitialized)
204 : {
205 0 : return;
206 : }
207 :
208 : #if CHIP_DETAIL_LOGGING
209 : char peerAddrBuff[Transport::PeerAddress::kMaxToStringSize];
210 0 : addr.ToString(peerAddrBuff);
211 :
212 0 : ChipLogDetail(Discovery, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: Updating device address to %s while in state %d",
213 : mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), peerAddrBuff, static_cast<int>(mState));
214 : #endif
215 :
216 0 : mDeviceAddress = addr;
217 :
218 : // Initialize CASE session state with any MRP parameters that DNS-SD has provided.
219 : // It can be overridden by CASE session protocol messages that include MRP parameters.
220 0 : if (mCASEClient)
221 : {
222 0 : mCASEClient->SetRemoteMRPIntervals(config);
223 : }
224 :
225 0 : if (mState != State::ResolvingAddress)
226 : {
227 0 : ChipLogError(Discovery, "Received UpdateDeviceData in incorrect state");
228 0 : DequeueConnectionCallbacks(CHIP_ERROR_INCORRECT_STATE);
229 : // Do not touch `this` instance anymore; it has been destroyed in
230 : // DequeueConnectionCallbacks.
231 0 : return;
232 : }
233 :
234 0 : MoveToState(State::HasAddress);
235 0 : mInitParams.sessionManager->UpdateAllSessionsPeerAddress(mPeerId, addr);
236 :
237 0 : if (mPerformingAddressUpdate)
238 : {
239 : // Nothing else to do here.
240 0 : DequeueConnectionCallbacks(CHIP_NO_ERROR);
241 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
242 0 : return;
243 : }
244 :
245 0 : CHIP_ERROR err = EstablishConnection(config);
246 0 : LogErrorOnFailure(err);
247 0 : if (err == CHIP_NO_ERROR)
248 : {
249 : // We expect to get a callback via OnSessionEstablished or OnSessionEstablishmentError to continue
250 : // the state machine forward.
251 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
252 0 : if (tryingNextResultDueToSessionEstablishmentError)
253 : {
254 : // Our retry has already been kicked off, so claim 0 delay until it
255 : // starts. We only reach this from OnSessionEstablishmentError when
256 : // the error is CHIP_ERROR_TIMEOUT.
257 0 : NotifyRetryHandlers(CHIP_ERROR_TIMEOUT, config, System::Clock::kZero);
258 : }
259 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
260 0 : return;
261 : }
262 :
263 : // Move to the ResolvingAddress state, in case we have more results,
264 : // since we expect to receive results in that state. Pretend like we moved
265 : // on directly to this address from whatever triggered us to try this result
266 : // (so restore mTryingNextResultDueToSessionEstablishmentError to the value
267 : // it had at the start of this function).
268 0 : MoveToState(State::ResolvingAddress);
269 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
270 0 : mTryingNextResultDueToSessionEstablishmentError = tryingNextResultDueToSessionEstablishmentError;
271 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
272 0 : if (CHIP_NO_ERROR == Resolver::Instance().TryNextResult(mAddressLookupHandle))
273 : {
274 : // No need to NotifyRetryHandlers, since we never actually spent any
275 : // time trying the previous result. Whatever work we need to do has
276 : // been handled by our recursive OnNodeAddressResolved callback. Make
277 : // sure not to touch `this` under here, because it might have been
278 : // deleted by OnNodeAddressResolved.
279 0 : return;
280 : }
281 :
282 : // No need to reset mTryingNextResultDueToSessionEstablishmentError here,
283 : // because we're about to delete ourselves.
284 :
285 0 : DequeueConnectionCallbacks(err);
286 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
287 : }
288 :
289 0 : CHIP_ERROR OperationalSessionSetup::EstablishConnection(const ReliableMessageProtocolConfig & config)
290 : {
291 0 : mCASEClient = mClientPool->Allocate();
292 0 : ReturnErrorCodeIf(mCASEClient == nullptr, CHIP_ERROR_NO_MEMORY);
293 :
294 0 : CHIP_ERROR err = mCASEClient->EstablishSession(mInitParams, mPeerId, mDeviceAddress, config, this);
295 0 : if (err != CHIP_NO_ERROR)
296 : {
297 0 : CleanupCASEClient();
298 0 : return err;
299 : }
300 :
301 0 : MoveToState(State::Connecting);
302 :
303 0 : return CHIP_NO_ERROR;
304 : }
305 :
306 0 : void OperationalSessionSetup::EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
307 : Callback::Callback<OnDeviceConnectionFailure> * onFailure,
308 : Callback::Callback<OnSetupFailure> * onSetupFailure)
309 : {
310 0 : if (onConnection != nullptr)
311 : {
312 0 : mConnectionSuccess.Enqueue(onConnection->Cancel());
313 : }
314 :
315 0 : if (onFailure != nullptr)
316 : {
317 0 : mConnectionFailure.Enqueue(onFailure->Cancel());
318 : }
319 :
320 0 : if (onSetupFailure != nullptr)
321 : {
322 0 : mSetupFailure.Enqueue(onSetupFailure->Cancel());
323 : }
324 0 : }
325 :
326 0 : void OperationalSessionSetup::DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
327 : ReleaseBehavior releaseBehavior)
328 : {
329 0 : Cancelable failureReady, setupFailureReady, successReady;
330 :
331 : //
332 : // Dequeue both failure and success callback lists into temporary stack args before invoking either of them.
333 : // We do this since we may not have a valid 'this' pointer anymore upon invoking any of those callbacks
334 : // since the callee may destroy this object as part of that callback.
335 : //
336 0 : mConnectionFailure.DequeueAll(failureReady);
337 0 : mSetupFailure.DequeueAll(setupFailureReady);
338 0 : mConnectionSuccess.DequeueAll(successReady);
339 :
340 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
341 : // Clear out mConnectionRetry, so that those cancelables are not holding
342 : // pointers to us, since we're about to go away.
343 0 : while (auto * cb = mConnectionRetry.First())
344 : {
345 0 : cb->Cancel();
346 0 : }
347 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
348 :
349 : // Gather up state we will need for our notifications.
350 0 : bool performingAddressUpdate = mPerformingAddressUpdate;
351 0 : auto * exchangeMgr = mInitParams.exchangeMgr;
352 0 : Optional<SessionHandle> optionalSessionHandle = mSecureSession.Get();
353 0 : ScopedNodeId peerId = mPeerId;
354 :
355 0 : if (releaseBehavior == ReleaseBehavior::Release)
356 : {
357 0 : VerifyOrDie(mReleaseDelegate != nullptr);
358 0 : mReleaseDelegate->ReleaseSession(this);
359 : }
360 :
361 : // DO NOT touch any members of this object after this point. It's dead.
362 :
363 0 : NotifyConnectionCallbacks(failureReady, setupFailureReady, successReady, error, stage, peerId, performingAddressUpdate,
364 : exchangeMgr, optionalSessionHandle);
365 0 : }
366 :
367 0 : void OperationalSessionSetup::NotifyConnectionCallbacks(Cancelable & failureReady, Cancelable & setupFailureReady,
368 : Cancelable & successReady, CHIP_ERROR error,
369 : SessionEstablishmentStage stage, const ScopedNodeId & peerId,
370 : bool performingAddressUpdate, Messaging::ExchangeManager * exchangeMgr,
371 : const Optional<SessionHandle> & optionalSessionHandle)
372 : {
373 : //
374 : // If we encountered no error, go ahead and call all success callbacks. Otherwise,
375 : // call the failure callbacks.
376 : //
377 0 : while (failureReady.mNext != &failureReady)
378 : {
379 : // We expect that we only have callbacks if we are not performing just address update.
380 0 : VerifyOrDie(!performingAddressUpdate);
381 : Callback::Callback<OnDeviceConnectionFailure> * cb =
382 0 : Callback::Callback<OnDeviceConnectionFailure>::FromCancelable(failureReady.mNext);
383 :
384 0 : cb->Cancel();
385 :
386 0 : if (error != CHIP_NO_ERROR)
387 : {
388 0 : cb->mCall(cb->mContext, peerId, error);
389 : }
390 : }
391 :
392 0 : while (setupFailureReady.mNext != &setupFailureReady)
393 : {
394 : // We expect that we only have callbacks if we are not performing just address update.
395 0 : VerifyOrDie(!performingAddressUpdate);
396 0 : Callback::Callback<OnSetupFailure> * cb = Callback::Callback<OnSetupFailure>::FromCancelable(setupFailureReady.mNext);
397 :
398 0 : cb->Cancel();
399 :
400 0 : if (error != CHIP_NO_ERROR)
401 : {
402 : // Initialize the ConnnectionFailureInfo object
403 0 : ConnnectionFailureInfo failureInfo(peerId, error, stage);
404 0 : cb->mCall(cb->mContext, failureInfo);
405 : }
406 : }
407 :
408 0 : while (successReady.mNext != &successReady)
409 : {
410 : // We expect that we only have callbacks if we are not performing just address update.
411 0 : VerifyOrDie(!performingAddressUpdate);
412 0 : Callback::Callback<OnDeviceConnected> * cb = Callback::Callback<OnDeviceConnected>::FromCancelable(successReady.mNext);
413 :
414 0 : cb->Cancel();
415 0 : if (error == CHIP_NO_ERROR)
416 : {
417 0 : VerifyOrDie(exchangeMgr);
418 : // We know that we for sure have the SessionHandle in the successful case.
419 0 : cb->mCall(cb->mContext, *exchangeMgr, optionalSessionHandle.Value());
420 : }
421 : }
422 0 : }
423 :
424 0 : void OperationalSessionSetup::OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage)
425 : {
426 0 : VerifyOrReturn(mState == State::Connecting,
427 : ChipLogError(Discovery, "OnSessionEstablishmentError was called while we were not connecting"));
428 :
429 : // If this condition ever changes, we may need to store the error in a
430 : // member instead of having a boolean
431 : // mTryingNextResultDueToSessionEstablishmentError, so we can recover the
432 : // error in UpdateDeviceData.
433 0 : if (CHIP_ERROR_TIMEOUT == error)
434 : {
435 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
436 : // Make a copy of the ReliableMessageProtocolConfig, since our
437 : // mCaseClient is about to go away once we change state.
438 0 : ReliableMessageProtocolConfig remoteMprConfig = mCASEClient->GetRemoteMRPIntervals();
439 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
440 :
441 : // Move to the ResolvingAddress state, in case we have more results,
442 : // since we expect to receive results in that state.
443 0 : MoveToState(State::ResolvingAddress);
444 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
445 0 : mTryingNextResultDueToSessionEstablishmentError = true;
446 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
447 0 : if (CHIP_NO_ERROR == Resolver::Instance().TryNextResult(mAddressLookupHandle))
448 : {
449 : // Whatever work we needed to do has been handled by our
450 : // OnNodeAddressResolved callback. Make sure not to touch `this`
451 : // under here, because it might have been deleted by
452 : // OnNodeAddressResolved.
453 0 : return;
454 : }
455 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
456 0 : mTryingNextResultDueToSessionEstablishmentError = false;
457 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
458 :
459 : // Moving back to the Connecting state would be a bit of a lie, since we
460 : // don't have an mCASEClient. Just go back to NeedsAddress, since
461 : // that's really where we are now.
462 0 : MoveToState(State::NeedsAddress);
463 :
464 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
465 0 : if (mRemainingAttempts > 0)
466 : {
467 : System::Clock::Seconds16 reattemptDelay;
468 0 : CHIP_ERROR err = ScheduleSessionSetupReattempt(reattemptDelay);
469 0 : if (err == CHIP_NO_ERROR)
470 : {
471 0 : MoveToState(State::WaitingForRetry);
472 0 : NotifyRetryHandlers(error, remoteMprConfig, reattemptDelay);
473 0 : return;
474 : }
475 : }
476 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
477 : }
478 :
479 0 : DequeueConnectionCallbacks(error, stage);
480 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
481 : }
482 :
483 0 : void OperationalSessionSetup::OnSessionEstablished(const SessionHandle & session)
484 : {
485 0 : VerifyOrReturn(mState == State::Connecting,
486 : ChipLogError(Discovery, "OnSessionEstablished was called while we were not connecting"));
487 :
488 0 : if (!mSecureSession.Grab(session))
489 : {
490 : // Got an invalid session, just dispatch an error. We have to do this
491 : // so we don't leak.
492 0 : DequeueConnectionCallbacks(CHIP_ERROR_INCORRECT_STATE);
493 :
494 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
495 0 : return;
496 : }
497 :
498 0 : MoveToState(State::SecureConnected);
499 :
500 0 : DequeueConnectionCallbacks(CHIP_NO_ERROR);
501 : }
502 :
503 0 : void OperationalSessionSetup::CleanupCASEClient()
504 : {
505 0 : if (mCASEClient)
506 : {
507 0 : mClientPool->Release(mCASEClient);
508 0 : mCASEClient = nullptr;
509 : }
510 0 : }
511 :
512 0 : OperationalSessionSetup::~OperationalSessionSetup()
513 : {
514 0 : if (mAddressLookupHandle.IsActive())
515 : {
516 0 : ChipLogDetail(Discovery,
517 : "OperationalSessionSetup[%u:" ChipLogFormatX64
518 : "]: Cancelling incomplete address resolution as device is being deleted.",
519 : mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()));
520 :
521 : // Skip cancel callback since the destructor is being called, so we assume that this object is
522 : // obviously not used anymore
523 0 : CHIP_ERROR err = Resolver::Instance().CancelLookup(mAddressLookupHandle, Resolver::FailureCallback::Skip);
524 0 : if (err != CHIP_NO_ERROR)
525 : {
526 0 : ChipLogError(Discovery, "Lookup cancel failed: %" CHIP_ERROR_FORMAT, err.Format());
527 : }
528 : }
529 :
530 0 : if (mCASEClient)
531 : {
532 : // Make sure we don't leak it.
533 0 : mClientPool->Release(mCASEClient);
534 : }
535 :
536 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
537 0 : CancelSessionSetupReattempt();
538 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
539 :
540 0 : DequeueConnectionCallbacks(CHIP_ERROR_CANCELLED, ReleaseBehavior::DoNotRelease);
541 0 : }
542 :
543 0 : CHIP_ERROR OperationalSessionSetup::LookupPeerAddress()
544 : {
545 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
546 0 : if (mRemainingAttempts > 0)
547 : {
548 0 : --mRemainingAttempts;
549 : }
550 0 : if (mAttemptsDone < UINT8_MAX)
551 : {
552 0 : ++mAttemptsDone;
553 : }
554 0 : if (mResolveAttemptsAllowed > 0)
555 : {
556 0 : --mResolveAttemptsAllowed;
557 : }
558 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
559 :
560 : // NOTE: This is public API that can be used to update our stored peer
561 : // address even when we are in State::Connected, so we do not make any
562 : // MoveToState calls in this method.
563 0 : if (mAddressLookupHandle.IsActive())
564 : {
565 0 : ChipLogProgress(Discovery,
566 : "OperationalSessionSetup[%u:" ChipLogFormatX64
567 : "]: Operational node lookup already in progress. Will NOT start a new one.",
568 : mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()));
569 0 : return CHIP_NO_ERROR;
570 : }
571 :
572 0 : auto const * fabricInfo = mInitParams.fabricTable->FindFabricWithIndex(mPeerId.GetFabricIndex());
573 0 : VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
574 :
575 0 : PeerId peerId(fabricInfo->GetCompressedFabricId(), mPeerId.GetNodeId());
576 :
577 0 : NodeLookupRequest request(peerId);
578 :
579 0 : return Resolver::Instance().LookupNode(request, mAddressLookupHandle);
580 : }
581 :
582 0 : void OperationalSessionSetup::PerformAddressUpdate()
583 : {
584 0 : if (mPerformingAddressUpdate)
585 : {
586 : // We are already in the middle of a lookup from a previous call to
587 : // PerformAddressUpdate. In that case we will just exit right away as
588 : // we are already looking to update the results from the previous lookup.
589 0 : return;
590 : }
591 :
592 : // We must be newly-allocated to handle this address lookup, so must be in the NeedsAddress state.
593 0 : VerifyOrDie(mState == State::NeedsAddress);
594 :
595 : // We are doing an address lookup whether we have an active session for this peer or not.
596 0 : mPerformingAddressUpdate = true;
597 0 : MoveToState(State::ResolvingAddress);
598 0 : CHIP_ERROR err = LookupPeerAddress();
599 0 : if (err != CHIP_NO_ERROR)
600 : {
601 0 : ChipLogError(Discovery, "Failed to look up peer address: %" CHIP_ERROR_FORMAT, err.Format());
602 0 : DequeueConnectionCallbacks(err);
603 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
604 0 : return;
605 : }
606 : }
607 :
608 0 : void OperationalSessionSetup::OnNodeAddressResolved(const PeerId & peerId, const ResolveResult & result)
609 : {
610 0 : UpdateDeviceData(result.address, result.mrpRemoteConfig);
611 0 : }
612 :
613 0 : void OperationalSessionSetup::OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason)
614 : {
615 0 : ChipLogError(Discovery, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: operational discovery failed: %" CHIP_ERROR_FORMAT,
616 : mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), reason.Format());
617 :
618 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
619 : // If we're in a mode where we would generally retry CASE, retry operational
620 : // discovery if we're allowed to. That allows us to more-gracefully handle broken networks
621 : // where multicast DNS does not actually work and hence only the initial
622 : // unicast DNS-SD queries get a response.
623 : //
624 : // We check for State::ResolvingAddress just in case in the meantime
625 : // something weird happened and we are no longer trying to resolve an
626 : // address.
627 0 : if (mState == State::ResolvingAddress && mResolveAttemptsAllowed > 0)
628 : {
629 0 : ChipLogProgress(Discovery, "Retrying operational DNS-SD discovery. Attempts remaining: %u", mResolveAttemptsAllowed);
630 :
631 : // Pretend like our previous attempt (i.e. call to LookupPeerAddress)
632 : // has not happened for purposes of the generic attempt counters, so we
633 : // don't mess up the counters for our actual CASE retry logic.
634 0 : if (mRemainingAttempts < UINT8_MAX)
635 : {
636 0 : ++mRemainingAttempts;
637 : }
638 0 : if (mAttemptsDone > 0)
639 : {
640 0 : --mAttemptsDone;
641 : }
642 :
643 0 : CHIP_ERROR err = LookupPeerAddress();
644 0 : if (err == CHIP_NO_ERROR)
645 : {
646 : // We need to notify our consumer that the resolve will take more
647 : // time, but we don't actually know how much time it will take,
648 : // because the resolver does not expose that information. Just use
649 : // one minute to be safe.
650 : using namespace chip::System::Clock::Literals;
651 0 : NotifyRetryHandlers(reason, 60_s16);
652 0 : return;
653 : }
654 : }
655 : #endif
656 :
657 : // No need to modify any variables in `this` since call below releases `this`.
658 0 : DequeueConnectionCallbacks(reason);
659 : // Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
660 : }
661 :
662 : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
663 0 : void OperationalSessionSetup::UpdateAttemptCount(uint8_t attemptCount)
664 : {
665 0 : if (attemptCount == 0)
666 : {
667 : // Nothing to do.
668 0 : return;
669 : }
670 :
671 0 : if (mState != State::NeedsAddress)
672 : {
673 : // We're in the middle of an attempt already, so decrement attemptCount
674 : // by 1 to account for that.
675 0 : --attemptCount;
676 : }
677 :
678 0 : if (attemptCount > mRemainingAttempts)
679 : {
680 0 : mRemainingAttempts = attemptCount;
681 : }
682 :
683 0 : if (attemptCount > mResolveAttemptsAllowed)
684 : {
685 0 : mResolveAttemptsAllowed = attemptCount;
686 : }
687 : }
688 :
689 0 : CHIP_ERROR OperationalSessionSetup::ScheduleSessionSetupReattempt(System::Clock::Seconds16 & timerDelay)
690 : {
691 0 : VerifyOrDie(mRemainingAttempts > 0);
692 : // Try again, but not if things are in shutdown such that we can't get
693 : // to a system layer, and not if we've run out of attempts.
694 0 : if (!mInitParams.exchangeMgr->GetSessionManager() || !mInitParams.exchangeMgr->GetSessionManager()->SystemLayer())
695 : {
696 0 : return CHIP_ERROR_INCORRECT_STATE;
697 : }
698 :
699 0 : MoveToState(State::NeedsAddress);
700 : // Stop exponential backoff before our delays get too large.
701 : //
702 : // Note that mAttemptsDone is always > 0 here, because we have
703 : // just finished one attempt.
704 0 : VerifyOrDie(mAttemptsDone > 0);
705 : static_assert(UINT16_MAX / CHIP_DEVICE_CONFIG_AUTOMATIC_CASE_RETRY_INITIAL_DELAY_SECONDS >=
706 : (1 << CHIP_DEVICE_CONFIG_AUTOMATIC_CASE_RETRY_MAX_BACKOFF),
707 : "Our backoff calculation will overflow.");
708 0 : timerDelay = System::Clock::Seconds16(
709 0 : static_cast<uint16_t>(CHIP_DEVICE_CONFIG_AUTOMATIC_CASE_RETRY_INITIAL_DELAY_SECONDS
710 0 : << min((mAttemptsDone - 1), CHIP_DEVICE_CONFIG_AUTOMATIC_CASE_RETRY_MAX_BACKOFF)));
711 0 : if (mAttemptsDone % 2 == 0)
712 : {
713 : // It's possible that the other side received one of our Sigma1 messages
714 : // and then failed to get its Sigma2 back to us. If that's the case, it
715 : // will be waiting for that Sigma2 to time out before it starts
716 : // listening for Sigma1 messages again.
717 : //
718 : // To handle that, on every other retry, add the amount of time it would
719 : // take the other side to time out.
720 0 : auto additionalTimeout = CASESession::ComputeSigma2ResponseTimeout(GetLocalMRPConfig().ValueOr(GetDefaultMRPConfig()));
721 0 : timerDelay += std::chrono::duration_cast<System::Clock::Seconds16>(additionalTimeout);
722 : }
723 0 : CHIP_ERROR err = mInitParams.exchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(timerDelay, TrySetupAgain, this);
724 : // The cast on count() is needed because the type count() returns might not
725 : // actually be uint16_t; on some platforms it's int.
726 0 : ChipLogProgress(Discovery,
727 : "OperationalSessionSetup:attempts done: %u, attempts left: %u, retry delay %us, status %" CHIP_ERROR_FORMAT,
728 : mAttemptsDone, mRemainingAttempts, static_cast<unsigned>(timerDelay.count()), err.Format());
729 0 : return err;
730 : }
731 :
732 0 : void OperationalSessionSetup::CancelSessionSetupReattempt()
733 : {
734 : // If we can't get a system layer, there is no way for us to cancel things
735 : // at this point, but hopefully that's because everything is torn down
736 : // anyway and hence the timer will not fire.
737 0 : auto * sessionManager = mInitParams.exchangeMgr->GetSessionManager();
738 0 : VerifyOrReturn(sessionManager != nullptr);
739 :
740 0 : auto * systemLayer = sessionManager->SystemLayer();
741 0 : VerifyOrReturn(systemLayer != nullptr);
742 :
743 0 : systemLayer->CancelTimer(TrySetupAgain, this);
744 : }
745 :
746 0 : void OperationalSessionSetup::TrySetupAgain(System::Layer * systemLayer, void * state)
747 : {
748 0 : auto * self = static_cast<OperationalSessionSetup *>(state);
749 :
750 0 : self->MoveToState(State::ResolvingAddress);
751 0 : CHIP_ERROR err = self->LookupPeerAddress();
752 0 : if (err == CHIP_NO_ERROR)
753 : {
754 0 : return;
755 : }
756 :
757 : // Give up; we could not start a lookup.
758 0 : self->DequeueConnectionCallbacks(err);
759 : // Do not touch `self` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
760 : }
761 :
762 0 : void OperationalSessionSetup::AddRetryHandler(Callback::Callback<OnDeviceConnectionRetry> * onRetry)
763 : {
764 0 : mConnectionRetry.Enqueue(onRetry->Cancel());
765 0 : }
766 :
767 0 : void OperationalSessionSetup::NotifyRetryHandlers(CHIP_ERROR error, const ReliableMessageProtocolConfig & remoteMrpConfig,
768 : System::Clock::Seconds16 retryDelay)
769 : {
770 : // Compute the time we are likely to need to detect that the retry has
771 : // failed.
772 0 : System::Clock::Timeout messageTimeout = CASESession::ComputeSigma1ResponseTimeout(remoteMrpConfig);
773 0 : auto timeoutSecs = std::chrono::duration_cast<System::Clock::Seconds16>(messageTimeout);
774 : // Add 1 second in case we had fractional milliseconds in messageTimeout.
775 : using namespace chip::System::Clock::Literals;
776 0 : NotifyRetryHandlers(error, timeoutSecs + 1_s16 + retryDelay);
777 0 : }
778 :
779 0 : void OperationalSessionSetup::NotifyRetryHandlers(CHIP_ERROR error, System::Clock::Seconds16 timeoutEstimate)
780 : {
781 : // We have to be very careful here: Calling into these handlers might in
782 : // theory destroy the Callback objects involved, but unlike the
783 : // succcess/failure cases we don't want to just clear the handlers from our
784 : // list when we are calling them, because we might need to call a given
785 : // handler more than once.
786 : //
787 : // To handle this we:
788 : //
789 : // 1) Snapshot the list of handlers up front, so if any of the handlers
790 : // triggers an AddRetryHandler with some other handler that does not
791 : // affect the list we plan to notify here.
792 : //
793 : // 2) When planning to notify a handler move it to a new list that contains
794 : // just that handler. This way if it gets canceled as part of the
795 : // notification we can tell it has been canceled.
796 : //
797 : // 3) If notifying the handler does not cancel it, add it back to our list
798 : // of handlers so we will notify it on future retries.
799 :
800 0 : Cancelable retryHandlerListSnapshot;
801 0 : mConnectionRetry.DequeueAll(retryHandlerListSnapshot);
802 :
803 0 : while (retryHandlerListSnapshot.mNext != &retryHandlerListSnapshot)
804 : {
805 0 : auto * cb = Callback::Callback<OnDeviceConnectionRetry>::FromCancelable(retryHandlerListSnapshot.mNext);
806 :
807 0 : Callback::CallbackDeque currentCallbackHolder;
808 0 : currentCallbackHolder.Enqueue(cb->Cancel());
809 :
810 0 : cb->mCall(cb->mContext, mPeerId, error, timeoutEstimate);
811 :
812 0 : if (currentCallbackHolder.mNext != ¤tCallbackHolder)
813 : {
814 : // Callback has not been canceled as part of the call, so is still
815 : // supposed to be registered with us.
816 0 : AddRetryHandler(cb);
817 : }
818 0 : }
819 0 : }
820 : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
821 :
822 : } // namespace chip
|