Line data Source code
1 : /* 2 : * 3 : * Copyright (c) 2020-2021 Project CHIP Authors 4 : * 5 : * Licensed under the Apache License, Version 2.0 (the "License"); 6 : * you may not use this file except in compliance with the License. 7 : * You may obtain a copy of the License at 8 : * 9 : * http://www.apache.org/licenses/LICENSE-2.0 10 : * 11 : * Unless required by applicable law or agreed to in writing, software 12 : * distributed under the License is distributed on an "AS IS" BASIS, 13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 : * See the License for the specific language governing permissions and 15 : * limitations under the License. 16 : */ 17 : #pragma once 18 : 19 : #include <lib/core/CHIPError.h> 20 : #include <lib/support/CodeUtils.h> 21 : #include <lib/support/Pool.h> 22 : #include <lib/support/SortUtils.h> 23 : #include <system/TimeSource.h> 24 : #include <transport/SecureSession.h> 25 : 26 : namespace chip { 27 : namespace Transport { 28 : 29 : inline constexpr uint16_t kMaxSessionID = UINT16_MAX; 30 : inline constexpr uint16_t kUnsecuredSessionId = 0; 31 : 32 : /** 33 : * Handles a set of sessions. 34 : * 35 : * Intended for: 36 : * - handle session active time and expiration 37 : * - allocate and free space for sessions. 38 : */ 39 : class SecureSessionTable 40 : { 41 : public: 42 101 : ~SecureSessionTable() { mEntries.ReleaseAll(); } 43 : 44 334 : void Init() { mNextSessionId = chip::Crypto::GetRandU16(); } 45 : 46 : /** 47 : * Allocate a new secure session out of the internal resource pool. 48 : * 49 : * @param secureSessionType secure session type 50 : * @param localSessionId unique identifier for the local node's secure unicast session context 51 : * @param localNodeId represents the local Node ID for this node 52 : * @param peerNodeId represents peer Node's ID 53 : * @param peerCATs represents peer CASE Authenticated Tags 54 : * @param peerSessionId represents the encryption key ID assigned by peer node 55 : * @param fabricIndex represents fabric index for the session 56 : * @param config represents the reliable message protocol configuration 57 : * 58 : * @note the newly created state will have an 'active' time set based on the current time source. 59 : * 60 : * @returns CHIP_NO_ERROR if state could be initialized. May fail if maximum session count 61 : * has been reached (with CHIP_ERROR_NO_MEMORY). 62 : */ 63 : CHECK_RETURN_VALUE 64 : Optional<SessionHandle> CreateNewSecureSessionForTest(SecureSession::Type secureSessionType, uint16_t localSessionId, 65 : NodeId localNodeId, NodeId peerNodeId, CATValues peerCATs, 66 : uint16_t peerSessionId, FabricIndex fabricIndex, 67 : const ReliableMessageProtocolConfig & config); 68 : /** 69 : * Allocate a new secure session out of the internal resource pool with a 70 : * non-colliding session ID and increments mNextSessionId to give a clue to 71 : * the allocator for the next allocation. The secure session session will 72 : * not become active until the call to SecureSession::Activate. 73 : * 74 : * @returns allocated session, or NullOptional on failure 75 : */ 76 : CHECK_RETURN_VALUE 77 : Optional<SessionHandle> CreateNewSecureSession(SecureSession::Type secureSessionType, ScopedNodeId sessionEvictionHint); 78 : 79 134968 : void ReleaseSession(SecureSession * session) { mEntries.ReleaseObject(session); } 80 : 81 : template <typename Function> 82 541 : Loop ForEachSession(Function && function) 83 : { 84 541 : return mEntries.ForEachActiveObject(std::forward<Function>(function)); 85 : } 86 : 87 : /** 88 : * Get a secure session given its session ID. 89 : * 90 : * @param localSessionId the identifier of a secure unicast session context within the local node 91 : * 92 : * @return the session if found, NullOptional if not found 93 : */ 94 : CHECK_RETURN_VALUE 95 : Optional<SessionHandle> FindSecureSessionByLocalKey(uint16_t localSessionId); 96 : 97 : // Select SessionHolders which are pointing to a session with the same peer as the given session. Shift them to the given 98 : // session. 99 : // This is an internal API, using raw pointer to a session is allowed here. 100 100 : void NewerSessionAvailable(SecureSession * session) 101 : { 102 100 : VerifyOrDie(session->GetSecureSessionType() == SecureSession::Type::kCASE); 103 100 : mEntries.ForEachActiveObject([&](SecureSession * oldSession) { 104 393 : if (session == oldSession) 105 100 : return Loop::Continue; 106 : 107 293 : SessionHandle ref(*oldSession); 108 : 109 : // This will give all SessionHolders pointing to oldSession a chance to switch to the provided session 110 : // 111 : // See documentation for SessionDelegate::GetNewSessionHandlingPolicy about how session auto-shifting works, and how 112 : // to disable it for a specific SessionHolder in a specific scenario. 113 346 : if (oldSession->GetSecureSessionType() == SecureSession::Type::kCASE && oldSession->GetPeer() == session->GetPeer() && 114 53 : oldSession->GetPeerCATs() == session->GetPeerCATs()) 115 : { 116 53 : oldSession->NewerSessionAvailable(SessionHandle(*session)); 117 : } 118 : 119 293 : return Loop::Continue; 120 293 : }); 121 100 : } 122 : 123 : private: 124 : friend class TestSecureSessionTable; 125 : 126 : /** 127 : * This provides a sortable wrapper for a SecureSession object. A SecureSession 128 : * isn't directly sortable since it is not swappable (i.e meet criteria for ValueSwappable). 129 : * 130 : * However, this wrapper has a stable pointer to a SecureSession while being swappable with 131 : * another instance of it. 132 : * 133 : */ 134 : struct SortableSession 135 : { 136 : public: 137 : void swap(SortableSession & other) 138 : { 139 : SortableSession tmp(other); 140 : other.mSession = mSession; 141 : mSession = tmp.mSession; 142 : } 143 : 144 290 : const Transport::SecureSession * operator->() const { return mSession; } 145 : auto GetNumMatchingOnFabric() { return mNumMatchingOnFabric; } 146 : auto GetNumMatchingOnPeer() { return mNumMatchingOnPeer; } 147 : 148 : private: 149 : SecureSession * mSession; 150 : uint16_t mNumMatchingOnFabric; 151 : uint16_t mNumMatchingOnPeer; 152 : 153 : static_assert(CHIP_CONFIG_SECURE_SESSION_POOL_SIZE <= std::numeric_limits<decltype(mNumMatchingOnFabric)>::max(), 154 : "mNumMatchingOnFabric must be able to count up to CHIP_CONFIG_SECURE_SESSION_POOL_SIZE!"); 155 : static_assert(CHIP_CONFIG_SECURE_SESSION_POOL_SIZE <= std::numeric_limits<decltype(mNumMatchingOnPeer)>::max(), 156 : "mNumMatchingOnPeer must be able to count up to CHIP_CONFIG_SECURE_SESSION_POOL_SIZE!"); 157 : 158 : friend class SecureSessionTable; 159 : }; 160 : 161 : /** 162 : * 163 : * Encapsulates all the necessary context for an eviction policy callback 164 : * to implement its specific policy. The context is provided to the callee 165 : * with the expectation that it'll call Sort() with a comparator function provided 166 : * to get the list of sessions sorted in the desired order. 167 : * 168 : */ 169 : class EvictionPolicyContext 170 : { 171 : public: 172 : /* 173 : * Called by the policy implementor to sort the list of sessions given a comparator 174 : * function. The provided function shall have the following signature: 175 : * 176 : * bool CompareFunc(const SortableSession &a, const SortableSession &b); 177 : * 178 : * If a is a better candidate than b, true should be returned. Else, return false. 179 : * 180 : * NOTE: Sort() can be called multiple times. 181 : * 182 : */ 183 : template <typename CompareFunc> 184 13 : void Sort(CompareFunc func) 185 : { 186 13 : Sorting::BubbleSort(mSessionList.begin(), mSessionList.size(), func); 187 13 : } 188 : 189 786 : const ScopedNodeId & GetSessionEvictionHint() const { return mSessionEvictionHint; } 190 : 191 : private: 192 13 : EvictionPolicyContext(Span<SortableSession> sessionList, ScopedNodeId sessionEvictionHint) 193 13 : { 194 13 : mSessionList = sessionList; 195 13 : mSessionEvictionHint = sessionEvictionHint; 196 13 : } 197 : 198 : friend class SecureSessionTable; 199 : Span<SortableSession> mSessionList; 200 : ScopedNodeId mSessionEvictionHint; 201 : }; 202 : 203 : /** 204 : * 205 : * This implements an eviction policy by sorting sessions using the following sorting keys and selecting 206 : * the session that is most ahead as the best candidate for eviction: 207 : * 208 : * - Key1: Sessions on fabrics that have more sessions in the table are placed ahead of sessions on fabrics 209 : * with fewer sessions. We conclusively know that if a particular fabric has more sessions in the table 210 : * than another, then that fabric is definitely over minimas (assuming a minimally sized session table 211 : * conformant to spec minimas). 212 : * 213 : * Key2: Sessions that match the eviction hint's fabric are placed ahead of those that don't. This ensures that 214 : * if Key1 is even (i.e two fabrics are tied in count), that you attempt to select sessions that match 215 : * the eviction hint's fabric to ensure we evict sessions within the fabric that a new session might be about 216 : * to be created within. This is essential to preventing cross-fabric denial of service possibilities. 217 : * 218 : * Key3: Sessions with a higher mNumMatchingOnPeer are placed ahead of those with a lower one. This ensures 219 : * we pick sessions that have a higher number of duplicated sessions to a peer over those with lower since 220 : * evicting a duplicated session will have less of an impact to that peer. 221 : * 222 : * Key4: Sessions whose target peer's ScopedNodeId matches the eviction hint are placed ahead of those who don't. This 223 : * ensures that all things equal, a session that already exists to the peer is refreshed ahead of another to another peer. 224 : * 225 : * Key5: Sessions that are in defunct state are placed ahead of those in the active state, ahead of any other state. 226 : * This ensures that we prioritize evicting defunct sessions (since they have been deemed non-functional anyways) 227 : * over active, healthy ones, over those are currently in the process of establishment. 228 : * 229 : * Key6: Sessions that have a less recent activity time are placed ahead of those with a more recent activity time. This 230 : * is the canonical sorting criteria for basic LRU. 231 : * 232 : */ 233 : void DefaultEvictionPolicy(EvictionPolicyContext & evictionContext); 234 : 235 : /** 236 : * 237 : * Evicts a session from the session table using the DefaultEvictionPolicy implementation. 238 : * 239 : */ 240 : SecureSession * EvictAndAllocate(uint16_t localSessionId, SecureSession::Type secureSessionType, 241 : const ScopedNodeId & sessionEvictionHint); 242 : 243 : /** 244 : * Find an available session ID that is unused in the secure session table. 245 : * 246 : * The search algorithm iterates over the session ID space in the outer loop 247 : * and the session table in the inner loop to locate an available session ID 248 : * from the starting mNextSessionId clue. 249 : * 250 : * The outer-loop considers 64 session IDs in each iteration to give a 251 : * runtime complexity of O(CHIP_CONFIG_PEER_CONNECTION_POOL_SIZE^2/64). Speed up could be 252 : * achieved with a sorted session table or additional storage. 253 : * 254 : * @return an unused session ID if any is found, else NullOptional 255 : */ 256 : CHECK_RETURN_VALUE 257 : Optional<uint16_t> FindUnusedSessionId(); 258 : 259 : bool mRunningEvictionLogic = false; 260 : ObjectPool<SecureSession, CHIP_CONFIG_SECURE_SESSION_POOL_SIZE> mEntries; 261 : 262 133766 : size_t GetMaxSessionTableSize() const 263 : { 264 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST 265 133766 : return mMaxSessionTableSize; 266 : #else 267 : return CHIP_CONFIG_SECURE_SESSION_POOL_SIZE; 268 : #endif 269 : } 270 : 271 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST 272 : size_t mMaxSessionTableSize = CHIP_CONFIG_SECURE_SESSION_POOL_SIZE; 273 : void SetMaxSessionTableSize(size_t size) { mMaxSessionTableSize = size; } 274 : #endif 275 : 276 : uint16_t mNextSessionId = 0; 277 : }; 278 : 279 : } // namespace Transport 280 : } // namespace chip