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 96 : static LengthType GetContentLength(const uint8_t * buffer) { return *buffer; }
45 37 : static void SetContentLength(uint8_t * buffer, LengthType size) { *buffer = static_cast<uint8_t>(size); }
46 :
47 : // Casts for chars as well
48 72 : static LengthType GetContentLength(const char * buffer) { return GetContentLength(Uint8::from_const_char(buffer)); }
49 32 : 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 : using ValueType = Span<const T>;
79 : static constexpr LengthType kInvalidLength = PascalPrefixOperations<PREFIX_LEN>::kInvalidLength;
80 :
81 : /// How many bytes of buffer are needed to store a max `charCount` sized buffer.
82 : static constexpr size_t BufferSizeFor(size_t charCount) { return PREFIX_LEN + charCount; }
83 :
84 : static_assert(sizeof(T) == 1);
85 :
86 : PascalBuffer(PascalBuffer &&) = default;
87 : PascalBuffer(const PascalBuffer &) = default;
88 :
89 : /// Interprets the given data as the pascal string content.
90 : ///
91 : /// Note that this references the given data in place. Data lifetime must
92 : /// exceed the PascalBuffer lifetime.
93 : template <size_t N>
94 12 : PascalBuffer(T (&data)[N]) : mData(data), mMaxSize(N - PREFIX_LEN)
95 : {
96 : static_assert(N >= PREFIX_LEN);
97 : static_assert(N <= kInvalidLength);
98 12 : }
99 :
100 : /// Uses an existing buffer for the pascal string, represented as a span.
101 : ///
102 : /// The size of the span includes the prefix and must be at least PREFIX_LEN (this is NOT checked).
103 : ///
104 : /// Note that this references the given buffer in place. The buffer that the span
105 : /// points to must have a lifetime that exceeds the PascalBuffer lifetime.
106 98 : PascalBuffer(Span<T> data) : mData(data.data()), mMaxSize(static_cast<LengthType>(data.size() - PREFIX_LEN)) {}
107 :
108 : /// Returns the content of the pascal string.
109 : /// Uses the prefix size information
110 41 : Span<const T> Content() const { return { mData + PREFIX_LEN, GetContentLength() }; }
111 :
112 : /// Accesses the "PASCAL" string (i.e. valid data including the string prefix)
113 : ///
114 : /// Use this to serialize the data. Specifically to recover the original string from
115 : /// persistent storage one can do one of two things:
116 : /// - persist pascalString.ContentWithLenPrefix (will include the data WITH the size prefix AND the prefix
117 : /// will correctly identify NULL strings via a kInvalidLength marker)
118 : /// - read via pascalString.RawFullBuffer
119 : /// - persist pascalString.Content (will NOT include data size and NULL strings will be the same as
120 : /// empty strings) and consider if IsNull requires special handling
121 : /// - read into a temporary buffer and set value via pascalString.SetValue()/SetNull()
122 : /// - OR read into (RawFullBuffer().data() + PREFIX_LEN) and call SetContentLength() or SetNull if applicable
123 42 : ByteSpan ContentWithLenPrefix() const
124 : {
125 42 : return { reinterpret_cast<const uint8_t *>(mData), static_cast<size_t>(GetContentLength() + PREFIX_LEN) };
126 : }
127 :
128 : /// Access to the full buffer. does NOT take into account current size
129 : /// and includes the "size prefix"
130 17 : MutableByteSpan RawFullBuffer()
131 : {
132 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
133 17 : return { reinterpret_cast<uint8_t *>(mData), static_cast<size_t>(mMaxSize + PREFIX_LEN) };
134 : }
135 :
136 104 : LengthType GetContentLength() const
137 : {
138 104 : const LengthType length = PascalPrefixOperations<PREFIX_LEN>::GetContentLength(mData);
139 104 : if (length == kInvalidLength)
140 : {
141 3 : return 0;
142 : }
143 101 : return std::min<LengthType>(mMaxSize, length);
144 : }
145 :
146 : // Returns true if the length was valid and could be set
147 52 : bool SetContentLength(LengthType len)
148 : {
149 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
150 52 : if (len != kInvalidLength)
151 : {
152 49 : VerifyOrReturnError(len <= mMaxSize, false);
153 : }
154 47 : PascalPrefixOperations<PREFIX_LEN>::SetContentLength(mData, len);
155 47 : return true;
156 : }
157 3 : void SetNull() { (void) SetContentLength(kInvalidLength); }
158 13 : bool IsNull() const { return PascalPrefixOperations<PREFIX_LEN>::GetContentLength(mData) == kInvalidLength; }
159 :
160 : // Returns true if the length of the input buffer fit in the
161 : // pascal buffer (and could be set)
162 45 : bool SetValue(Span<const T> value)
163 : {
164 : static_assert(!std::is_const_v<T>, "Cannot mutate a const pascal string");
165 45 : VerifyOrReturnValue(value.size() < kInvalidLength, false);
166 45 : VerifyOrReturnValue(SetContentLength(static_cast<LengthType>(value.size())), false);
167 40 : memcpy(mData + PREFIX_LEN, value.data(), value.size());
168 40 : return true;
169 : }
170 :
171 : /// Checks if the given span is a valid Pascal string: i.e. its size prefix
172 : /// is either Invalid (i.e. null marker) or it has a size that fits in the buffer
173 18 : static bool IsValid(ByteSpan span)
174 : {
175 18 : VerifyOrReturnValue(span.size() >= PREFIX_LEN, false);
176 18 : LengthType len = PascalPrefixOperations<PREFIX_LEN>::GetContentLength(span.data());
177 18 : return len == kInvalidLength || (static_cast<size_t>(len + PREFIX_LEN) <= span.size());
178 : }
179 :
180 : private:
181 : T * mData;
182 : const LengthType mMaxSize;
183 : };
184 :
185 : using ShortPascalString = PascalBuffer<char, 1>;
186 : using ShortPascalBytes = PascalBuffer<uint8_t, 1>;
187 : using LongPascalString = PascalBuffer<char, 2>;
188 : using LongPascalBytes = PascalBuffer<uint8_t, 2>;
189 :
190 : // same as the pascal strings, except the data is const and cannot
191 : // be changed. Useful to get the content of data
192 : using ShortConstPascalString = PascalBuffer<const char, 1>;
193 : using ShortConstPascalBytes = PascalBuffer<const uint8_t, 1>;
194 : using LongConstPascalString = PascalBuffer<const char, 2>;
195 : using LongConstPascalBytes = PascalBuffer<const uint8_t, 2>;
196 :
197 : } // namespace Storage
198 : } // namespace app
199 : } // namespace chip
|