Skip to content

Commit

Permalink
Return ConnectionFailureException which contains the connection state (
Browse files Browse the repository at this point in the history
  • Loading branch information
yufengwangca authored Dec 20, 2023
1 parent f7cd2d6 commit ad98820
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 29 deletions.
62 changes: 50 additions & 12 deletions src/app/OperationalSessionSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ bool OperationalSessionSetup::AttachToExistingSecureSession()
}

void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
Callback::Callback<OnDeviceConnectionFailure> * onFailure,
Callback::Callback<OnSetupFailure> * onSetupFailure)
{
CHIP_ERROR err = CHIP_NO_ERROR;
bool isConnected = false;
Expand All @@ -102,7 +103,7 @@ void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * on
// If anything goes wrong below, we'll trigger failures (including any queued from
// a previous iteration which in theory shouldn't happen, but this is written to be more defensive)
//
EnqueueConnectionCallbacks(onConnection, onFailure);
EnqueueConnectionCallbacks(onConnection, onFailure, onSetupFailure);

switch (mState)
{
Expand Down Expand Up @@ -178,6 +179,18 @@ void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * on
}
}

void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
{
Connect(onConnection, onFailure, nullptr);
}

void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnSetupFailure> * onSetupFailure)
{
Connect(onConnection, nullptr, onSetupFailure);
}

void OperationalSessionSetup::UpdateDeviceData(const Transport::PeerAddress & addr, const ReliableMessageProtocolConfig & config)
{
#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
Expand Down Expand Up @@ -291,7 +304,8 @@ CHIP_ERROR OperationalSessionSetup::EstablishConnection(const ReliableMessagePro
}

void OperationalSessionSetup::EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
Callback::Callback<OnDeviceConnectionFailure> * onFailure,
Callback::Callback<OnSetupFailure> * onSetupFailure)
{
if (onConnection != nullptr)
{
Expand All @@ -302,18 +316,25 @@ void OperationalSessionSetup::EnqueueConnectionCallbacks(Callback::Callback<OnDe
{
mConnectionFailure.Enqueue(onFailure->Cancel());
}

if (onSetupFailure != nullptr)
{
mSetupFailure.Enqueue(onSetupFailure->Cancel());
}
}

void OperationalSessionSetup::DequeueConnectionCallbacks(CHIP_ERROR error, ReleaseBehavior releaseBehavior)
void OperationalSessionSetup::DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
ReleaseBehavior releaseBehavior)
{
Cancelable failureReady, successReady;
Cancelable failureReady, setupFailureReady, successReady;

//
// Dequeue both failure and success callback lists into temporary stack args before invoking either of them.
// We do this since we may not have a valid 'this' pointer anymore upon invoking any of those callbacks
// since the callee may destroy this object as part of that callback.
//
mConnectionFailure.DequeueAll(failureReady);
mSetupFailure.DequeueAll(setupFailureReady);
mConnectionSuccess.DequeueAll(successReady);

#if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
Expand All @@ -339,13 +360,14 @@ void OperationalSessionSetup::DequeueConnectionCallbacks(CHIP_ERROR error, Relea

// DO NOT touch any members of this object after this point. It's dead.

NotifyConnectionCallbacks(failureReady, successReady, error, peerId, performingAddressUpdate, exchangeMgr,
optionalSessionHandle);
NotifyConnectionCallbacks(failureReady, setupFailureReady, successReady, error, stage, peerId, performingAddressUpdate,
exchangeMgr, optionalSessionHandle);
}

void OperationalSessionSetup::NotifyConnectionCallbacks(Cancelable & failureReady, Cancelable & successReady, CHIP_ERROR error,
const ScopedNodeId & peerId, bool performingAddressUpdate,
Messaging::ExchangeManager * exchangeMgr,
void OperationalSessionSetup::NotifyConnectionCallbacks(Cancelable & failureReady, Cancelable & setupFailureReady,
Cancelable & successReady, CHIP_ERROR error,
SessionEstablishmentStage stage, const ScopedNodeId & peerId,
bool performingAddressUpdate, Messaging::ExchangeManager * exchangeMgr,
const Optional<SessionHandle> & optionalSessionHandle)
{
//
Expand All @@ -367,6 +389,22 @@ void OperationalSessionSetup::NotifyConnectionCallbacks(Cancelable & failureRead
}
}

while (setupFailureReady.mNext != &setupFailureReady)
{
// We expect that we only have callbacks if we are not performing just address update.
VerifyOrDie(!performingAddressUpdate);
Callback::Callback<OnSetupFailure> * cb = Callback::Callback<OnSetupFailure>::FromCancelable(setupFailureReady.mNext);

cb->Cancel();

if (error != CHIP_NO_ERROR)
{
// Initialize the ConnnectionFailureInfo object
ConnnectionFailureInfo failureInfo(peerId, error, stage);
cb->mCall(cb->mContext, failureInfo);
}
}

while (successReady.mNext != &successReady)
{
// We expect that we only have callbacks if we are not performing just address update.
Expand All @@ -383,7 +421,7 @@ void OperationalSessionSetup::NotifyConnectionCallbacks(Cancelable & failureRead
}
}

void OperationalSessionSetup::OnSessionEstablishmentError(CHIP_ERROR error)
void OperationalSessionSetup::OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage)
{
VerifyOrReturn(mState == State::Connecting,
ChipLogError(Discovery, "OnSessionEstablishmentError was called while we were not connecting"));
Expand Down Expand Up @@ -438,7 +476,7 @@ void OperationalSessionSetup::OnSessionEstablishmentError(CHIP_ERROR error)
#endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
}

DequeueConnectionCallbacks(error);
DequeueConnectionCallbacks(error, stage);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
}

Expand Down
66 changes: 55 additions & 11 deletions src/app/OperationalSessionSetup.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ typedef void (*OnDeviceConnectionRetry)(void * context, const ScopedNodeId & pee
class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate, public AddressResolve::NodeListener
{
public:
struct ConnnectionFailureInfo
{
const ScopedNodeId peerId;
CHIP_ERROR error;
SessionEstablishmentStage sessionStage;

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,
Expand All @@ -180,8 +193,8 @@ class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate,
* The device is expected to have been commissioned, A CASE session
* setup will be triggered.
*
* On establishing the session, the callback function `onConnection` will be called. If the
* session setup fails, `onFailure` will be called.
* 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.
Expand All @@ -192,11 +205,28 @@ class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate,
*/
void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure);

/*
* 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).
*/
void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnSetupFailure> * onSetupFailure);

bool IsForAddressUpdate() const { return mPerformingAddressUpdate; }

//////////// SessionEstablishmentDelegate Implementation ///////////////
void OnSessionEstablished(const SessionHandle & session) override;
void OnSessionEstablishmentError(CHIP_ERROR error) override;
void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage) override;

ScopedNodeId GetPeerId() const { return mPeerId; }

Expand Down Expand Up @@ -264,6 +294,7 @@ class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate,

Callback::CallbackDeque mConnectionSuccess;
Callback::CallbackDeque mConnectionFailure;
Callback::CallbackDeque mSetupFailure;

OperationalSessionReleaseDelegate * mReleaseDelegate;

Expand Down Expand Up @@ -306,8 +337,12 @@ class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate,

void CleanupCASEClient();

void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
Callback::Callback<OnSetupFailure> * onSetupFailure);

void EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure);
Callback::Callback<OnDeviceConnectionFailure> * onFailure,
Callback::Callback<OnSetupFailure> * onSetupFailure);

enum class ReleaseBehavior
{
Expand All @@ -316,27 +351,36 @@ class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate,
};

/*
* This dequeues all failure and success callbacks and appropriately
* invokes either set depending on the value of error.
* 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.
*
* 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, ReleaseBehavior releaseBehavior = ReleaseBehavior::Release);
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(Callback::Cancelable & failureReady, Callback::Cancelable & successReady,
CHIP_ERROR error, const ScopedNodeId & peerId, bool performingAddressUpdate,
static void NotifyConnectionCallbacks(Callback::Cancelable & failureReady, Callback::Cancelable & setupFailureReady,
Callback::Cancelable & successReady, CHIP_ERROR error, SessionEstablishmentStage stage,
const ScopedNodeId & peerId, bool performingAddressUpdate,
Messaging::ExchangeManager * exchangeMgr,
const Optional<SessionHandle> & optionalSessionHandle);

Expand Down
29 changes: 28 additions & 1 deletion src/protocols/secure_channel/CASESession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,9 +556,11 @@ void CASESession::OnResponseTimeout(ExchangeContext * ec)

void CASESession::AbortPendingEstablish(CHIP_ERROR err)
{
// This needs to come before Clear() which will reset mState.
SessionEstablishmentStage state = MapCASEStateToSessionEstablishmentStage(mState);
Clear();
// Do this last in case the delegate frees us.
NotifySessionEstablishmentError(err);
NotifySessionEstablishmentError(err, state);
}

CHIP_ERROR CASESession::DeriveSecureSession(CryptoContext & session) const
Expand Down Expand Up @@ -2255,4 +2257,29 @@ bool CASESession::InvokeBackgroundWorkWatchdog()
return watchdogFired;
}

// Helper function to map CASESession::State to SessionEstablishmentStage
SessionEstablishmentStage CASESession::MapCASEStateToSessionEstablishmentStage(State caseState)
{
switch (caseState)
{
case State::kInitialized:
return SessionEstablishmentStage::kNotInKeyExchange;
case State::kSentSigma1:
case State::kSentSigma1Resume:
return SessionEstablishmentStage::kSentSigma1;
case State::kSentSigma2:
case State::kSentSigma2Resume:
return SessionEstablishmentStage::kSentSigma2;
case State::kSendSigma3Pending:
return SessionEstablishmentStage::kReceivedSigma2;
case State::kSentSigma3:
return SessionEstablishmentStage::kSentSigma3;
case State::kHandleSigma3Pending:
return SessionEstablishmentStage::kReceivedSigma3;
// Add more mappings here for other states
default:
return SessionEstablishmentStage::kUnknown; // Default mapping
}
}

} // namespace chip
2 changes: 2 additions & 0 deletions src/protocols/secure_channel/CASESession.h
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler,
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
Optional<State> mStopHandshakeAtState = Optional<State>::Missing();
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST

SessionEstablishmentStage MapCASEStateToSessionEstablishmentStage(State caseState);
};

} // namespace chip
4 changes: 2 additions & 2 deletions src/protocols/secure_channel/PairingSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ void PairingSession::Clear()
mSessionManager = nullptr;
}

void PairingSession::NotifySessionEstablishmentError(CHIP_ERROR error)
void PairingSession::NotifySessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage)
{
if (mDelegate == nullptr)
{
Expand All @@ -265,7 +265,7 @@ void PairingSession::NotifySessionEstablishmentError(CHIP_ERROR error)

auto * delegate = mDelegate;
mDelegate = nullptr;
delegate->OnSessionEstablishmentError(error);
delegate->OnSessionEstablishmentError(error, stage);
}

void PairingSession::OnSessionReleased()
Expand Down
10 changes: 7 additions & 3 deletions src/protocols/secure_channel/PairingSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,14 @@ class DLL_EXPORT PairingSession : public SessionDelegate
void Clear();

/**
* Notify our delegate about a session establishment error, if we have not
* notified it of an error or success before.
* Notify our delegate about a session establishment error and the stage when the error occurs
* if we have not already notified it of an error or success before.
*
* @param error The error code to report.
* @param stage The stage of the session when the error occurs, defaults to kNotInKeyExchange.
*/
void NotifySessionEstablishmentError(CHIP_ERROR error);
void NotifySessionEstablishmentError(CHIP_ERROR error,
SessionEstablishmentStage stage = SessionEstablishmentStage::kNotInKeyExchange);

protected:
CryptoContext::SessionRole mRole;
Expand Down
25 changes: 25 additions & 0 deletions src/protocols/secure_channel/SessionEstablishmentDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,41 @@

namespace chip {

enum class SessionEstablishmentStage : uint8_t
{
kUnknown = 0,
kNotInKeyExchange = 1,
kSentSigma1 = 2,
kReceivedSigma1 = 3,
kSentSigma2 = 4,
kReceivedSigma2 = 5,
kSentSigma3 = 6,
kReceivedSigma3 = 7,
};

class DLL_EXPORT SessionEstablishmentDelegate
{
public:
/**
* Called when session establishment fails with an error. This will be
* called at most once per session establishment and will not be called if
* OnSessionEstablished is called.
*
* This overload of OnSessionEstablishmentError is not called directly. It's only called from the default
*. implemetation of the two-argument overload.
*/
virtual void OnSessionEstablishmentError(CHIP_ERROR error) {}

/**
* Called when session establishment fails with an error and state at the
* failure. This will be called at most once per session establishment and
* will not be called if OnSessionEstablished is called.
*/
virtual void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage)
{
OnSessionEstablishmentError(error);
}

/**
* Called on start of session establishment process
*/
Expand Down

0 comments on commit ad98820

Please sign in to comment.