blob: d5690bc83c67479bc8cce6695474dadd2903b1ea [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.
*/
#pragma once
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/Pool.h>
#include <lib/support/SortUtils.h>
#include <system/TimeSource.h>
#include <transport/SecureSession.h>
namespace chip {
namespace Transport {
inline constexpr uint16_t kMaxSessionID = UINT16_MAX;
inline constexpr uint16_t kUnsecuredSessionId = 0;
/**
* Handles a set of sessions.
*
* Intended for:
* - handle session active time and expiration
* - allocate and free space for sessions.
*/
class SecureSessionTable
{
public:
~SecureSessionTable() { mEntries.ReleaseAll(); }
void Init() { mNextSessionId = chip::Crypto::GetRandU16(); }
/**
* Allocate a new secure session out of the internal resource pool.
*
* @param secureSessionType secure session type
* @param localSessionId unique identifier for the local node's secure unicast session context
* @param localNodeId represents the local Node ID for this node
* @param peerNodeId represents peer Node's ID
* @param peerCATs represents peer CASE Authenticated Tags
* @param peerSessionId represents the encryption key ID assigned by peer node
* @param fabricIndex represents fabric index for the session
* @param config represents the reliable message protocol configuration
*
* @note the newly created state will have an 'active' time set based on the current time source.
*
* @returns CHIP_NO_ERROR if state could be initialized. May fail if maximum session count
* has been reached (with CHIP_ERROR_NO_MEMORY).
*/
CHECK_RETURN_VALUE
Optional<SessionHandle> CreateNewSecureSessionForTest(SecureSession::Type secureSessionType, uint16_t localSessionId,
NodeId localNodeId, NodeId peerNodeId, CATValues peerCATs,
uint16_t peerSessionId, FabricIndex fabricIndex,
const ReliableMessageProtocolConfig & config);
/**
* Allocate a new secure session out of the internal resource pool with a
* non-colliding session ID and increments mNextSessionId to give a clue to
* the allocator for the next allocation. The secure session session will
* not become active until the call to SecureSession::Activate.
*
* @returns allocated session, or NullOptional on failure
*/
CHECK_RETURN_VALUE
Optional<SessionHandle> CreateNewSecureSession(SecureSession::Type secureSessionType, ScopedNodeId sessionEvictionHint);
void ReleaseSession(SecureSession * session) { mEntries.ReleaseObject(session); }
template <typename Function>
Loop ForEachSession(Function && function)
{
return mEntries.ForEachActiveObject(std::forward<Function>(function));
}
/**
* Get a secure session given its session ID.
*
* @param localSessionId the identifier of a secure unicast session context within the local node
*
* @return the session if found, NullOptional if not found
*/
CHECK_RETURN_VALUE
Optional<SessionHandle> FindSecureSessionByLocalKey(uint16_t localSessionId);
// Select SessionHolders which are pointing to a session with the same peer as the given session. Shift them to the given
// session.
// This is an internal API, using raw pointer to a session is allowed here.
void NewerSessionAvailable(SecureSession * session)
{
VerifyOrDie(session->GetSecureSessionType() == SecureSession::Type::kCASE);
mEntries.ForEachActiveObject([&](SecureSession * oldSession) {
if (session == oldSession)
return Loop::Continue;
SessionHandle ref(*oldSession);
// This will give all SessionHolders pointing to oldSession a chance to switch to the provided session
//
// See documentation for SessionDelegate::GetNewSessionHandlingPolicy about how session auto-shifting works, and how
// to disable it for a specific SessionHolder in a specific scenario.
if (oldSession->GetSecureSessionType() == SecureSession::Type::kCASE && oldSession->GetPeer() == session->GetPeer() &&
oldSession->GetPeerCATs() == session->GetPeerCATs())
{
oldSession->NewerSessionAvailable(SessionHandle(*session));
}
return Loop::Continue;
});
}
private:
friend class TestSecureSessionTable;
/**
* This provides a sortable wrapper for a SecureSession object. A SecureSession
* isn't directly sortable since it is not swappable (i.e meet criteria for ValueSwappable).
*
* However, this wrapper has a stable pointer to a SecureSession while being swappable with
* another instance of it.
*
*/
struct SortableSession
{
public:
void swap(SortableSession & other)
{
SortableSession tmp(other);
other.mSession = mSession;
mSession = tmp.mSession;
}
const Transport::SecureSession * operator->() const { return mSession; }
auto GetNumMatchingOnFabric() { return mNumMatchingOnFabric; }
auto GetNumMatchingOnPeer() { return mNumMatchingOnPeer; }
private:
SecureSession * mSession;
uint16_t mNumMatchingOnFabric;
uint16_t mNumMatchingOnPeer;
static_assert(CHIP_CONFIG_SECURE_SESSION_POOL_SIZE <= std::numeric_limits<decltype(mNumMatchingOnFabric)>::max(),
"mNumMatchingOnFabric must be able to count up to CHIP_CONFIG_SECURE_SESSION_POOL_SIZE!");
static_assert(CHIP_CONFIG_SECURE_SESSION_POOL_SIZE <= std::numeric_limits<decltype(mNumMatchingOnPeer)>::max(),
"mNumMatchingOnPeer must be able to count up to CHIP_CONFIG_SECURE_SESSION_POOL_SIZE!");
friend class SecureSessionTable;
};
/**
*
* Encapsulates all the necessary context for an eviction policy callback
* to implement its specific policy. The context is provided to the callee
* with the expectation that it'll call Sort() with a comparator function provided
* to get the list of sessions sorted in the desired order.
*
*/
class EvictionPolicyContext
{
public:
/*
* Called by the policy implementor to sort the list of sessions given a comparator
* function. The provided function shall have the following signature:
*
* bool CompareFunc(const SortableSession &a, const SortableSession &b);
*
* If a is a better candidate than b, true should be returned. Else, return false.
*
* NOTE: Sort() can be called multiple times.
*
*/
template <typename CompareFunc>
void Sort(CompareFunc func)
{
Sorting::InsertionSort(mSessionList.begin(), mSessionList.size(), func);
}
const ScopedNodeId & GetSessionEvictionHint() const { return mSessionEvictionHint; }
private:
EvictionPolicyContext(Span<SortableSession> sessionList, ScopedNodeId sessionEvictionHint)
{
mSessionList = sessionList;
mSessionEvictionHint = sessionEvictionHint;
}
friend class SecureSessionTable;
Span<SortableSession> mSessionList;
ScopedNodeId mSessionEvictionHint;
};
/**
*
* This implements an eviction policy by sorting sessions using the following sorting keys and selecting
* the session that is most ahead as the best candidate for eviction:
*
* - Key1: Sessions on fabrics that have more sessions in the table are placed ahead of sessions on fabrics
* with fewer sessions. We conclusively know that if a particular fabric has more sessions in the table
* than another, then that fabric is definitely over minimas (assuming a minimally sized session table
* conformant to spec minimas).
*
* Key2: Sessions that match the eviction hint's fabric are placed ahead of those that don't. This ensures that
* if Key1 is even (i.e two fabrics are tied in count), that you attempt to select sessions that match
* the eviction hint's fabric to ensure we evict sessions within the fabric that a new session might be about
* to be created within. This is essential to preventing cross-fabric denial of service possibilities.
*
* Key3: Sessions with a higher mNumMatchingOnPeer are placed ahead of those with a lower one. This ensures
* we pick sessions that have a higher number of duplicated sessions to a peer over those with lower since
* evicting a duplicated session will have less of an impact to that peer.
*
* Key4: Sessions whose target peer's ScopedNodeId matches the eviction hint are placed ahead of those who don't. This
* ensures that all things equal, a session that already exists to the peer is refreshed ahead of another to another peer.
*
* Key5: Sessions that are in defunct state are placed ahead of those in the active state, ahead of any other state.
* This ensures that we prioritize evicting defunct sessions (since they have been deemed non-functional anyways)
* over active, healthy ones, over those are currently in the process of establishment.
*
* Key6: Sessions that have a less recent activity time are placed ahead of those with a more recent activity time. This
* is the canonical sorting criteria for basic LRU.
*
*/
void DefaultEvictionPolicy(EvictionPolicyContext & evictionContext);
/**
*
* Evicts a session from the session table using the DefaultEvictionPolicy implementation.
*
*/
SecureSession * EvictAndAllocate(uint16_t localSessionId, SecureSession::Type secureSessionType,
const ScopedNodeId & sessionEvictionHint);
/**
* Find an available session ID that is unused in the secure session table.
*
* The search algorithm iterates over the session ID space in the outer loop
* and the session table in the inner loop to locate an available session ID
* from the starting mNextSessionId clue.
*
* The outer-loop considers 64 session IDs in each iteration to give a
* runtime complexity of O(CHIP_CONFIG_PEER_CONNECTION_POOL_SIZE^2/64). Speed up could be
* achieved with a sorted session table or additional storage.
*
* @return an unused session ID if any is found, else NullOptional
*/
CHECK_RETURN_VALUE
Optional<uint16_t> FindUnusedSessionId();
bool mRunningEvictionLogic = false;
ObjectPool<SecureSession, CHIP_CONFIG_SECURE_SESSION_POOL_SIZE> mEntries;
size_t GetMaxSessionTableSize() const
{
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
return mMaxSessionTableSize;
#else
return CHIP_CONFIG_SECURE_SESSION_POOL_SIZE;
#endif
}
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
size_t mMaxSessionTableSize = CHIP_CONFIG_SECURE_SESSION_POOL_SIZE;
void SetMaxSessionTableSize(size_t size) { mMaxSessionTableSize = size; }
#endif
uint16_t mNextSessionId = 0;
};
} // namespace Transport
} // namespace chip