LCOV - code coverage report
Current view: top level - transport - PeerMessageCounter.h (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 101 137 73.7 %
Date: 2024-02-15 08:20:41 Functions: 19 24 79.2 %

          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             : /**
      18             :  *    @file
      19             :  *      This file defines the CHIP message counters of remote nodes.
      20             :  *
      21             :  */
      22             : #pragma once
      23             : 
      24             : #include <array>
      25             : #include <bitset>
      26             : 
      27             : #include <lib/support/Span.h>
      28             : 
      29             : namespace chip {
      30             : namespace Transport {
      31             : 
      32             : class PeerMessageCounter
      33             : {
      34             : public:
      35             :     static constexpr size_t kChallengeSize      = 8;
      36             :     static constexpr uint32_t kInitialSyncValue = 0;
      37             : 
      38      151214 :     PeerMessageCounter() : mStatus(Status::NotSynced) {}
      39      151214 :     ~PeerMessageCounter() { Reset(); }
      40             : 
      41       18442 :     void Reset()
      42             :     {
      43       18442 :         switch (mStatus)
      44             :         {
      45       17323 :         case Status::NotSynced:
      46       17323 :             break;
      47           0 :         case Status::SyncInProcess:
      48           0 :             mSyncInProcess.~SyncInProcess();
      49           0 :             break;
      50        1119 :         case Status::Synced:
      51        1119 :             mSynced.~Synced();
      52        1119 :             break;
      53             :         }
      54       18442 :         mStatus = Status::NotSynced;
      55       18442 :     }
      56             : 
      57           0 :     bool IsSynchronizing() { return mStatus == Status::SyncInProcess; }
      58           0 :     bool IsSynchronized() { return mStatus == Status::Synced; }
      59             : 
      60           0 :     void SyncStarting(FixedByteSpan<kChallengeSize> challenge)
      61             :     {
      62           0 :         VerifyOrDie(mStatus == Status::NotSynced);
      63           0 :         mStatus = Status::SyncInProcess;
      64           0 :         new (&mSyncInProcess) SyncInProcess();
      65           0 :         ::memcpy(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize);
      66           0 :     }
      67             : 
      68           0 :     void SyncFailed() { Reset(); }
      69             : 
      70           0 :     CHIP_ERROR VerifyChallenge(uint32_t counter, FixedByteSpan<kChallengeSize> challenge)
      71             :     {
      72           0 :         if (mStatus != Status::SyncInProcess)
      73             :         {
      74           0 :             return CHIP_ERROR_INCORRECT_STATE;
      75             :         }
      76           0 :         if (::memcmp(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize) != 0)
      77             :         {
      78           0 :             return CHIP_ERROR_INVALID_ARGUMENT;
      79             :         }
      80             : 
      81           0 :         mSyncInProcess.~SyncInProcess();
      82           0 :         mStatus = Status::Synced;
      83           0 :         new (&mSynced) Synced();
      84           0 :         mSynced.mMaxCounter = counter;
      85           0 :         mSynced.mWindow.reset(); // reset all bits, accept all packets in the window
      86           0 :         return CHIP_NO_ERROR;
      87             :     }
      88             : 
      89             :     /**
      90             :      * @brief Implementation of spec 4.5.4.2
      91             :      *
      92             :      * For encrypted messages of Group Session Type, any arriving message with a counter in the range
      93             :      * [(max_message_counter + 1) to (max_message_counter + 2^31 - 1)] (modulo 2^32) SHALL be considered
      94             :      * new, and cause the max_message_counter value to be updated. Messages with counters from
      95             :      * [(max_message_counter - 2^31) to (max_message_counter - MSG_COUNTER_WINDOW_SIZE - 1)] (modulo 2^
      96             :      * 32) SHALL be considered duplicate. Message counters within the range of the bitmap SHALL be
      97             :      * considered duplicate if the corresponding bit offset is set to true.
      98             :      *
      99             :      */
     100           3 :     CHIP_ERROR VerifyGroup(uint32_t counter) const
     101             :     {
     102           3 :         if (mStatus != Status::Synced)
     103             :         {
     104           0 :             return CHIP_ERROR_INCORRECT_STATE;
     105             :         }
     106             : 
     107           3 :         Position pos = ClassifyWithRollover(counter);
     108           3 :         return VerifyPositionEncrypted(pos, counter);
     109             :     }
     110             : 
     111           4 :     CHIP_ERROR VerifyOrTrustFirstGroup(uint32_t counter)
     112             :     {
     113           4 :         switch (mStatus)
     114             :         {
     115           1 :         case Status::NotSynced: {
     116             :             // Trust and set the counter when not synced
     117           1 :             SetCounter(counter);
     118           1 :             return CHIP_NO_ERROR;
     119             :         }
     120           3 :         case Status::Synced: {
     121           3 :             return VerifyGroup(counter);
     122             :         }
     123           0 :         default:
     124           0 :             VerifyOrDie(false);
     125             :             return CHIP_ERROR_INTERNAL;
     126             :         }
     127             :     }
     128             : 
     129             :     /**
     130             :      * @brief
     131             :      *    With the group counter verified and the packet MIC also verified by the secure key, we can trust the packet and adjust
     132             :      *    counter states.
     133             :      *
     134             :      * @pre counter has been verified via VerifyGroup or VerifyOrTrustFirstGroup
     135             :      */
     136           3 :     void CommitGroup(uint32_t counter) { CommitWithRollover(counter); }
     137             : 
     138        9391 :     CHIP_ERROR VerifyEncryptedUnicast(uint32_t counter) const
     139             :     {
     140        9391 :         if (mStatus != Status::Synced)
     141             :         {
     142           0 :             return CHIP_ERROR_INCORRECT_STATE;
     143             :         }
     144             : 
     145        9391 :         Position pos = ClassifyWithoutRollover(counter);
     146        9391 :         return VerifyPositionEncrypted(pos, counter);
     147             :     }
     148             : 
     149             :     /**
     150             :      * @brief
     151             :      *    With the counter verified and the packet MIC also verified by the secure key, we can trust the packet and adjust
     152             :      *    counter states.
     153             :      *
     154             :      * @pre counter has been verified via VerifyEncryptedUnicast
     155             :      */
     156        9383 :     void CommitEncryptedUnicast(uint32_t counter) { CommitWithoutRollover(counter); }
     157             : 
     158          96 :     CHIP_ERROR VerifyUnencrypted(uint32_t counter)
     159             :     {
     160          96 :         switch (mStatus)
     161             :         {
     162          41 :         case Status::NotSynced: {
     163             :             // Trust and set the counter when not synced
     164          41 :             SetCounter(counter);
     165          41 :             return CHIP_NO_ERROR;
     166             :         }
     167          55 :         case Status::Synced: {
     168          55 :             Position pos = ClassifyWithRollover(counter);
     169          55 :             return VerifyPositionUnencrypted(pos, counter);
     170             :         }
     171           0 :         default: {
     172           0 :             VerifyOrDie(false);
     173             :             return CHIP_ERROR_INTERNAL;
     174             :         }
     175             :         }
     176             :     }
     177             : 
     178             :     /**
     179             :      * @brief
     180             :      *    With the unencrypted counter verified we can trust the packet and adjust
     181             :      *    counter states.
     182             :      *
     183             :      * @pre counter has been verified via VerifyUnencrypted
     184             :      */
     185          96 :     void CommitUnencrypted(uint32_t counter) { CommitWithRollover(counter); }
     186             : 
     187        1337 :     void SetCounter(uint32_t value)
     188             :     {
     189        1337 :         Reset();
     190        1337 :         mStatus = Status::Synced;
     191        1337 :         new (&mSynced) Synced();
     192        1337 :         mSynced.mMaxCounter = value;
     193        1337 :         mSynced.mWindow.reset();
     194        1337 :     }
     195             : 
     196             :     uint32_t GetCounter() const { return mSynced.mMaxCounter; }
     197             : 
     198             : private:
     199             :     // Counter position indicator with respect to our current
     200             :     // mSynced.mMaxCounter.
     201             :     enum class Position
     202             :     {
     203             :         BeforeWindow,
     204             :         InWindow,
     205             :         MaxCounter,
     206             :         FutureCounter,
     207             :     };
     208             : 
     209             :     // Classify an incoming counter value's position.  Must be used only if
     210             :     // mStatus is Status::Synced.
     211       18774 :     Position ClassifyWithoutRollover(uint32_t counter) const
     212             :     {
     213       18774 :         if (counter > mSynced.mMaxCounter)
     214             :         {
     215       18764 :             return Position::FutureCounter;
     216             :         }
     217             : 
     218          10 :         return ClassifyNonFutureCounter(counter);
     219             :     }
     220             : 
     221             :     /**
     222             :      * Classify an incoming counter value's position for the cases when counters
     223             :      * are allowed to roll over.  Must be used only if mStatus is
     224             :      * Status::Synced.
     225             :      *
     226             :      * This can be used as the basis for implementing section 4.5.4.2 in the
     227             :      * spec:
     228             :      *
     229             :      * For encrypted messages of Group Session Type, any arriving message with a counter in the range
     230             :      * [(max_message_counter + 1) to (max_message_counter + 2^31 - 1)] (modulo 2^32) SHALL be considered
     231             :      * new, and cause the max_message_counter value to be updated. Messages with counters from
     232             :      * [(max_message_counter - 2^31) to (max_message_counter - MSG_COUNTER_WINDOW_SIZE - 1)] (modulo 2^
     233             :      * 32) SHALL be considered duplicate. Message counters within the range of the bitmap SHALL be
     234             :      * considered duplicate if the corresponding bit offset is set to true.
     235             :      */
     236         157 :     Position ClassifyWithRollover(uint32_t counter) const
     237             :     {
     238         157 :         uint32_t counterIncrease               = counter - mSynced.mMaxCounter;
     239         157 :         constexpr uint32_t futureCounterWindow = (static_cast<uint32_t>(1 << 31)) - 1;
     240             : 
     241         157 :         if (counterIncrease >= 1 && counterIncrease <= futureCounterWindow)
     242             :         {
     243         114 :             return Position::FutureCounter;
     244             :         }
     245             : 
     246          43 :         return ClassifyNonFutureCounter(counter);
     247             :     }
     248             : 
     249             :     /**
     250             :      * Classify a counter that's known to not be future counter.  This works
     251             :      * identically whether we are doing rollover or not.
     252             :      */
     253          53 :     Position ClassifyNonFutureCounter(uint32_t counter) const
     254             :     {
     255          53 :         if (counter == mSynced.mMaxCounter)
     256             :         {
     257          48 :             return Position::MaxCounter;
     258             :         }
     259             : 
     260           5 :         uint32_t offset = mSynced.mMaxCounter - counter;
     261           5 :         if (offset <= CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
     262             :         {
     263           3 :             return Position::InWindow;
     264             :         }
     265             : 
     266           2 :         return Position::BeforeWindow;
     267             :     }
     268             : 
     269             :     /**
     270             :      * Given an encrypted (group or unicast) counter position and the counter
     271             :      * value, verify whether we should accept it.
     272             :      */
     273        9394 :     CHIP_ERROR VerifyPositionEncrypted(Position position, uint32_t counter) const
     274             :     {
     275        9394 :         switch (position)
     276             :         {
     277        9384 :         case Position::FutureCounter:
     278        9384 :             return CHIP_NO_ERROR;
     279           2 :         case Position::InWindow: {
     280           2 :             uint32_t offset = mSynced.mMaxCounter - counter;
     281           2 :             if (mSynced.mWindow.test(offset - 1))
     282             :             {
     283           1 :                 return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
     284             :             }
     285           1 :             return CHIP_NO_ERROR;
     286             :         }
     287           8 :         default: {
     288             :             // Equal to max counter, or before window.
     289           8 :             return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
     290             :         }
     291             :         }
     292             :     }
     293             : 
     294             :     /**
     295             :      * Given an unencrypted counter position and value, verify whether we should
     296             :      * accept it.
     297             :      */
     298          55 :     CHIP_ERROR VerifyPositionUnencrypted(Position position, uint32_t counter) const
     299             :     {
     300          55 :         switch (position)
     301             :         {
     302           0 :         case Position::MaxCounter:
     303           0 :             return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
     304           0 :         case Position::InWindow: {
     305           0 :             uint32_t offset = mSynced.mMaxCounter - counter;
     306           0 :             if (mSynced.mWindow.test(offset - 1))
     307             :             {
     308           0 :                 return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED;
     309             :             }
     310           0 :             return CHIP_NO_ERROR;
     311             :         }
     312          55 :         default: {
     313             :             // Future counter or before window; all of these are accepted.  The
     314             :             // before-window case is accepted because the peer may have reset
     315             :             // and is using a new randomized initial value.
     316          55 :             return CHIP_NO_ERROR;
     317             :         }
     318             :         }
     319             :     }
     320             : 
     321          99 :     void CommitWithRollover(uint32_t counter)
     322             :     {
     323          99 :         Position pos = ClassifyWithRollover(counter);
     324          99 :         CommitWithPosition(pos, counter);
     325          99 :     }
     326             : 
     327        9383 :     void CommitWithoutRollover(uint32_t counter)
     328             :     {
     329        9383 :         Position pos = ClassifyWithoutRollover(counter);
     330        9383 :         CommitWithPosition(pos, counter);
     331        9383 :     }
     332             : 
     333             :     /**
     334             :      * Commit a counter value that is known to be at the given position with
     335             :      * respect to our max counter.
     336             :      */
     337        9482 :     void CommitWithPosition(Position position, uint32_t counter)
     338             :     {
     339        9482 :         switch (position)
     340             :         {
     341           1 :         case Position::InWindow: {
     342           1 :             uint32_t offset = mSynced.mMaxCounter - counter;
     343           1 :             mSynced.mWindow.set(offset - 1);
     344           1 :             break;
     345             :         }
     346          42 :         case Position::MaxCounter: {
     347             :             // Nothing to do
     348          42 :             break;
     349             :         }
     350        9439 :         default: {
     351             :             // Since we are committing, this becomes a new max-counter value.
     352        9439 :             uint32_t shift      = counter - mSynced.mMaxCounter;
     353        9439 :             mSynced.mMaxCounter = counter;
     354        9439 :             if (shift > CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
     355             :             {
     356         424 :                 mSynced.mWindow.reset();
     357             :             }
     358             :             else
     359             :             {
     360        9015 :                 mSynced.mWindow <<= shift;
     361        9015 :                 mSynced.mWindow.set(shift - 1);
     362             :             }
     363        9439 :             break;
     364             :         }
     365             :         }
     366        9482 :     }
     367             : 
     368             :     enum class Status
     369             :     {
     370             :         NotSynced,     // No state associated
     371             :         SyncInProcess, // mSyncInProcess will be active
     372             :         Synced,        // mSynced will be active
     373             :     } mStatus;
     374             : 
     375             :     struct SyncInProcess
     376             :     {
     377             :         std::array<uint8_t, kChallengeSize> mChallenge;
     378             :     };
     379             : 
     380             :     struct Synced
     381             :     {
     382             :         /*
     383             :          *  Past <--                --> Future
     384             :          *          MaxCounter - 1
     385             :          *                 |
     386             :          *                 v
     387             :          *  | <-- mWindow -->|
     388             :          *  |[n]|  ...   |[0]|
     389             :          */
     390             :         uint32_t mMaxCounter = 0; // The most recent counter we have seen
     391             :         std::bitset<CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE> mWindow;
     392             :     };
     393             : 
     394             :     // We should use std::variant here when migrated to C++17
     395             :     union
     396             :     {
     397             :         SyncInProcess mSyncInProcess;
     398             :         Synced mSynced;
     399             :     };
     400             : };
     401             : 
     402             : } // namespace Transport
     403             : } // namespace chip

Generated by: LCOV version 1.14