// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_containers/inline_var_len_entry_queue.h" #include #include "pw_assert/check.h" #include "pw_varint/varint.h" // Access the underlying buffer size and capacity (one less than size). static uint32_t BufferSize(const uint32_t* queue) { return queue[0]; } static uint32_t Capacity(const uint32_t* queue) { return BufferSize(queue) - 1; } // Access the head and tail offsets. #define HEAD(queue) queue[1] #define TAIL(queue) queue[2] // Access the data. Strict aliasing rules do not apply to byte pointers. static uint8_t* WritableData(uint32_t* queue) { return (uint8_t*)&queue[3]; } static const uint8_t* Data(const uint32_t* queue) { return (const uint8_t*)&queue[3]; } static uint32_t WrapIndex(pw_InlineVarLenEntryQueue_ConstHandle queue, uint32_t offset) { if (offset >= BufferSize(queue)) { offset -= BufferSize(queue); } return offset; } typedef struct { uint32_t prefix; uint32_t data; } EntrySize; // Returns the size of an entry, including both the prefix length and data size. static EntrySize ReadEntrySize(pw_InlineVarLenEntryQueue_ConstHandle queue, uint32_t offset) { EntrySize size = {0, 0}; bool keep_going; do { PW_DCHECK_UINT_NE(size.prefix, PW_VARINT_MAX_INT32_SIZE_BYTES); keep_going = pw_varint_DecodeOneByte32( Data(queue)[offset], size.prefix++, &size.data); offset = WrapIndex(queue, offset + 1); } while (keep_going); return size; } static uint32_t EncodePrefix(pw_InlineVarLenEntryQueue_ConstHandle queue, uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES], uint32_t data_size_bytes) { const uint32_t prefix_size = (uint32_t)pw_varint_Encode32( data_size_bytes, prefix, PW_VARINT_MAX_INT32_SIZE_BYTES); // Check that the ring buffer is capable of holding entries of this size. PW_CHECK_UINT_LE(prefix_size + data_size_bytes, Capacity(queue), "Entry is too large for this InlineVarLenEntryQueue"); return prefix_size; } // Returns the total encoded size of an entry. static uint32_t ReadEncodedEntrySize( pw_InlineVarLenEntryQueue_ConstHandle queue, uint32_t offset) { const EntrySize entry_size = ReadEntrySize(queue, offset); return entry_size.prefix + entry_size.data; } static uint32_t PopNonEmpty(pw_InlineVarLenEntryQueue_Handle queue) { const uint32_t entry_size = ReadEncodedEntrySize(queue, HEAD(queue)); HEAD(queue) = WrapIndex(queue, HEAD(queue) + entry_size); return entry_size; } // Copies data to the buffer, wrapping around the end if needed. static uint32_t CopyAndWrap(pw_InlineVarLenEntryQueue_Handle queue, uint32_t tail, const uint8_t* data, uint32_t size) { if (size == 0) { // Avoid zero-length memcpy, which may be UB if data is null return tail; } // Copy the new data in one or two chunks. The first chunk is copied to the // byte after the tail, the second from the beginning of the buffer. Both may // be zero bytes. uint32_t first_chunk = BufferSize(queue) - tail; if (first_chunk >= size) { first_chunk = size; } else { // Copy 2nd chunk from the beginning of the buffer (may be 0 bytes). memcpy(WritableData(queue), (const uint8_t*)data + first_chunk, size - first_chunk); } memcpy(&WritableData(queue)[tail], data, first_chunk); return WrapIndex(queue, tail + size); } static void AppendEntryKnownToFit(pw_InlineVarLenEntryQueue_Handle queue, const uint8_t* prefix, uint32_t prefix_size, const void* data, uint32_t size) { // Calculate the new tail offset. Don't update it until the copy is complete. uint32_t tail = TAIL(queue); tail = CopyAndWrap(queue, tail, prefix, prefix_size); TAIL(queue) = CopyAndWrap(queue, tail, data, size); } static inline uint32_t AvailableBytes( pw_InlineVarLenEntryQueue_ConstHandle queue) { uint32_t tail = TAIL(queue); if (tail < HEAD(queue)) { tail += BufferSize(queue); } return Capacity(queue) - (tail - HEAD(queue)); } void pw_InlineVarLenEntryQueue_Push(pw_InlineVarLenEntryQueue_Handle queue, const void* data, const uint32_t data_size_bytes) { uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES]; uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes); PW_CHECK(prefix_size + data_size_bytes <= AvailableBytes(queue), "Insufficient remaining space for entry"); AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes); } bool pw_InlineVarLenEntryQueue_TryPush(pw_InlineVarLenEntryQueue_Handle queue, const void* data, const uint32_t data_size_bytes) { uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES]; uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes); if (prefix_size + data_size_bytes > AvailableBytes(queue)) { return false; } AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes); return true; } void pw_InlineVarLenEntryQueue_PushOverwrite( pw_InlineVarLenEntryQueue_Handle queue, const void* data, const uint32_t data_size_bytes) { uint8_t prefix[PW_VARINT_MAX_INT32_SIZE_BYTES]; uint32_t prefix_size = EncodePrefix(queue, prefix, data_size_bytes); uint32_t available_bytes = AvailableBytes(queue); while (data_size_bytes + prefix_size > available_bytes) { available_bytes += PopNonEmpty(queue); } AppendEntryKnownToFit(queue, prefix, prefix_size, data, data_size_bytes); } void pw_InlineVarLenEntryQueue_Pop(pw_InlineVarLenEntryQueue_Handle queue) { PW_CHECK(!pw_InlineVarLenEntryQueue_Empty(queue)); PopNonEmpty(queue); } void pw_InlineVarLenEntryQueue_Iterator_Advance( pw_InlineVarLenEntryQueue_Iterator* iterator) { iterator->_pw_offset = WrapIndex( iterator->_pw_queue, iterator->_pw_offset + ReadEncodedEntrySize(iterator->_pw_queue, iterator->_pw_offset)); } pw_InlineVarLenEntryQueue_Entry pw_InlineVarLenEntryQueue_GetEntry( const pw_InlineVarLenEntryQueue_Iterator* iterator) { pw_InlineVarLenEntryQueue_ConstHandle queue = iterator->_pw_queue; pw_InlineVarLenEntryQueue_Entry entry; EntrySize size = ReadEntrySize(queue, iterator->_pw_offset); uint32_t offset_1 = WrapIndex(queue, iterator->_pw_offset + size.prefix); const uint32_t first_chunk = BufferSize(queue) - offset_1; if (size.data <= first_chunk) { entry.size_1 = size.data; entry.size_2 = 0; } else { entry.size_1 = first_chunk; entry.size_2 = size.data - first_chunk; } entry.data_1 = Data(queue) + offset_1; entry.data_2 = Data(queue) + WrapIndex(queue, offset_1 + entry.size_1); return entry; } uint32_t pw_InlineVarLenEntryQueue_Entry_Copy( const pw_InlineVarLenEntryQueue_Entry* entry, void* dest, uint32_t count) { PW_DCHECK(dest != NULL || count == 0u); const uint32_t entry_size = entry->size_1 + entry->size_2; const uint32_t to_copy = count < entry_size ? count : entry_size; if (to_copy == 0u) { return 0u; } memcpy(dest, entry->data_1, entry->size_1); const uint32_t remaining = to_copy - entry->size_1; if (remaining != 0u) { memcpy((uint8_t*)dest + entry->size_1, entry->data_2, remaining); } return to_copy; } const uint8_t* _pw_InlineVarLenEntryQueue_Entry_GetPointerChecked( const pw_InlineVarLenEntryQueue_Entry* entry, size_t index) { PW_CHECK_UINT_LT(index, entry->size_1 + entry->size_2); return _pw_InlineVarLenEntryQueue_Entry_GetPointer(entry, index); } uint32_t pw_InlineVarLenEntryQueue_Size( pw_InlineVarLenEntryQueue_ConstHandle queue) { uint32_t entry_count = 0; uint32_t offset = HEAD(queue); while (offset != TAIL(queue)) { offset = WrapIndex(queue, offset + ReadEncodedEntrySize(queue, offset)); entry_count += 1; } return entry_count; } uint32_t pw_InlineVarLenEntryQueue_SizeBytes( pw_InlineVarLenEntryQueue_ConstHandle queue) { uint32_t total_entry_size_bytes = 0; uint32_t offset = HEAD(queue); while (offset != TAIL(queue)) { const EntrySize size = ReadEntrySize(queue, offset); offset = WrapIndex(queue, offset + size.prefix + size.data); total_entry_size_bytes += size.data; } return total_entry_size_bytes; }