blob: dc122039b7cd29477fe6419a18f7c576f1bd7a9f [file] [log] [blame]
/*
*
* Copyright (c) 2020-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.
*/
/**
* @brief Defines state relevant for an active connection to a peer.
*/
#pragma once
#include <app/util/basic-types.h>
#include <ble/BleConfig.h>
#include <lib/core/ReferenceCounted.h>
#include <messaging/ReliableMessageProtocolConfig.h>
#include <transport/CryptoContext.h>
#include <transport/Session.h>
#include <transport/SessionMessageCounter.h>
#include <transport/raw/PeerAddress.h>
namespace chip {
namespace Transport {
class SecureSessionTable;
class SecureSessionDeleter
{
public:
static void Release(SecureSession * entry);
};
/**
* Defines state of a peer connection at a transport layer.
*
* Information contained within the state:
* - SecureSessionType represents CASE or PASE session
* - PeerAddress represents how to talk to the peer
* - PeerNodeId is the unique ID of the peer
* - PeerCATs represents CASE Authenticated Tags
* - SendMessageIndex is an ever increasing index for sending messages
* - LastActivityTime is a monotonic timestamp of when this connection was
* last used. Inactive connections can expire.
* - CryptoContext contains the encryption context of a connection
*/
class SecureSession : public Session, public ReferenceCounted<SecureSession, SecureSessionDeleter, 0, uint16_t>
{
public:
/**
* @brief
* Defines SecureSession Type. Currently supported types are PASE and CASE.
*/
enum class Type : uint8_t
{
kPASE = 1,
kCASE = 2,
};
// Test-only: inject a session in Active state.
// TODO: Tests should allocate a pending session and then call Activate(), just like non-test code does.
SecureSession(SecureSessionTable & table, Type secureSessionType, uint16_t localSessionId, NodeId localNodeId,
NodeId peerNodeId, CATValues peerCATs, uint16_t peerSessionId, FabricIndex fabric,
const ReliableMessageProtocolConfig & config) :
mTable(table),
mState(State::kEstablishing), mSecureSessionType(secureSessionType), mLocalNodeId(localNodeId), mPeerNodeId(peerNodeId),
mPeerCATs(peerCATs), mLocalSessionId(localSessionId), mPeerSessionId(peerSessionId), mRemoteMRPConfig(config)
{
MoveToState(State::kActive);
Retain(); // Put the test session in Active state. This ref is released inside MarkForEviction
SetFabricIndex(fabric);
ChipLogDetail(Inet, "SecureSession[%p]: Allocated for Test Type:%d LSID:%d", this, to_underlying(mSecureSessionType),
mLocalSessionId);
}
/**
* @brief
* Construct a secure session object to associate with a pending secure
* session establishment attempt. The object for the pending session
* receives a local session ID, but no other state.
*/
SecureSession(SecureSessionTable & table, Type secureSessionType, uint16_t localSessionId) :
mTable(table), mState(State::kEstablishing), mSecureSessionType(secureSessionType), mLocalSessionId(localSessionId)
{
ChipLogDetail(Inet, "SecureSession[%p]: Allocated Type:%d LSID:%d", this, to_underlying(mSecureSessionType),
mLocalSessionId);
}
/**
* @brief
* Activate a pending Secure Session that had been reserved during CASE or
* PASE, setting internal state according to the parameters used and
* discovered during session establishment.
*/
void Activate(const ScopedNodeId & localNode, const ScopedNodeId & peerNode, CATValues peerCATs, uint16_t peerSessionId,
const ReliableMessageProtocolConfig & config);
~SecureSession() override
{
ChipLogDetail(Inet, "SecureSession[%p]: Released - Type:%d LSID:%d", this, to_underlying(mSecureSessionType),
mLocalSessionId);
}
SecureSession(SecureSession &&) = delete;
SecureSession(const SecureSession &) = delete;
SecureSession & operator=(const SecureSession &) = delete;
SecureSession & operator=(SecureSession &&) = delete;
void Retain() override;
void Release() override;
bool IsActiveSession() const override { return mState == State::kActive; }
bool IsEstablishing() const { return mState == State::kEstablishing; }
bool IsPendingEviction() const { return mState == State::kPendingEviction; }
bool IsDefunct() const { return mState == State::kDefunct; }
const char * GetStateStr() const { return StateToString(mState); }
/*
* This marks the session for eviction. It will first detach all SessionHolders attached to this
* session by calling 'OnSessionReleased' on each of them. This will force them to release their reference
* to the session. If there are no more references left, the session will then be de-allocated.
*
* Once marked for eviction, the session SHALL NOT ever become active again.
*
*/
void MarkForEviction();
/*
* This marks a previously active session as defunct to temporarily prevent it from being used with
* new exchanges to send or receive messages on this session. This should be called when there is suspicion of
* a loss-of-sync with the session state on the associated peer. This could arise if there is evidence
* of transport failure.
*
* If messages are received thereafter on this session, the session SHALL be put back into the Active state.
*
* This SHALL only be callable on an active session.
* This SHALL NOT detach any existing SessionHolders.
*
*/
void MarkAsDefunct();
Session::SessionType GetSessionType() const override { return Session::SessionType::kSecure; }
ScopedNodeId GetPeer() const override { return ScopedNodeId(mPeerNodeId, GetFabricIndex()); }
ScopedNodeId GetLocalScopedNodeId() const override { return ScopedNodeId(mLocalNodeId, GetFabricIndex()); }
Access::SubjectDescriptor GetSubjectDescriptor() const override;
bool RequireMRP() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kUdp; }
System::Clock::Milliseconds32 GetAckTimeout() const override
{
switch (mPeerAddress.GetTransportType())
{
case Transport::Type::kUdp:
return GetRetransmissionTimeout(mRemoteMRPConfig.mActiveRetransTimeout, mRemoteMRPConfig.mIdleRetransTimeout,
GetLastPeerActivityTime(), mRemoteMRPConfig.mActiveThresholdTime);
case Transport::Type::kTcp:
return System::Clock::Seconds16(30);
case Transport::Type::kBle:
return System::Clock::Milliseconds32(BTP_ACK_TIMEOUT_MS);
default:
break;
}
return System::Clock::Timeout();
}
const PeerAddress & GetPeerAddress() const { return mPeerAddress; }
void SetPeerAddress(const PeerAddress & address) { mPeerAddress = address; }
Type GetSecureSessionType() const { return mSecureSessionType; }
bool IsCASESession() const { return GetSecureSessionType() == Type::kCASE; }
bool IsPASESession() const { return GetSecureSessionType() == Type::kPASE; }
NodeId GetPeerNodeId() const { return mPeerNodeId; }
NodeId GetLocalNodeId() const { return mLocalNodeId; }
const CATValues & GetPeerCATs() const { return mPeerCATs; }
void SetRemoteMRPConfig(const ReliableMessageProtocolConfig & config) { mRemoteMRPConfig = config; }
const ReliableMessageProtocolConfig & GetRemoteMRPConfig() const override { return mRemoteMRPConfig; }
uint16_t GetLocalSessionId() const { return mLocalSessionId; }
uint16_t GetPeerSessionId() const { return mPeerSessionId; }
// Called when AddNOC has gone through sufficient success that we need to switch the
// session to reflect a new fabric if it was a PASE session
CHIP_ERROR AdoptFabricIndex(FabricIndex fabricIndex)
{
// It's not legal to augment session type for non-PASE
if (mSecureSessionType != Type::kPASE)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
SetFabricIndex(fabricIndex);
return CHIP_NO_ERROR;
}
System::Clock::Timestamp GetLastActivityTime() const { return mLastActivityTime; }
System::Clock::Timestamp GetLastPeerActivityTime() const { return mLastPeerActivityTime; }
void MarkActive() { mLastActivityTime = System::SystemClock().GetMonotonicTimestamp(); }
void MarkActiveRx()
{
mLastPeerActivityTime = System::SystemClock().GetMonotonicTimestamp();
MarkActive();
if (mState == State::kDefunct)
{
MoveToState(State::kActive);
}
}
bool IsPeerActive() const
{
return ((System::SystemClock().GetMonotonicTimestamp() - GetLastPeerActivityTime()) <
GetRemoteMRPConfig().mActiveThresholdTime);
}
System::Clock::Timestamp GetMRPBaseTimeout() const override
{
return IsPeerActive() ? GetRemoteMRPConfig().mActiveRetransTimeout : GetRemoteMRPConfig().mIdleRetransTimeout;
}
CryptoContext & GetCryptoContext() { return mCryptoContext; }
const CryptoContext & GetCryptoContext() const { return mCryptoContext; }
SessionMessageCounter & GetSessionMessageCounter() { return mSessionMessageCounter; }
// This should be a private API, only meant to be called by SecureSessionTable
// Session holders to this session may shift to the target session regarding SessionDelegate::GetNewSessionHandlingPolicy.
// It requires that the target sessoin is also a CASE session, having the same peer and CATs as this session.
void NewerSessionAvailable(const SessionHandle & session);
private:
enum class State : uint8_t
{
//
// Denotes a secure session object that is internally
// reserved by the stack before and during session establishment.
//
// Although the stack can tolerate eviction of these (releasing one
// out from under the holder would exhibit as CHIP_ERROR_INCORRECT_STATE
// during CASE or PASE), intent is that we should not and would leave
// these untouched until CASE or PASE complete.
//
// In this state, the reference count is held by the PairingSession.
//
kEstablishing = 1,
//
// The session is active, ready for use. When transitioning to this state via Activate, the
// reference count is incremented by 1, and will subsequently be decremented
// by 1 when MarkForEviction is called. This ensures the session remains resident
// and active for future use even if there currently are no references to it.
//
kActive = 2,
//
// The session is temporarily disabled due to suspicion of a loss of synchronization
// with the session state on the peer (e.g transport failure).
// In this state, no new outbound exchanges can be created. However, if we receive valid messages
// again on this session, we CAN mark this session as being active again.
//
// Transitioning to this state does not detach any existing SessionHolders.
//
// In addition to any existing SessionHolders holding a reference to this session, the SessionManager
// maintains a reference as well to the session that will only be relinquished when MarkForEviction is called.
//
kDefunct = 3,
//
// The session has been marked for eviction and is pending deallocation. All SessionHolders would have already
// been detached in a previous call to MarkForEviction. Future SessionHolders will not be able to attach to
// this session.
//
// When all SessionHandles go out of scope, the session will be released automatically.
//
kPendingEviction = 4,
};
const char * StateToString(State state) const;
void MoveToState(State targetState);
friend class SecureSessionDeleter;
friend class TestSecureSessionTable;
SecureSessionTable & mTable;
State mState;
const Type mSecureSessionType;
NodeId mLocalNodeId = kUndefinedNodeId;
NodeId mPeerNodeId = kUndefinedNodeId;
CATValues mPeerCATs = CATValues{};
const uint16_t mLocalSessionId;
uint16_t mPeerSessionId = 0;
PeerAddress mPeerAddress;
/// Timestamp of last tx or rx. @see SessionTimestamp in the spec
System::Clock::Timestamp mLastActivityTime = System::SystemClock().GetMonotonicTimestamp();
/// Timestamp of last rx. @see ActiveTimestamp in the spec
System::Clock::Timestamp mLastPeerActivityTime = System::SystemClock().GetMonotonicTimestamp();
ReliableMessageProtocolConfig mRemoteMRPConfig = GetDefaultMRPConfig();
CryptoContext mCryptoContext;
SessionMessageCounter mSessionMessageCounter;
};
} // namespace Transport
} // namespace chip