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 419 : ~SecureSessionTable() { mEntries.ReleaseAll(); }
43 :
44 370 : 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 135111 : void ReleaseSession(SecureSession * session) { mEntries.ReleaseObject(session); }
80 :
81 : template <typename Function>
82 896 : Loop ForEachSession(Function && function)
83 : {
84 896 : 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 198 : 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::InsertionSort(mSessionList.begin(), mSessionList.size(), func);
187 13 : }
188 :
189 520 : 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 133763 : size_t GetMaxSessionTableSize() const
263 : {
264 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
265 133763 : 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
|