Line data Source code
1 : /*
2 : * Copyright (c) 2026 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #pragma once
18 :
19 : #include <cstdint>
20 :
21 : namespace chip::app::reporting {
22 :
23 : // u32 generations can generally wrap around. We try to do a best effort figuring out
24 : // if First < Second considering wrap-around. So we cast their difference to i32 and
25 : // see if that is positive.
26 4818 : inline constexpr bool AreGenerationsInOrder(uint32_t generationFirst, uint32_t generationSecond)
27 : {
28 4818 : return static_cast<int32_t>(generationSecond - generationFirst) > 0;
29 : }
30 :
31 : // essentially our unit tests
32 : static_assert(AreGenerationsInOrder(1, 100));
33 : static_assert(!AreGenerationsInOrder(100, 1));
34 :
35 : static_assert(AreGenerationsInOrder(0xFFFFFFAB, 120));
36 : static_assert(!AreGenerationsInOrder(120, 0xFFFFFFAB));
37 :
38 : static_assert(AreGenerationsInOrder(1, 0x7FFFFFFF));
39 : static_assert(!AreGenerationsInOrder(0x7FFFFFFF, 1));
40 :
41 : // random increases from small numbers
42 : static_assert(AreGenerationsInOrder(2, 0x80000000));
43 : static_assert(AreGenerationsInOrder(100, 0x80000000));
44 : static_assert(AreGenerationsInOrder(1000, 0x800000AB));
45 :
46 : // wrap-arounds
47 : static_assert(AreGenerationsInOrder(0x80000000 + 1000, 900));
48 : static_assert(AreGenerationsInOrder(0x80000000 + 0x12345, 0x12344));
49 :
50 : /// Represents a generation of an attribute. A thin wrapper around `uint32_t` that intentionally does not
51 : /// provide an implicit conversion operator back to `uint32_t`, ensuring that callers use
52 : /// wrap-around-aware comparison logic (e.g. `Before`/`After`) instead of
53 : /// raw integer comparisons which would break at the 2^32-1 boundary.
54 : ///
55 : /// Note: usage of uint32_t is intentional to minimize size overhead. For example, in
56 : /// `struct AttributePathParamsWithGeneration` (defined in Engine.h), using 32-bit generations
57 : /// keeps the structure size at 16 bytes.
58 : ///
59 : /// The size breakdown is as follows:
60 : /// - Base `AttributePathParams`: 12 bytes (4-byte ClusterId, 4-byte AttributeId,
61 : /// 2-byte EndpointId, 2-byte ListIndex).
62 : /// - Current: Adding a 4-byte `AttributeGeneration` results in a total of 16 bytes.
63 : /// - Hypothetical: If this were 64-bit, the compiler would insert 4 bytes of alignment
64 : /// padding after the 12-byte base to satisfy the 8-byte alignment requirement for
65 : /// the uint64_t, resulting in 24 bytes (12 + 4 + 8) — a 50% increase in size.
66 : ///
67 : /// On typical 32-bit MCU targets used by this stack, using 32-bit arithmetic instead of
68 : /// 64-bit handling often results in smaller generated code, helping reduce flash usage.
69 : /// The value 0 is reserved as a "not defined" marker and is skipped during increment.
70 : class AttributeGeneration
71 : {
72 : public:
73 4641 : explicit AttributeGeneration(uint32_t value) : mValue(value) {}
74 :
75 240 : AttributeGeneration() = default;
76 : AttributeGeneration(const AttributeGeneration &) = default;
77 : AttributeGeneration(AttributeGeneration &&) = default;
78 :
79 : AttributeGeneration & operator=(const AttributeGeneration &) = default;
80 : AttributeGeneration & operator=(AttributeGeneration &&) = default;
81 :
82 28 : constexpr bool Before(const AttributeGeneration & other) const { return AreGenerationsInOrder(mValue, other.mValue); }
83 4790 : constexpr bool After(const AttributeGeneration & other) const { return AreGenerationsInOrder(other.mValue, mValue); }
84 :
85 : // zero is a special marker, generally may be used as "not defined"
86 128 : constexpr bool IsZero() const { return mValue == 0; }
87 :
88 : // reset to zero value (since zero is used as a special/uninitialized marker)
89 28 : void Clear() { mValue = 0; }
90 :
91 3960 : uint32_t Raw() const { return mValue; }
92 :
93 : // increment, guarantees 0 is NOT used as a value when incrementing and wrapping around
94 5413 : void Increment()
95 : {
96 5413 : mValue++;
97 5413 : if (mValue == 0)
98 : {
99 0 : mValue = 1;
100 : }
101 5413 : }
102 :
103 : private:
104 : uint32_t mValue{};
105 : };
106 :
107 : } // namespace chip::app::reporting
|