Line data Source code
1 : /**
2 : * Copyright (c) 2025 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 : #pragma once
17 :
18 : #include <app/ConcreteAttributePath.h>
19 : #include <lib/core/CHIPEncoding.h>
20 : #include <lib/core/CHIPError.h>
21 : #include <lib/core/CHIPSafeCasts.h>
22 : #include <lib/support/CodeUtils.h>
23 : #include <lib/support/Span.h>
24 :
25 : #include <algorithm>
26 : #include <cstdint>
27 : #include <type_traits>
28 :
29 : namespace chip {
30 : namespace app {
31 : namespace Storage {
32 :
33 : /// Describes how to set/get the length of pascal strings
34 : /// given a PREFIX_LEN in bytes.
35 : template <size_t PREFIX_LEN>
36 : struct PascalPrefixOperations;
37 :
38 : template <>
39 : struct PascalPrefixOperations<1>
40 : {
41 : using LengthType = uint8_t;
42 : static constexpr LengthType kInvalidLength = 0xFF;
43 :
44 30 : static LengthType GetContentLength(const uint8_t * buffer) { return *buffer; }
45 8 : static void SetContentLength(uint8_t * buffer, LengthType size) { *buffer = static_cast<uint8_t>(size); }
46 :
47 : // Casts for chars as well
48 10 : static LengthType GetContentLength(const char * buffer) { return GetContentLength(Uint8::from_const_char(buffer)); }
49 3 : static void SetContentLength(char * buffer, LengthType size) { SetContentLength(Uint8::from_char(buffer), size); }
50 : };
51 :
52 : template <>
53 : struct PascalPrefixOperations<2>
54 : {
55 : using LengthType = uint16_t;
56 : static constexpr LengthType kInvalidLength = 0xFFFF;
57 :
58 39 : static LengthType GetContentLength(const uint8_t * buffer) { return Encoding::LittleEndian::Get16(buffer); }
59 10 : static void SetContentLength(uint8_t * buffer, LengthType size) { Encoding::LittleEndian::Put16(buffer, size); }
60 :
61 : // Casts for chars as well
62 15 : static LengthType GetContentLength(const char * buffer) { return GetContentLength(Uint8::from_const_char(buffer)); }
63 5 : static void SetContentLength(char * buffer, LengthType size) { SetContentLength(Uint8::from_char(buffer), size); }
64 : };
65 :
66 : /// Interprets a byte buffer as a pascal buffer:
67 : /// - a prefix that specifies the length of the data
68 : /// - the following characters that contain the data
69 : ///
70 : /// Parameters:
71 : /// T - the underlying data type, generally uint8_t for bytes or char for strings
72 : /// PREFIX_LEN - the size of the pascal prefix (generally 1 or 2 bytes)
73 : template <typename T, uint8_t PREFIX_LEN>
74 : class PascalBuffer
75 : {
76 : public:
77 : using LengthType = typename PascalPrefixOperations<PREFIX_LEN>::LengthType;
78 : static constexpr LengthType kInvalidLength = PascalPrefixOperations<PREFIX_LEN>::kInvalidLength;
79 :
80 : static_assert(sizeof(T) == 1);
81 :
82 : PascalBuffer(PascalBuffer &&) = default;
83 : PascalBuffer(const PascalBuffer &) = default;
84 :
85 : template <size_t N>
86 12 : PascalBuffer(T (&data)[N]) : mData(data), mMaxSize(N - PREFIX_LEN)
87 : {
88 : static_assert(N >= PREFIX_LEN);
89 : static_assert(N <= kInvalidLength);
90 12 : }
91 :
92 : /// Returns the content of the pascal string.
93 : /// Uses the prefix size information
94 13 : Span<T> Content() { return { mData + PREFIX_LEN, GetContentLength() }; }
95 : Span<const T> Content() const { return { mData + PREFIX_LEN, GetContentLength() }; }
96 :
97 : /// Accesses the "PASCAL" string (i.e. valid data including the string prefix)
98 : ///
99 : /// Use this to serialize the data. Specifically to recover the original string from
100 : /// persistent storage one can do one of two things:
101 : /// - persist pascalString.ContentWithLenPrefix (will include the data WITH the size prefix AND the prefix
102 : /// will correctly identify NULL strings via a kInvalidLength marker)
103 : /// - read via pascalString.RawFullBuffer
104 : /// - persist pascalString.Content (will NOT include data size and NULL strings will be the same as
105 : /// empty strings) and consider if IsNull requires special handling
106 : /// - read into a temporary buffer and set value via pascalString.SetValue()/SetNull()
107 : /// - OR read into (RawFullBuffer().data() + PREFIX_LEN) and call SetContentLength() or SetNull if applicable
108 8 : ByteSpan ContentWithLenPrefix() const
109 : {
110 8 : return { reinterpret_cast<const uint8_t *>(mData), static_cast<size_t>(GetContentLength() + PREFIX_LEN) };
111 : }
112 :
113 : /// Access to the full buffer. does NOT take into account current size
114 : /// and includes the "size prefix"
115 10 : MutableByteSpan RawFullBuffer()
116 : {
117 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
118 10 : return { reinterpret_cast<uint8_t *>(mData), static_cast<size_t>(mMaxSize + PREFIX_LEN) };
119 : }
120 :
121 42 : LengthType GetContentLength() const
122 : {
123 42 : const LengthType length = PascalPrefixOperations<PREFIX_LEN>::GetContentLength(mData);
124 42 : if (length == kInvalidLength)
125 : {
126 3 : return 0;
127 : }
128 39 : return std::min<LengthType>(mMaxSize, length);
129 : }
130 :
131 : // Returns true if the length was valid and could be set
132 22 : bool SetContentLength(LengthType len)
133 : {
134 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
135 22 : if (len != kInvalidLength)
136 : {
137 19 : VerifyOrReturnError(len <= mMaxSize, false);
138 : }
139 18 : PascalPrefixOperations<PREFIX_LEN>::SetContentLength(mData, len);
140 18 : return true;
141 : }
142 3 : void SetNull() { (void) SetContentLength(kInvalidLength); }
143 13 : bool IsNull() const { return PascalPrefixOperations<PREFIX_LEN>::GetContentLength(mData) == kInvalidLength; }
144 :
145 : // Returns true if the length of the input buffer fit in the
146 : // pascal buffer (and could be set)
147 15 : bool SetValue(Span<const T> value)
148 : {
149 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
150 15 : VerifyOrReturnValue(value.size() < kInvalidLength, false);
151 15 : VerifyOrReturnValue(SetContentLength(static_cast<LengthType>(value.size())), false);
152 11 : memcpy(mData + PREFIX_LEN, value.data(), value.size());
153 11 : return true;
154 : }
155 :
156 : /// Checks if the given span is a valid Pascal string: i.e. its size prefix
157 : /// is either Invalid (i.e. null marker) or it has a size that fits in the buffer
158 14 : static bool IsValid(Span<const T> span)
159 : {
160 14 : VerifyOrReturnValue(span.size() >= PREFIX_LEN, false);
161 14 : LengthType len = PascalPrefixOperations<PREFIX_LEN>::GetContentLength(span.data());
162 14 : return len == kInvalidLength || (static_cast<size_t>(len + PREFIX_LEN) <= span.size());
163 : }
164 :
165 : private:
166 : T * mData;
167 : const LengthType mMaxSize;
168 : };
169 :
170 : using ShortPascalString = PascalBuffer<char, 1>;
171 : using ShortPascalBytes = PascalBuffer<uint8_t, 1>;
172 : using LongPascalString = PascalBuffer<char, 2>;
173 : using LongPascalBytes = PascalBuffer<uint8_t, 2>;
174 :
175 : // same as the pascal strings, except the data is const and cannot
176 : // be changed. Useful to get the content of data
177 : using ShortConstPascalString = PascalBuffer<const char, 1>;
178 : using ShortConstPascalBytes = PascalBuffer<const uint8_t, 1>;
179 : using LongConstPascalString = PascalBuffer<const char, 2>;
180 : using LongConstPascalBytes = PascalBuffer<const uint8_t, 2>;
181 :
182 : } // namespace Storage
183 : } // namespace app
184 : } // namespace chip
|