blob: ae4ca272858a78ff17f48a28d530d500e824fae6 [file] [log] [blame]
/*
*
* Copyright (c) 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.
*/
#include <protocols/secure_channel/PairingSession.h>
#include <app/SpecificationDefinedRevisions.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/TLVTypes.h>
#include <lib/support/SafeInt.h>
#include <transport/SessionManager.h>
namespace chip {
CHIP_ERROR PairingSession::AllocateSecureSession(SessionManager & sessionManager, const ScopedNodeId & sessionEvictionHint)
{
auto handle = sessionManager.AllocateSession(GetSecureSessionType(), sessionEvictionHint);
VerifyOrReturnError(handle.HasValue(), CHIP_ERROR_NO_MEMORY);
VerifyOrReturnError(mSecureSessionHolder.GrabPairingSession(handle.Value()), CHIP_ERROR_INTERNAL);
mSessionManager = &sessionManager;
return CHIP_NO_ERROR;
}
CHIP_ERROR PairingSession::ActivateSecureSession(const Transport::PeerAddress & peerAddress)
{
// Prepare SecureSession fields, including key derivation, first, before activation
Transport::SecureSession * secureSession = mSecureSessionHolder->AsSecureSession();
ReturnErrorOnFailure(DeriveSecureSession(secureSession->GetCryptoContext()));
uint16_t peerSessionId = GetPeerSessionId();
secureSession->SetPeerAddress(peerAddress);
secureSession->GetSessionMessageCounter().GetPeerMessageCounter().SetCounter(Transport::PeerMessageCounter::kInitialSyncValue);
// Call Activate last, otherwise errors on anything after would lead to
// a partially valid session.
secureSession->Activate(GetLocalScopedNodeId(), GetPeer(), GetPeerCATs(), peerSessionId, GetRemoteSessionParameters());
ChipLogDetail(Inet, "New secure session activated for device " ChipLogFormatScopedNodeId ", LSID:%d PSID:%d!",
ChipLogValueScopedNodeId(GetPeer()), secureSession->GetLocalSessionId(), peerSessionId);
return CHIP_NO_ERROR;
}
void PairingSession::Finish()
{
Transport::PeerAddress address = mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->GetPeerAddress();
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
if (address.GetTransportType() == Transport::Type::kTcp)
{
// Fetch the connection for the unauthenticated session used to set up
// the secure session.
Transport::ActiveTCPConnectionState * conn =
mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->GetTCPConnection();
// Associate the connection with the secure session being activated.
mSecureSessionHolder->AsSecureSession()->SetTCPConnection(conn);
}
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
// Discard the exchange so that Clear() doesn't try closing it. The exchange will handle that.
DiscardExchange();
CHIP_ERROR err = ActivateSecureSession(address);
if (err == CHIP_NO_ERROR)
{
VerifyOrDie(mSecureSessionHolder);
// Make sure to null out mDelegate so we don't send it any other
// notifications.
auto * delegate = mDelegate;
mDelegate = nullptr;
delegate->OnSessionEstablished(mSecureSessionHolder.Get().Value());
}
else
{
NotifySessionEstablishmentError(err);
}
}
void PairingSession::DiscardExchange()
{
if (mExchangeCtxt.HasValue())
{
// Make sure the exchange doesn't try to notify us when it closes,
// since we might be dead by then.
mExchangeCtxt.Value()->SetDelegate(nullptr);
// Null out mExchangeCtxt so that Clear() doesn't try closing it. The
// exchange will handle that.
mExchangeCtxt.ClearValue();
}
}
CHIP_ERROR PairingSession::EncodeSessionParameters(TLV::Tag tag, const ReliableMessageProtocolConfig & mrpLocalConfig,
TLV::TLVWriter & tlvWriter)
{
TLV::TLVType mrpParamsContainer;
ReturnErrorOnFailure(tlvWriter.StartContainer(tag, TLV::kTLVType_Structure, mrpParamsContainer));
ReturnErrorOnFailure(
tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kSessionIdleInterval), mrpLocalConfig.mIdleRetransTimeout.count()));
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kSessionActiveInterval),
mrpLocalConfig.mActiveRetransTimeout.count()));
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kSessionActiveThreshold),
mrpLocalConfig.mActiveThresholdTime.count()));
uint16_t dataModel = Revision::kDataModelRevision;
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kDataModelRevision), dataModel));
uint16_t interactionModel = Revision::kInteractionModelRevision;
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kInteractionModelRevision), interactionModel));
uint32_t specVersion = Revision::kSpecificationVersion;
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kSpecificationVersion), specVersion));
uint16_t maxPathsPerInvoke = CHIP_CONFIG_MAX_PATHS_PER_INVOKE;
ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(SessionParameters::Tag::kMaxPathsPerInvoke), maxPathsPerInvoke));
return tlvWriter.EndContainer(mrpParamsContainer);
}
CHIP_ERROR PairingSession::DecodeMRPParametersIfPresent(TLV::Tag expectedTag, TLV::ContiguousBufferTLVReader & tlvReader)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// The MRP parameters are optional.
if (tlvReader.GetTag() != expectedTag)
{
return CHIP_NO_ERROR;
}
TLV::TLVType containerType = TLV::kTLVType_Structure;
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));
ReturnErrorOnFailure(tlvReader.Next());
ChipLogDetail(SecureChannel, "Found MRP parameters in the message");
// All TLV elements in the structure are optional. If the first element is present, process it and move
// the TLV reader to the next element.
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kSessionIdleInterval)
{
uint32_t idleRetransTimeout;
ReturnErrorOnFailure(tlvReader.Get(idleRetransTimeout));
mRemoteSessionParams.SetMRPIdleRetransTimeout(System::Clock::Milliseconds32(idleRetransTimeout));
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kSessionActiveInterval)
{
uint32_t activeRetransTimeout;
ReturnErrorOnFailure(tlvReader.Get(activeRetransTimeout));
mRemoteSessionParams.SetMRPActiveRetransTimeout(System::Clock::Milliseconds32(activeRetransTimeout));
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kSessionActiveThreshold)
{
uint16_t activeThresholdTime;
ReturnErrorOnFailure(tlvReader.Get(activeThresholdTime));
mRemoteSessionParams.SetMRPActiveThresholdTime(System::Clock::Milliseconds16(activeThresholdTime));
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kDataModelRevision)
{
uint16_t dataModelRevision;
ReturnErrorOnFailure(tlvReader.Get(dataModelRevision));
mRemoteSessionParams.SetDataModelRevision(dataModelRevision);
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kInteractionModelRevision)
{
uint16_t interactionModelRevision;
ReturnErrorOnFailure(tlvReader.Get(interactionModelRevision));
mRemoteSessionParams.SetInteractionModelRevision(interactionModelRevision);
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kSpecificationVersion)
{
uint32_t specificationVersion;
ReturnErrorOnFailure(tlvReader.Get(specificationVersion));
mRemoteSessionParams.SetSpecificationVersion(specificationVersion);
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
if (TLV::TagNumFromTag(tlvReader.GetTag()) == SessionParameters::Tag::kMaxPathsPerInvoke)
{
uint16_t maxPathsPerInvoke;
ReturnErrorOnFailure(tlvReader.Get(maxPathsPerInvoke));
mRemoteSessionParams.SetMaxPathsPerInvoke(maxPathsPerInvoke);
// The next element is optional. If it's not present, return CHIP_NO_ERROR.
SuccessOrExit(err = tlvReader.Next());
}
// Future proofing - Don't error out if there are other tags
exit:
if (err == CHIP_END_OF_TLV)
{
return tlvReader.ExitContainer(containerType);
}
return err;
}
bool PairingSession::IsSessionEstablishmentInProgress()
{
if (!mSecureSessionHolder)
{
return false;
}
Transport::SecureSession * secureSession = mSecureSessionHolder->AsSecureSession();
return secureSession->IsEstablishing();
}
void PairingSession::Clear()
{
// Clear acts like the destructor of PairingSession. If it is called during
// the middle of pairing, that means we should terminate the exchange. For the
// normal path, the exchange should already be discarded before calling Clear.
if (mExchangeCtxt.HasValue())
{
// The only time we reach this is when we are getting destroyed in the
// middle of our handshake. In that case, there is no point in trying to
// do MRP resends of the last message we sent. So, abort the exchange
// instead of just closing it.
mExchangeCtxt.Value()->Abort();
mExchangeCtxt.ClearValue();
}
mSecureSessionHolder.Release();
mPeerSessionId.ClearValue();
mSessionManager = nullptr;
}
void PairingSession::NotifySessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage)
{
if (mDelegate == nullptr)
{
// Already notified success or error.
return;
}
auto * delegate = mDelegate;
mDelegate = nullptr;
delegate->OnSessionEstablishmentError(error, stage);
}
void PairingSession::OnSessionReleased()
{
if (mRole == CryptoContext::SessionRole::kInitiator)
{
NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
return;
}
// Send the error notification async, because our delegate is likely to want
// to create a new session to listen for new connection attempts, and doing
// that under an OnSessionReleased notification is not safe.
if (!mSessionManager)
{
return;
}
mSessionManager->SystemLayer()->ScheduleWork(
[](auto * systemLayer, auto * appState) -> void {
ChipLogError(Inet, "ASYNC CASE Session establishment failed");
auto * _this = static_cast<PairingSession *>(appState);
_this->NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
},
this);
}
} // namespace chip