blob: 7694df2e2e3aca5c932ffa9658072c522c9b9fd7 [file] [log] [blame]
/*
* Copyright (c) 2021 Project CHIP Authors
*
* 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 <access/AuthMode.h>
#include <transport/SecureSession.h>
#include <transport/SecureSessionTable.h>
namespace chip {
namespace Transport {
void SecureSessionDeleter::Release(SecureSession * entry)
{
entry->mTable.ReleaseSession(entry);
}
void SecureSession::Activate(const ScopedNodeId & localNode, const ScopedNodeId & peerNode, CATValues peerCATs,
uint16_t peerSessionId, const SessionParameters & sessionParameters)
{
VerifyOrDie(mState == State::kEstablishing);
VerifyOrDie(peerNode.GetFabricIndex() == localNode.GetFabricIndex());
// PASE sessions must always start unassociated with a Fabric!
VerifyOrDie(!((mSecureSessionType == Type::kPASE) && (peerNode.GetFabricIndex() != kUndefinedFabricIndex)));
// CASE sessions must always start "associated" a given Fabric!
VerifyOrDie(!((mSecureSessionType == Type::kCASE) && (peerNode.GetFabricIndex() == kUndefinedFabricIndex)));
// CASE sessions can only be activated against operational node IDs!
VerifyOrDie(!((mSecureSessionType == Type::kCASE) &&
(!IsOperationalNodeId(peerNode.GetNodeId()) || !IsOperationalNodeId(localNode.GetNodeId()))));
mPeerNodeId = peerNode.GetNodeId();
mLocalNodeId = localNode.GetNodeId();
mPeerCATs = peerCATs;
mPeerSessionId = peerSessionId;
mRemoteSessionParams = sessionParameters;
SetFabricIndex(peerNode.GetFabricIndex());
MarkActiveRx(); // Initialize SessionTimestamp and ActiveTimestamp per spec.
Retain(); // This ref is released inside MarkForEviction
MoveToState(State::kActive);
if (mSecureSessionType == Type::kCASE)
mTable.NewerSessionAvailable(this);
ChipLogDetail(Inet, "SecureSession[%p]: Activated - Type:%d LSID:%d", this, to_underlying(mSecureSessionType), mLocalSessionId);
}
const char * SecureSession::StateToString(State state) const
{
switch (state)
{
case State::kEstablishing:
return "kEstablishing";
break;
case State::kActive:
return "kActive";
break;
case State::kDefunct:
return "kDefunct";
break;
case State::kPendingEviction:
return "kPendingEviction";
break;
default:
return "???";
break;
}
}
void SecureSession::MoveToState(State targetState)
{
if (mState != targetState)
{
ChipLogProgress(SecureChannel, "SecureSession[%p, LSID:%d]: State change '%s' --> '%s'", this, mLocalSessionId,
StateToString(mState), StateToString(targetState));
mState = targetState;
}
}
void SecureSession::MarkAsDefunct()
{
ChipLogDetail(Inet, "SecureSession[%p]: MarkAsDefunct Type:%d LSID:%d", this, to_underlying(mSecureSessionType),
mLocalSessionId);
ReferenceCountedHandle<Transport::Session> ref(*this);
switch (mState)
{
case State::kEstablishing:
//
// A session can only be marked as defunct from the state of Active.
//
VerifyOrDie(false);
return;
case State::kActive:
MoveToState(State::kDefunct);
return;
case State::kDefunct:
//
// Do nothing
//
return;
case State::kPendingEviction:
//
// Once a session is headed for eviction, we CANNOT bring it back to either being active or defunct. Let's just
// do nothing and return.
//
return;
}
}
void SecureSession::MarkForEviction()
{
ChipLogDetail(Inet, "SecureSession[%p]: MarkForEviction Type:%d LSID:%d", this, to_underlying(mSecureSessionType),
mLocalSessionId);
ReferenceCountedHandle<Transport::Session> ref(*this);
switch (mState)
{
case State::kEstablishing:
MoveToState(State::kPendingEviction);
// Interrupt the pairing
NotifySessionReleased();
return;
case State::kDefunct:
FALLTHROUGH;
case State::kActive:
Release(); // Decrease the ref which is retained at Activate
MoveToState(State::kPendingEviction);
NotifySessionReleased();
return;
case State::kPendingEviction:
// Do nothing
return;
}
}
Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const
{
Access::SubjectDescriptor subjectDescriptor;
if (IsOperationalNodeId(mPeerNodeId))
{
subjectDescriptor.authMode = Access::AuthMode::kCase;
subjectDescriptor.subject = mPeerNodeId;
subjectDescriptor.cats = mPeerCATs;
subjectDescriptor.fabricIndex = GetFabricIndex();
subjectDescriptor.isCommissioning = IsCommissioningSession();
}
else if (IsPAKEKeyId(mPeerNodeId))
{
// Responder (aka commissionee) gets subject descriptor filled in.
// Initiator (aka commissioner) leaves subject descriptor unfilled.
if (GetCryptoContext().IsResponder())
{
subjectDescriptor.authMode = Access::AuthMode::kPase;
subjectDescriptor.subject = mPeerNodeId;
subjectDescriptor.fabricIndex = GetFabricIndex();
subjectDescriptor.isCommissioning = IsCommissioningSession();
}
}
else
{
VerifyOrDie(false);
}
return subjectDescriptor;
}
bool SecureSession::IsCommissioningSession() const
{
// PASE session is always a commissioning session.
if (IsPASESession())
{
return true;
}
// CASE session is a commissioning session if it was marked as such.
// The SessionManager is what keeps track.
if (IsCASESession() && mIsCaseCommissioningSession)
{
return true;
}
return false;
}
void SecureSession::Retain()
{
#if CHIP_CONFIG_SECURE_SESSION_REFCOUNT_LOGGING
ChipLogProgress(SecureChannel, "SecureSession[%p]: ++ %d -> %d", this, GetReferenceCount(), GetReferenceCount() + 1);
#endif
ReferenceCounted<SecureSession, SecureSessionDeleter, 0, uint16_t>::Retain();
}
void SecureSession::Release()
{
#if CHIP_CONFIG_SECURE_SESSION_REFCOUNT_LOGGING
ChipLogProgress(SecureChannel, "SecureSession[%p]: -- %d -> %d", this, GetReferenceCount(), GetReferenceCount() - 1);
#endif
ReferenceCounted<SecureSession, SecureSessionDeleter, 0, uint16_t>::Release();
}
void SecureSession::NewerSessionAvailable(const SessionHandle & session)
{
// Shift to the new session, checks are performed by the the caller SecureSessionTable::NewerSessionAvailable.
IntrusiveList<SessionHolder>::Iterator iter = mHolders.begin();
while (iter != mHolders.end())
{
// The iterator can be invalid once the session holder is migrated to another session. So we store its next value before
// notifying the holder.
IntrusiveList<SessionHolder>::Iterator next = iter;
++next;
iter->ShiftToSession(session);
iter = next;
}
}
} // namespace Transport
} // namespace chip