blob: 38aa12566c59b8d3353722686d96d508f2c8b466 [file] [log] [blame]
/*
*
* 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 implementation of 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.
*/
#include <app/OperationalSessionSetup.h>
#include <app/CASEClient.h>
#include <app/InteractionModelEngine.h>
#include <transport/SecureSession.h>
#include <lib/address_resolve/AddressResolve.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/dnssd/Resolver.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <system/SystemLayer.h>
using namespace chip::Callback;
using chip::AddressResolve::NodeLookupRequest;
using chip::AddressResolve::Resolver;
using chip::AddressResolve::ResolveResult;
namespace chip {
void OperationalSessionSetup::MoveToState(State aTargetState)
{
if (mState != aTargetState)
{
ChipLogDetail(Controller, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: State change %d --> %d",
mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), to_underlying(mState),
to_underlying(aTargetState));
mState = aTargetState;
if (aTargetState != State::Connecting)
{
CleanupCASEClient();
}
}
}
bool OperationalSessionSetup::AttachToExistingSecureSession()
{
VerifyOrReturnError(mState == State::NeedsAddress || mState == State::ResolvingAddress || mState == State::HasAddress, false);
auto sessionHandle =
mInitParams.sessionManager->FindSecureSessionForNode(mPeerId, MakeOptional(Transport::SecureSession::Type::kCASE));
if (!sessionHandle.HasValue())
return false;
ChipLogProgress(Controller, "Found an existing secure session to [%u:" ChipLogFormatX64 "]!", mPeerId.GetFabricIndex(),
ChipLogValueX64(mPeerId.GetNodeId()));
mDeviceAddress = sessionHandle.Value()->AsSecureSession()->GetPeerAddress();
if (!mSecureSession.Grab(sessionHandle.Value()))
return false;
return true;
}
void OperationalSessionSetup::Connect(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
{
CHIP_ERROR err = CHIP_NO_ERROR;
bool isConnected = false;
//
// Always enqueue our user provided callbacks into our callback list.
// 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);
switch (mState)
{
case State::Uninitialized:
err = CHIP_ERROR_INCORRECT_STATE;
break;
case State::NeedsAddress:
isConnected = AttachToExistingSecureSession();
if (!isConnected)
{
// LookupPeerAddress could perhaps call back with a result
// synchronously, so do our state update first.
MoveToState(State::ResolvingAddress);
err = LookupPeerAddress();
if (err != CHIP_NO_ERROR)
{
// Roll back the state change, since we are presumably not in
// the middle of a lookup.
MoveToState(State::NeedsAddress);
}
}
break;
case State::ResolvingAddress:
isConnected = AttachToExistingSecureSession();
break;
case State::HasAddress:
isConnected = AttachToExistingSecureSession();
if (!isConnected)
{
// We should not actually every be in be in State::HasAddress. This
// is because in the same call that we moved to State::HasAddress
// we either move to State::Connecting or call
// DequeueConnectionCallbacks with an error thus releasing
// ourselves before any call would reach this section of code.
err = CHIP_ERROR_INCORRECT_STATE;
}
break;
case State::Connecting:
break;
case State::SecureConnected:
isConnected = true;
break;
default:
err = CHIP_ERROR_INCORRECT_STATE;
}
if (isConnected)
{
MoveToState(State::SecureConnected);
}
//
// Dequeue all our callbacks on either encountering an error
// or if we successfully connected. Both should not be set
// simultaneously.
//
if (err != CHIP_NO_ERROR || isConnected)
{
DequeueConnectionCallbacks(err);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
// While it is odd to have an explicit return here at the end of the function, we do so
// as a precaution in case someone later on adds something to the end of this function.
return;
}
}
void OperationalSessionSetup::UpdateDeviceData(const Transport::PeerAddress & addr, const ReliableMessageProtocolConfig & config)
{
if (mState == State::Uninitialized)
{
return;
}
#if CHIP_DETAIL_LOGGING
char peerAddrBuff[Transport::PeerAddress::kMaxToStringSize];
addr.ToString(peerAddrBuff);
ChipLogDetail(Discovery, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: Updating device address to %s while in state %d",
mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), peerAddrBuff, static_cast<int>(mState));
#endif
CHIP_ERROR err = CHIP_NO_ERROR;
mDeviceAddress = addr;
// Initialize CASE session state with any MRP parameters that DNS-SD has provided.
// It can be overridden by CASE session protocol messages that include MRP parameters.
if (mCASEClient)
{
mCASEClient->SetRemoteMRPIntervals(config);
}
if (mState == State::ResolvingAddress)
{
MoveToState(State::HasAddress);
mInitParams.sessionManager->UpdateAllSessionsPeerAddress(mPeerId, addr);
if (!mPerformingAddressUpdate)
{
err = EstablishConnection(config);
if (err != CHIP_NO_ERROR)
{
DequeueConnectionCallbacks(err);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
return;
}
// We expect to get a callback via OnSessionEstablished or OnSessionEstablishmentError to continue
// the state machine forward.
return;
}
DequeueConnectionCallbacks(CHIP_NO_ERROR);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
return;
}
ChipLogError(Controller, "Received UpdateDeviceData in incorrect state");
DequeueConnectionCallbacks(CHIP_ERROR_INCORRECT_STATE);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
}
CHIP_ERROR OperationalSessionSetup::EstablishConnection(const ReliableMessageProtocolConfig & config)
{
mCASEClient = mInitParams.clientPool->Allocate(CASEClientInitParams{
mInitParams.sessionManager, mInitParams.sessionResumptionStorage, mInitParams.certificateValidityPolicy,
mInitParams.exchangeMgr, mFabricTable, mInitParams.groupDataProvider, mInitParams.mrpLocalConfig });
ReturnErrorCodeIf(mCASEClient == nullptr, CHIP_ERROR_NO_MEMORY);
CHIP_ERROR err = mCASEClient->EstablishSession(mPeerId, mDeviceAddress, config, this);
if (err != CHIP_NO_ERROR)
{
CleanupCASEClient();
return err;
}
MoveToState(State::Connecting);
return CHIP_NO_ERROR;
}
void OperationalSessionSetup::EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
{
if (onConnection != nullptr)
{
mConnectionSuccess.Enqueue(onConnection->Cancel());
}
if (onFailure != nullptr)
{
mConnectionFailure.Enqueue(onFailure->Cancel());
}
}
void OperationalSessionSetup::DequeueConnectionCallbacks(CHIP_ERROR error)
{
Cancelable failureReady, 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);
mConnectionSuccess.DequeueAll(successReady);
//
// If we encountered no error, go ahead and call all success callbacks. Otherwise,
// call the failure callbacks.
//
while (failureReady.mNext != &failureReady)
{
// We expect that we only have callbacks if we are not performing just address update.
VerifyOrDie(!mPerformingAddressUpdate);
Callback::Callback<OnDeviceConnectionFailure> * cb =
Callback::Callback<OnDeviceConnectionFailure>::FromCancelable(failureReady.mNext);
cb->Cancel();
if (error != CHIP_NO_ERROR)
{
cb->mCall(cb->mContext, mPeerId, error);
}
}
while (successReady.mNext != &successReady)
{
// We expect that we only have callbacks if we are not performing just address update.
VerifyOrDie(!mPerformingAddressUpdate);
Callback::Callback<OnDeviceConnected> * cb = Callback::Callback<OnDeviceConnected>::FromCancelable(successReady.mNext);
cb->Cancel();
if (error == CHIP_NO_ERROR)
{
auto * exchangeMgr = mInitParams.exchangeMgr;
VerifyOrDie(exchangeMgr);
// We know that we for sure have the SessionHandle in the successful case.
auto optionalSessionHandle = mSecureSession.Get();
cb->mCall(cb->mContext, *exchangeMgr, optionalSessionHandle.Value());
}
}
VerifyOrDie(mReleaseDelegate != nullptr);
mReleaseDelegate->ReleaseSession(this);
}
void OperationalSessionSetup::OnSessionEstablishmentError(CHIP_ERROR error)
{
VerifyOrReturn(mState != State::Uninitialized && mState != State::NeedsAddress,
ChipLogError(Controller, "HandleCASEConnectionFailure was called while the device was not initialized"));
DequeueConnectionCallbacks(error);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
}
void OperationalSessionSetup::OnSessionEstablished(const SessionHandle & session)
{
VerifyOrReturn(mState != State::Uninitialized,
ChipLogError(Controller, "HandleCASEConnected was called while the device was not initialized"));
if (!mSecureSession.Grab(session))
return; // Got an invalid session, do not change any state
MoveToState(State::SecureConnected);
DequeueConnectionCallbacks(CHIP_NO_ERROR);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
}
void OperationalSessionSetup::CleanupCASEClient()
{
if (mCASEClient)
{
mInitParams.clientPool->Release(mCASEClient);
mCASEClient = nullptr;
}
}
void OperationalSessionSetup::OnSessionReleased()
{
// This is unlikely to be called since within the same call that we get SessionHandle we
// then call DequeueConnectionCallbacks which releases `this`. If this is called, and we
// we have any callbacks we will just send an error.
DequeueConnectionCallbacks(CHIP_ERROR_INCORRECT_STATE);
}
OperationalSessionSetup::~OperationalSessionSetup()
{
if (mAddressLookupHandle.IsActive())
{
ChipLogDetail(Discovery,
"OperationalSessionSetup[%u:" ChipLogFormatX64
"]: Cancelling incomplete address resolution as device is being deleted.",
mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()));
// Skip cancel callback since the destructor is being called, so we assume that this object is
// obviously not used anymore
CHIP_ERROR err = Resolver::Instance().CancelLookup(mAddressLookupHandle, Resolver::FailureCallback::Skip);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Lookup cancel failed: %" CHIP_ERROR_FORMAT, err.Format());
}
}
if (mCASEClient)
{
// Make sure we don't leak it.
mInitParams.clientPool->Release(mCASEClient);
}
}
CHIP_ERROR OperationalSessionSetup::LookupPeerAddress()
{
// NOTE: This is public API that can be used to update our stored peer
// address even when we are in State::Connected, so we do not make any
// MoveToState calls in this method.
if (mAddressLookupHandle.IsActive())
{
ChipLogProgress(Discovery,
"OperationalSessionSetup[%u:" ChipLogFormatX64
"]: Operational node lookup already in progress. Will NOT start a new one.",
mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()));
return CHIP_NO_ERROR;
}
auto const * fabricInfo = mFabricTable->FindFabricWithIndex(mPeerId.GetFabricIndex());
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
PeerId peerId(fabricInfo->GetCompressedFabricId(), mPeerId.GetNodeId());
NodeLookupRequest request(peerId);
return Resolver::Instance().LookupNode(request, mAddressLookupHandle);
}
void OperationalSessionSetup::PerformAddressUpdate()
{
if (mPerformingAddressUpdate)
{
// We are already in the middle of a lookup from a previous call to
// PerformAddressUpdate. In that case we will just exit right away as
// we are already looking to update the results from the previous lookup.
return;
}
// We must be newly-allocated to handle this address lookup, so must be in the NeedsAddress state.
VerifyOrDie(mState == State::NeedsAddress);
// We are doing an address lookup whether we have an active session for this peer or not.
mPerformingAddressUpdate = true;
MoveToState(State::ResolvingAddress);
CHIP_ERROR err = LookupPeerAddress();
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to look up peer address: %" CHIP_ERROR_FORMAT, err.Format());
DequeueConnectionCallbacks(err);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
return;
}
}
void OperationalSessionSetup::OnNodeAddressResolved(const PeerId & peerId, const ResolveResult & result)
{
UpdateDeviceData(result.address, result.mrpRemoteConfig);
}
void OperationalSessionSetup::OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason)
{
ChipLogError(Discovery, "OperationalSessionSetup[%u:" ChipLogFormatX64 "]: operational discovery failed: %" CHIP_ERROR_FORMAT,
mPeerId.GetFabricIndex(), ChipLogValueX64(mPeerId.GetNodeId()), reason.Format());
// No need to modify any variables in `this` since call below releases `this`.
DequeueConnectionCallbacks(reason);
// Do not touch `this` instance anymore; it has been destroyed in DequeueConnectionCallbacks.
}
} // namespace chip