Matter SDK Coverage Report
Current view: top level - transport - PeerMessageCounter.h (source / functions) Coverage Total Hit
Test: SHA:209dc18e4021e7d0dff8120ccc585909391dd862 Lines: 80.6 % 124 100
Test Date: 2026-06-16 07:34:53 Functions: 79.2 % 24 19

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

Generated by: LCOV version 2.0-1