forked from project-chip/connectedhomeip
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSecureSessionTable.cpp
407 lines (359 loc) · 16.2 KB
/
SecureSessionTable.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
* Copyright (c) 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.
*/
#include "lib/support/CHIPMem.h"
#include "lib/support/CodeUtils.h"
#include "lib/support/ScopedBuffer.h"
#include <access/AuthMode.h>
#include <lib/support/Defer.h>
#include <transport/SecureSession.h>
#include <transport/SecureSessionTable.h>
namespace chip {
namespace Transport {
Optional<SessionHandle> SecureSessionTable::CreateNewSecureSessionForTest(SecureSession::Type secureSessionType,
uint16_t localSessionId, NodeId localNodeId,
NodeId peerNodeId, CATValues peerCATs,
uint16_t peerSessionId, FabricIndex fabricIndex,
const ReliableMessageProtocolConfig & config)
{
if (secureSessionType == SecureSession::Type::kCASE)
{
if ((fabricIndex == kUndefinedFabricIndex) || (localNodeId == kUndefinedNodeId) || (peerNodeId == kUndefinedNodeId))
{
return Optional<SessionHandle>::Missing();
}
}
else if (secureSessionType == SecureSession::Type::kPASE)
{
if ((fabricIndex != kUndefinedFabricIndex) || (localNodeId != kUndefinedNodeId) || (peerNodeId != kUndefinedNodeId))
{
// TODO: This secure session type is infeasible! We must fix the tests
if (false)
{
return Optional<SessionHandle>::Missing();
}
(void) fabricIndex;
}
}
SecureSession * result = mEntries.CreateObject(*this, secureSessionType, localSessionId, localNodeId, peerNodeId, peerCATs,
peerSessionId, fabricIndex, config);
return result != nullptr ? MakeOptional<SessionHandle>(*result) : Optional<SessionHandle>::Missing();
}
Optional<SessionHandle> SecureSessionTable::CreateNewSecureSession(SecureSession::Type secureSessionType,
ScopedNodeId sessionEvictionHint)
{
Optional<SessionHandle> rv = Optional<SessionHandle>::Missing();
SecureSession * allocated = nullptr;
auto sessionId = FindUnusedSessionId();
VerifyOrReturnValue(sessionId.HasValue(), Optional<SessionHandle>::Missing());
//
// We allocate a new session out of the pool if we have space in it. If we don't, we need
// to run the eviction algorithm to get a free slot. We shall ALWAYS be guaranteed to evict
// an existing session in the table in normal operating circumstances.
//
if (mEntries.Allocated() < GetMaxSessionTableSize())
{
allocated = mEntries.CreateObject(*this, secureSessionType, sessionId.Value());
}
else
{
allocated = EvictAndAllocate(sessionId.Value(), secureSessionType, sessionEvictionHint);
}
VerifyOrReturnValue(allocated != nullptr, Optional<SessionHandle>::Missing());
rv = MakeOptional<SessionHandle>(*allocated);
mNextSessionId = sessionId.Value() == kMaxSessionID ? static_cast<uint16_t>(kUnsecuredSessionId + 1)
: static_cast<uint16_t>(sessionId.Value() + 1);
return rv;
}
SecureSession * SecureSessionTable::EvictAndAllocate(uint16_t localSessionId, SecureSession::Type secureSessionType,
const ScopedNodeId & sessionEvictionHint)
{
VerifyOrDieWithMsg(!mRunningEvictionLogic, SecureChannel,
"EvictAndAllocate isn't re-entrant, yet someone called us while we're already running");
mRunningEvictionLogic = true;
auto cleanup = MakeDefer([this]() { mRunningEvictionLogic = false; });
ChipLogProgress(SecureChannel, "Evicting a slot for session with LSID: %d, type: %u", localSessionId,
(uint8_t) secureSessionType);
VerifyOrDie(mEntries.Allocated() <= GetMaxSessionTableSize());
//
// Create a temporary list of objects each of which points to a session in the existing
// session table, but are swappable. This allows them to then be used with a sorting algorithm
// without affecting the sessions in the table itself.
//
// The size of this shouldn't place significant demands on the stack if using the default
// configuration for CHIP_CONFIG_SECURE_SESSION_POOL_SIZE (17). Each item is
// 8 bytes in size (on a 32-bit platform), and 16 bytes in size (on a 64-bit platform,
// including padding).
//
// Total size of this stack variable = 17 * 8 = 136bytes (32-bit platform), 272 bytes (64-bit platform).
//
// Even if the define is set to a large value, it's likely not so bad on the sort of platform setup
// that would have that sort of pool size.
//
// We need to sort (as opposed to just a linear search for the smallest/largest item)
// since it is possible that the candidate selected for eviction may not actually be
// released once marked for expiration (see comments below for more details).
//
// Consequently, we may need to walk the candidate list till we find one that is.
// Sorting provides a better overall performance model in this scheme.
//
// (#19967): Investigate doing linear search instead.
//
//
SortableSession sortableSessions[CHIP_CONFIG_SECURE_SESSION_POOL_SIZE];
unsigned int index = 0;
//
// Compute two key stats for each session - the number of other sessions that
// match its fabric, as well as the number of other sessions that match its peer.
//
// This will be used by the session eviction algorithm later.
//
ForEachSession([&index, &sortableSessions, this](auto * session) {
sortableSessions[index].mSession = session;
sortableSessions[index].mNumMatchingOnFabric = 0;
sortableSessions[index].mNumMatchingOnPeer = 0;
ForEachSession([session, index, &sortableSessions](auto * otherSession) {
if (session != otherSession)
{
if (session->GetFabricIndex() == otherSession->GetFabricIndex())
{
sortableSessions[index].mNumMatchingOnFabric++;
if (session->GetPeerNodeId() == otherSession->GetPeerNodeId())
{
sortableSessions[index].mNumMatchingOnPeer++;
}
}
}
return Loop::Continue;
});
index++;
return Loop::Continue;
});
auto sortableSessionSpan = Span<SortableSession>(sortableSessions, mEntries.Allocated());
EvictionPolicyContext policyContext(sortableSessionSpan, sessionEvictionHint);
DefaultEvictionPolicy(policyContext);
ChipLogProgress(SecureChannel, "Sorted sessions for eviction...");
const auto numSessions = mEntries.Allocated();
#if CHIP_DETAIL_LOGGING
ChipLogDetail(SecureChannel, "Sorted Eviction Candidates (ranked from best candidate to worst):");
for (auto * session = sortableSessions; session != (sortableSessions + numSessions); session++)
{
ChipLogDetail(SecureChannel,
"\t%ld: [%p] -- Peer: [%u:" ChipLogFormatX64
"] State: '%s', NumMatchingOnFabric: %d NumMatchingOnPeer: %d ActivityTime: %lu",
static_cast<long int>(session - sortableSessions), session->mSession,
session->mSession->GetPeer().GetFabricIndex(), ChipLogValueX64(session->mSession->GetPeer().GetNodeId()),
session->mSession->GetStateStr(), session->mNumMatchingOnFabric, session->mNumMatchingOnPeer,
static_cast<unsigned long>(session->mSession->GetLastActivityTime().count()));
}
#endif
for (auto * session = sortableSessions; session != (sortableSessions + numSessions); session++)
{
if (session->mSession->IsPendingEviction())
{
continue;
}
ChipLogProgress(SecureChannel, "Candidate Session[%p] - Attempting to evict...", session->mSession);
auto prevCount = mEntries.Allocated();
//
// SessionHolders act like weak-refs on a session, but since they do still add to the ref-count of a SecureSession, we
// cannot actually tell whether there are truly any strong-refs (SessionHandles) on this session because if we did, we'd
// avoid evicting it since it's pointless to do so.
//
// However, we don't actually have SessionHolders implemented correctly as weak-refs, requiring us to go ahead and 'try' to
// evict it, and see if it still remains in the table. If it does, we have to try the next one. If it doesn't, we know we've
// earned a free spot.
//
// See #19495.
//
session->mSession->MarkForEviction();
auto newCount = mEntries.Allocated();
if (newCount < prevCount)
{
ChipLogProgress(SecureChannel, "Successfully evicted a session!");
auto * retSession = mEntries.CreateObject(*this, secureSessionType, localSessionId);
VerifyOrDie(session != nullptr);
return retSession;
}
}
VerifyOrDieWithMsg(false, SecureChannel, "We couldn't find any session to evict at all, something's wrong!");
return nullptr;
}
void SecureSessionTable::DefaultEvictionPolicy(EvictionPolicyContext & evictionContext)
{
//
// This implements a spec-compliant sorting policy that ensures both guarantees for sessions per-fabric as
// mandated by the spec as well as fairness in terms of selecting the most appropriate session to evict
// based on multiple criteria.
//
// See the description of this function in the header for more details on each sorting key below.
//
evictionContext.Sort([&evictionContext](const SortableSession & a, const SortableSession & b) -> bool {
//
// Sorting on Key1
//
if (a.mNumMatchingOnFabric != b.mNumMatchingOnFabric)
{
return a.mNumMatchingOnFabric > b.mNumMatchingOnFabric;
}
bool doesAMatchSessionHintFabric =
a.mSession->GetPeer().GetFabricIndex() == evictionContext.GetSessionEvictionHint().GetFabricIndex();
bool doesBMatchSessionHintFabric =
b.mSession->GetPeer().GetFabricIndex() == evictionContext.GetSessionEvictionHint().GetFabricIndex();
//
// Sorting on Key2
//
if (doesAMatchSessionHintFabric != doesBMatchSessionHintFabric)
{
return doesAMatchSessionHintFabric > doesBMatchSessionHintFabric;
}
//
// Sorting on Key3
//
if (a.mNumMatchingOnPeer != b.mNumMatchingOnPeer)
{
return a.mNumMatchingOnPeer > b.mNumMatchingOnPeer;
}
// We have an evicton hint in two cases:
//
// 1) When we just established CASE as a responder, the hint is the node
// we just established CASE to.
// 2) When starting to establish CASE as an initiator, the hint is the
// node we are going to establish CASE to.
//
// In case 2, we should not end up here if there is an active session to
// the peer at all (because that session should have been used instead
// of establishing a new one).
//
// In case 1, we know we have a session matching the hint, but we don't
// want to pick that one for eviction, because we just established it.
// So we should not consider a session as matching a hint if it's active
// and is the only session to our peer.
//
// Checking for the "active" state in addition to the "only session to
// peer" state allows us to prioritize evicting defuct sessions that
// match the hint against other defunct sessions.
auto sessionMatchesEvictionHint = [&evictionContext](const SortableSession & session) -> int {
if (session.mSession->GetPeer() != evictionContext.GetSessionEvictionHint())
{
return false;
}
bool isOnlyActiveSessionToPeer = session.mSession->IsActiveSession() && session.mNumMatchingOnPeer == 0;
return !isOnlyActiveSessionToPeer;
};
int doesAMatchSessionHint = sessionMatchesEvictionHint(a);
int doesBMatchSessionHint = sessionMatchesEvictionHint(b);
//
// Sorting on Key4
//
if (doesAMatchSessionHint != doesBMatchSessionHint)
{
return doesAMatchSessionHint > doesBMatchSessionHint;
}
int aStateScore = 0, bStateScore = 0;
auto assignStateScore = [](auto & score, const auto & session) {
if (session.IsDefunct())
{
score = 2;
}
else if (session.IsActiveSession())
{
score = 1;
}
else
{
score = 0;
}
};
assignStateScore(aStateScore, *a.mSession);
assignStateScore(bStateScore, *b.mSession);
//
// Sorting on Key5
//
if (aStateScore != bStateScore)
{
return (aStateScore > bStateScore);
}
//
// Sorting on Key6
//
return (a->GetLastActivityTime() < b->GetLastActivityTime());
});
}
Optional<SessionHandle> SecureSessionTable::FindSecureSessionByLocalKey(uint16_t localSessionId)
{
SecureSession * result = nullptr;
mEntries.ForEachActiveObject([&](auto session) {
if (session->GetLocalSessionId() == localSessionId)
{
result = session;
return Loop::Break;
}
return Loop::Continue;
});
return result != nullptr ? MakeOptional<SessionHandle>(*result) : Optional<SessionHandle>::Missing();
}
Optional<uint16_t> SecureSessionTable::FindUnusedSessionId()
{
uint16_t candidate_base = 0;
uint64_t candidate_mask = 0;
for (uint32_t i = 0; i <= kMaxSessionID; i += 64)
{
// candidate_base is the base session ID we are searching from.
// We have a 64-bit mask anchored at this ID and iterate over the
// whole session table, setting bits in the mask for in-use IDs.
// If we can iterate through the entire session table and have
// any bits clear in the mask, we have available session IDs.
candidate_base = static_cast<uint16_t>(i + mNextSessionId);
candidate_mask = 0;
{
uint16_t shift = static_cast<uint16_t>(kUnsecuredSessionId - candidate_base);
if (shift <= 63)
{
candidate_mask |= (1ULL << shift); // kUnsecuredSessionId is never available
}
}
mEntries.ForEachActiveObject([&](auto session) {
uint16_t shift = static_cast<uint16_t>(session->GetLocalSessionId() - candidate_base);
if (shift <= 63)
{
candidate_mask |= (1ULL << shift);
}
if (candidate_mask == UINT64_MAX)
{
return Loop::Break; // No bits clear means this bucket is full.
}
return Loop::Continue;
});
if (candidate_mask != UINT64_MAX)
{
break; // Any bit clear means we have an available ID in this bucket.
}
}
if (candidate_mask != UINT64_MAX)
{
uint16_t offset = 0;
while (candidate_mask & 1)
{
candidate_mask >>= 1;
++offset;
}
uint16_t available = static_cast<uint16_t>(candidate_base + offset);
return MakeOptional<uint16_t>(available);
}
return NullOptional;
}
} // namespace Transport
} // namespace chip