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
|