LCOV - code coverage report
Current view: top level - transport - SecureSessionTable.cpp (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 127 132 96.2 %
Date: 2024-02-15 08:20:41 Functions: 14 14 100.0 %

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

Generated by: LCOV version 1.14