xref: /aosp_15_r20/external/pigweed/pw_containers/inline_var_len_entry_queue.c (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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