1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_containers/inline_var_len_entry_queue.h"
16
17 #include <string.h>
18
19 #include "pw_assert/check.h"
20 #include "pw_varint/varint.h"
21
22 // Access the underlying buffer size and capacity (one less than size).
BufferSize(const uint32_t * queue)23 static uint32_t BufferSize(const uint32_t* queue) { return queue[0]; }
Capacity(const uint32_t * queue)24 static uint32_t Capacity(const uint32_t* queue) {
25 return BufferSize(queue) - 1;
26 }
27
28 // Access the head and tail offsets.
29 #define HEAD(queue) queue[1]
30 #define TAIL(queue) queue[2]
31
32 // Access the data. Strict aliasing rules do not apply to byte pointers.
WritableData(uint32_t * queue)33 static uint8_t* WritableData(uint32_t* queue) { return (uint8_t*)&queue[3]; }
Data(const uint32_t * queue)34 static const uint8_t* Data(const uint32_t* queue) {
35 return (const uint8_t*)&queue[3];
36 }
37
WrapIndex(pw_InlineVarLenEntryQueue_ConstHandle queue,uint32_t offset)38 static uint32_t WrapIndex(pw_InlineVarLenEntryQueue_ConstHandle queue,
39 uint32_t offset) {
40 if (offset >= BufferSize(queue)) {
41 offset -= BufferSize(queue);
42 }
43 return offset;
44 }
45
46 typedef struct {
47 uint32_t prefix;
48 uint32_t data;
49 } EntrySize;
50
51 // Returns the size of an entry, including both the prefix length and data size.
ReadEntrySize(pw_InlineVarLenEntryQueue_ConstHandle queue,uint32_t offset)52 static EntrySize ReadEntrySize(pw_InlineVarLenEntryQueue_ConstHandle queue,
53 uint32_t offset) {
54 EntrySize size = {0, 0};
55
56 bool keep_going;
57 do {
58 PW_DCHECK_UINT_NE(size.prefix, PW_VARINT_MAX_INT32_SIZE_BYTES);
59
60 keep_going = pw_varint_DecodeOneByte32(
61 Data(queue)[offset], size.prefix++, &size.data);
62 offset = WrapIndex(queue, offset + 1);
63 } while (keep_going);
64
65 return size;
66 }
67
EncodePrefix(pw_InlineVarLenEntryQueue_ConstHandle queue,uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES],uint32_t data_size_bytes)68 static uint32_t EncodePrefix(pw_InlineVarLenEntryQueue_ConstHandle queue,
69 uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES],
70 uint32_t data_size_bytes) {
71 const uint32_t prefix_size = (uint32_t)pw_varint_Encode32(
72 data_size_bytes, prefix, PW_VARINT_MAX_INT32_SIZE_BYTES);
73
74 // Check that the ring buffer is capable of holding entries of this size.
75 PW_CHECK_UINT_LE(prefix_size + data_size_bytes,
76 Capacity(queue),
77 "Entry is too large for this InlineVarLenEntryQueue");
78 return prefix_size;
79 }
80
81 // Returns the total encoded size of an entry.
ReadEncodedEntrySize(pw_InlineVarLenEntryQueue_ConstHandle queue,uint32_t offset)82 static uint32_t ReadEncodedEntrySize(
83 pw_InlineVarLenEntryQueue_ConstHandle queue, uint32_t offset) {
84 const EntrySize entry_size = ReadEntrySize(queue, offset);
85 return entry_size.prefix + entry_size.data;
86 }
87
PopNonEmpty(pw_InlineVarLenEntryQueue_Handle queue)88 static uint32_t PopNonEmpty(pw_InlineVarLenEntryQueue_Handle queue) {
89 const uint32_t entry_size = ReadEncodedEntrySize(queue, HEAD(queue));
90 HEAD(queue) = WrapIndex(queue, HEAD(queue) + entry_size);
91 return entry_size;
92 }
93
94 // Copies data to the buffer, wrapping around the end if needed.
CopyAndWrap(pw_InlineVarLenEntryQueue_Handle queue,uint32_t tail,const uint8_t * data,uint32_t size)95 static uint32_t CopyAndWrap(pw_InlineVarLenEntryQueue_Handle queue,
96 uint32_t tail,
97 const uint8_t* data,
98 uint32_t size) {
99 if (size == 0) { // Avoid zero-length memcpy, which may be UB if data is null
100 return tail;
101 }
102
103 // Copy the new data in one or two chunks. The first chunk is copied to the
104 // byte after the tail, the second from the beginning of the buffer. Both may
105 // be zero bytes.
106 uint32_t first_chunk = BufferSize(queue) - tail;
107 if (first_chunk >= size) {
108 first_chunk = size;
109 } else { // Copy 2nd chunk from the beginning of the buffer (may be 0 bytes).
110 memcpy(WritableData(queue),
111 (const uint8_t*)data + first_chunk,
112 size - first_chunk);
113 }
114 memcpy(&WritableData(queue)[tail], data, first_chunk);
115 return WrapIndex(queue, tail + size);
116 }
117
AppendEntryKnownToFit(pw_InlineVarLenEntryQueue_Handle queue,const uint8_t * prefix,uint32_t prefix_size,const void * data,uint32_t size)118 static void AppendEntryKnownToFit(pw_InlineVarLenEntryQueue_Handle queue,
119 const uint8_t* prefix,
120 uint32_t prefix_size,
121 const void* data,
122 uint32_t size) {
123 // Calculate the new tail offset. Don't update it until the copy is complete.
124 uint32_t tail = TAIL(queue);
125 tail = CopyAndWrap(queue, tail, prefix, prefix_size);
126 TAIL(queue) = CopyAndWrap(queue, tail, data, size);
127 }
128
AvailableBytes(pw_InlineVarLenEntryQueue_ConstHandle queue)129 static inline uint32_t AvailableBytes(
130 pw_InlineVarLenEntryQueue_ConstHandle queue) {
131 uint32_t tail = TAIL(queue);
132 if (tail < HEAD(queue)) {
133 tail += BufferSize(queue);
134 }
135 return Capacity(queue) - (tail - HEAD(queue));
136 }
137
pw_InlineVarLenEntryQueue_Push(pw_InlineVarLenEntryQueue_Handle queue,const void * data,const uint32_t data_size_bytes)138 void pw_InlineVarLenEntryQueue_Push(pw_InlineVarLenEntryQueue_Handle queue,
139 const void* data,
140 const uint32_t data_size_bytes) {
141 uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES];
142 uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes);
143
144 PW_CHECK(prefix_size + data_size_bytes <= AvailableBytes(queue),
145 "Insufficient remaining space for entry");
146
147 AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes);
148 }
149
pw_InlineVarLenEntryQueue_TryPush(pw_InlineVarLenEntryQueue_Handle queue,const void * data,const uint32_t data_size_bytes)150 bool pw_InlineVarLenEntryQueue_TryPush(pw_InlineVarLenEntryQueue_Handle queue,
151 const void* data,
152 const uint32_t data_size_bytes) {
153 uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES];
154 uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes);
155
156 if (prefix_size + data_size_bytes > AvailableBytes(queue)) {
157 return false;
158 }
159
160 AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes);
161 return true;
162 }
163
pw_InlineVarLenEntryQueue_PushOverwrite(pw_InlineVarLenEntryQueue_Handle queue,const void * data,const uint32_t data_size_bytes)164 void pw_InlineVarLenEntryQueue_PushOverwrite(
165 pw_InlineVarLenEntryQueue_Handle queue,
166 const void* data,
167 const uint32_t data_size_bytes) {
168 uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES];
169 uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes);
170
171 uint32_t available_bytes = AvailableBytes(queue);
172 while (data_size_bytes + prefix_size > available_bytes) {
173 available_bytes += PopNonEmpty(queue);
174 }
175
176 AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes);
177 }
178
pw_InlineVarLenEntryQueue_Pop(pw_InlineVarLenEntryQueue_Handle queue)179 void pw_InlineVarLenEntryQueue_Pop(pw_InlineVarLenEntryQueue_Handle queue) {
180 PW_CHECK(!pw_InlineVarLenEntryQueue_Empty(queue));
181 PopNonEmpty(queue);
182 }
183
pw_InlineVarLenEntryQueue_Iterator_Advance(pw_InlineVarLenEntryQueue_Iterator * iterator)184 void pw_InlineVarLenEntryQueue_Iterator_Advance(
185 pw_InlineVarLenEntryQueue_Iterator* iterator) {
186 iterator->_pw_offset = WrapIndex(
187 iterator->_pw_queue,
188 iterator->_pw_offset +
189 ReadEncodedEntrySize(iterator->_pw_queue, iterator->_pw_offset));
190 }
191
pw_InlineVarLenEntryQueue_GetEntry(const pw_InlineVarLenEntryQueue_Iterator * iterator)192 pw_InlineVarLenEntryQueue_Entry pw_InlineVarLenEntryQueue_GetEntry(
193 const pw_InlineVarLenEntryQueue_Iterator* iterator) {
194 pw_InlineVarLenEntryQueue_ConstHandle queue = iterator->_pw_queue;
195
196 pw_InlineVarLenEntryQueue_Entry entry;
197 EntrySize size = ReadEntrySize(queue, iterator->_pw_offset);
198 uint32_t offset_1 = WrapIndex(queue, iterator->_pw_offset + size.prefix);
199
200 const uint32_t first_chunk = BufferSize(queue) - offset_1;
201
202 if (size.data <= first_chunk) {
203 entry.size_1 = size.data;
204 entry.size_2 = 0;
205 } else {
206 entry.size_1 = first_chunk;
207 entry.size_2 = size.data - first_chunk;
208 }
209
210 entry.data_1 = Data(queue) + offset_1;
211 entry.data_2 = Data(queue) + WrapIndex(queue, offset_1 + entry.size_1);
212 return entry;
213 }
214
pw_InlineVarLenEntryQueue_Entry_Copy(const pw_InlineVarLenEntryQueue_Entry * entry,void * dest,uint32_t count)215 uint32_t pw_InlineVarLenEntryQueue_Entry_Copy(
216 const pw_InlineVarLenEntryQueue_Entry* entry, void* dest, uint32_t count) {
217 PW_DCHECK(dest != NULL || count == 0u);
218
219 const uint32_t entry_size = entry->size_1 + entry->size_2;
220 const uint32_t to_copy = count < entry_size ? count : entry_size;
221
222 if (to_copy == 0u) {
223 return 0u;
224 }
225
226 memcpy(dest, entry->data_1, entry->size_1);
227
228 const uint32_t remaining = to_copy - entry->size_1;
229 if (remaining != 0u) {
230 memcpy((uint8_t*)dest + entry->size_1, entry->data_2, remaining);
231 }
232
233 return to_copy;
234 }
235
_pw_InlineVarLenEntryQueue_Entry_GetPointerChecked(const pw_InlineVarLenEntryQueue_Entry * entry,size_t index)236 const uint8_t* _pw_InlineVarLenEntryQueue_Entry_GetPointerChecked(
237 const pw_InlineVarLenEntryQueue_Entry* entry, size_t index) {
238 PW_CHECK_UINT_LT(index, entry->size_1 + entry->size_2);
239 return _pw_InlineVarLenEntryQueue_Entry_GetPointer(entry, index);
240 }
241
pw_InlineVarLenEntryQueue_Size(pw_InlineVarLenEntryQueue_ConstHandle queue)242 uint32_t pw_InlineVarLenEntryQueue_Size(
243 pw_InlineVarLenEntryQueue_ConstHandle queue) {
244 uint32_t entry_count = 0;
245 uint32_t offset = HEAD(queue);
246
247 while (offset != TAIL(queue)) {
248 offset = WrapIndex(queue, offset + ReadEncodedEntrySize(queue, offset));
249 entry_count += 1;
250 }
251 return entry_count;
252 }
253
pw_InlineVarLenEntryQueue_SizeBytes(pw_InlineVarLenEntryQueue_ConstHandle queue)254 uint32_t pw_InlineVarLenEntryQueue_SizeBytes(
255 pw_InlineVarLenEntryQueue_ConstHandle queue) {
256 uint32_t total_entry_size_bytes = 0;
257 uint32_t offset = HEAD(queue);
258
259 while (offset != TAIL(queue)) {
260 const EntrySize size = ReadEntrySize(queue, offset);
261 offset = WrapIndex(queue, offset + size.prefix + size.data);
262 total_entry_size_bytes += size.data;
263 }
264 return total_entry_size_bytes;
265 }
266