| /* |
| * |
| * 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::BubbleSort(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 |