diff --git a/AK/CMakeLists.txt b/AK/CMakeLists.txt index 295e41a3284..68071676711 100644 --- a/AK/CMakeLists.txt +++ b/AK/CMakeLists.txt @@ -1,6 +1,7 @@ set(AK_SOURCES Assertions.cpp Base64.cpp + CircularBuffer.cpp DeprecatedString.cpp FloatingPointStringConversions.cpp FlyString.cpp diff --git a/AK/CircularBuffer.cpp b/AK/CircularBuffer.cpp new file mode 100644 index 00000000000..71c26cd8bd6 --- /dev/null +++ b/AK/CircularBuffer.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2022, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace AK { + +CircularBuffer::CircularBuffer(ByteBuffer buffer) + : m_buffer(move(buffer)) +{ +} + +ErrorOr CircularBuffer::create_empty(size_t size) +{ + auto temporary_buffer = TRY(ByteBuffer::create_uninitialized(size)); + + CircularBuffer circular_buffer { move(temporary_buffer) }; + + return circular_buffer; +} + +ErrorOr CircularBuffer::create_initialized(ByteBuffer buffer) +{ + CircularBuffer circular_buffer { move(buffer) }; + + circular_buffer.m_used_space = circular_buffer.m_buffer.size(); + + return circular_buffer; +} + +size_t CircularBuffer::empty_space() const +{ + return capacity() - m_used_space; +} + +size_t CircularBuffer::used_space() const +{ + return m_used_space; +} + +size_t CircularBuffer::capacity() const +{ + return m_buffer.size(); +} + +bool CircularBuffer::is_wrapping_around() const +{ + return capacity() <= m_reading_head + m_used_space; +} + +Optional CircularBuffer::offset_of(StringView needle, Optional until) const +{ + auto const read_until = until.value_or(m_used_space); + + Array spans {}; + spans[0] = next_read_span(); + + if (spans[0].size() > read_until) + spans[0] = spans[0].trim(read_until); + else if (is_wrapping_around()) + spans[1] = m_buffer.span().slice(0, read_until - spans[0].size()); + + return AK::memmem(spans.begin(), spans.end(), needle.bytes()); +} + +void CircularBuffer::clear() +{ + m_reading_head = 0; + m_used_space = 0; +} + +Bytes CircularBuffer::next_write_span() +{ + if (is_wrapping_around()) + return m_buffer.span().slice(m_reading_head + m_used_space - capacity(), capacity() - m_used_space); + return m_buffer.span().slice(m_reading_head + m_used_space, capacity() - (m_reading_head + m_used_space)); +} + +ReadonlyBytes CircularBuffer::next_read_span() const +{ + return m_buffer.span().slice(m_reading_head, min(capacity() - m_reading_head, m_used_space)); +} + +size_t CircularBuffer::write(ReadonlyBytes bytes) +{ + auto remaining = bytes.size(); + + while (remaining > 0) { + auto const next_span = next_write_span(); + if (next_span.size() == 0) + break; + + auto const written_bytes = bytes.slice(bytes.size() - remaining).copy_trimmed_to(next_span); + + m_used_space += written_bytes; + + remaining -= written_bytes; + } + + return bytes.size() - remaining; +} + +Bytes CircularBuffer::read(Bytes bytes) +{ + auto remaining = bytes.size(); + + while (remaining > 0) { + auto const next_span = next_read_span(); + if (next_span.size() == 0) + break; + + auto written_bytes = next_span.copy_trimmed_to(bytes.slice(bytes.size() - remaining)); + + m_used_space -= written_bytes; + m_reading_head += written_bytes; + + if (m_reading_head >= capacity()) + m_reading_head -= capacity(); + + remaining -= written_bytes; + } + + return bytes.trim(bytes.size() - remaining); +} + +ErrorOr CircularBuffer::discard(size_t discarding_size) +{ + if (m_used_space < discarding_size) + return Error::from_string_literal("Can not discard more data than what the buffer contains"); + m_used_space -= discarding_size; + m_reading_head = (m_reading_head + discarding_size) % capacity(); + + return {}; +} + +} diff --git a/AK/CircularBuffer.h b/AK/CircularBuffer.h new file mode 100644 index 00000000000..0f121986f4a --- /dev/null +++ b/AK/CircularBuffer.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace AK { + +class CircularBuffer { + AK_MAKE_NONCOPYABLE(CircularBuffer); + +public: + static ErrorOr create_empty(size_t size); + static ErrorOr create_initialized(ByteBuffer); + + CircularBuffer(CircularBuffer&& other) + { + operator=(move(other)); + } + + CircularBuffer& operator=(CircularBuffer&& other) + { + if (&other == this) + return *this; + + swap(m_buffer, other.m_buffer); + swap(m_reading_head, other.m_reading_head); + swap(m_used_space, other.m_used_space); + + return *this; + } + + ~CircularBuffer() = default; + + size_t write(ReadonlyBytes bytes); + Bytes read(Bytes bytes); + ErrorOr discard(size_t discarded_bytes); + + [[nodiscard]] size_t empty_space() const; + [[nodiscard]] size_t used_space() const; + [[nodiscard]] size_t capacity() const; + + Optional offset_of(StringView needle, Optional until = {}) const; + + void clear(); + +private: + CircularBuffer(ByteBuffer); + + [[nodiscard]] bool is_wrapping_around() const; + + [[nodiscard]] Bytes next_write_span(); + [[nodiscard]] ReadonlyBytes next_read_span() const; + + ByteBuffer m_buffer {}; + + size_t m_reading_head {}; + size_t m_used_space {}; +}; + +} diff --git a/AK/Forward.h b/AK/Forward.h index c92787928b0..bb487627b74 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -18,6 +18,7 @@ class ByteBuffer; class Bitmap; using ByteBuffer = Detail::ByteBuffer<32>; +class CircularBuffer; class Error; class GenericLexer; class IPv4Address; @@ -155,6 +156,7 @@ using AK::Badge; using AK::Bitmap; using AK::ByteBuffer; using AK::Bytes; +using AK::CircularBuffer; using AK::CircularDuplexStream; using AK::CircularQueue; using AK::DeprecatedString; diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index 27751cbaa44..e7e0c0855f8 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -14,6 +14,7 @@ set(AK_TEST_SOURCES TestByteBuffer.cpp TestCharacterTypes.cpp TestChecked.cpp + TestCircularBuffer.cpp TestCircularDeque.cpp TestCircularDuplexStream.cpp TestCircularQueue.cpp diff --git a/Tests/AK/TestCircularBuffer.cpp b/Tests/AK/TestCircularBuffer.cpp new file mode 100644 index 00000000000..6b279d51d41 --- /dev/null +++ b/Tests/AK/TestCircularBuffer.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2022, Lucas Chollet + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include + +namespace { + +CircularBuffer create_circular_buffer(size_t size) +{ + auto buffer_or_error = CircularBuffer::create_empty(size); + EXPECT(!buffer_or_error.is_error()); + + return buffer_or_error.release_value(); +} + +void safe_write(CircularBuffer& buffer, u8 i) +{ + Bytes b { &i, 1 }; + auto written_bytes = buffer.write(b); + EXPECT_EQ(written_bytes, 1ul); +}; + +void safe_read(CircularBuffer& buffer, u8 supposed_result) +{ + u8 read_value {}; + Bytes b { &read_value, 1 }; + b = buffer.read(b); + EXPECT_EQ(b.size(), 1ul); + EXPECT_EQ(*b.data(), supposed_result); +}; + +void safe_discard(CircularBuffer& buffer, size_t size) +{ + auto result = buffer.discard(size); + EXPECT(!result.is_error()); +}; + +} + +TEST_CASE(simple_write_read) +{ + auto buffer = create_circular_buffer(1); + + safe_write(buffer, 42); + safe_read(buffer, 42); +} + +TEST_CASE(writing_above_limits) +{ + auto buffer = create_circular_buffer(1); + + safe_write(buffer, 1); + + u8 value = 42; + Bytes b { &value, 1 }; + auto written_bytes = buffer.write(b); + EXPECT_EQ(written_bytes, 0ul); +} + +TEST_CASE(usage_with_wrapping_around) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + for (unsigned i {}; i < capacity; ++i) + safe_write(buffer, i + 8); + + EXPECT_EQ(buffer.used_space(), capacity); + EXPECT_EQ(buffer.empty_space(), 0ul); + + safe_read(buffer, 0 + 8); + safe_read(buffer, 1 + 8); + + EXPECT_EQ(buffer.used_space(), capacity - 2); + + safe_write(buffer, 5); + safe_write(buffer, 6); + + EXPECT_EQ(buffer.used_space(), capacity); + + safe_read(buffer, 10); + safe_read(buffer, 5); + safe_read(buffer, 6); + + EXPECT_EQ(buffer.used_space(), 0ul); +} + +TEST_CASE(full_read_aligned) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + for (unsigned i {}; i < capacity; ++i) + safe_write(buffer, i); + + EXPECT_EQ(buffer.used_space(), capacity); + EXPECT_EQ(buffer.empty_space(), 0ul); + + u8 const source[] = { 0, 1, 2 }; + + u8 result[] = { 0, 0, 0 }; + auto const bytes_or_error = buffer.read({ result, 3 }); + EXPECT_EQ(bytes_or_error.size(), 3ul); + + EXPECT_EQ(memcmp(source, result, 3), 0); +} + +TEST_CASE(full_read_non_aligned) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + for (unsigned i {}; i < capacity; ++i) + safe_write(buffer, i + 5); + + safe_read(buffer, 5); + + safe_write(buffer, 42); + + EXPECT_EQ(buffer.used_space(), capacity); + EXPECT_EQ(buffer.empty_space(), 0ul); + + u8 result[] = { 0, 0, 0 }; + auto const bytes = buffer.read({ result, 3 }); + EXPECT_EQ(bytes.size(), 3ul); + + u8 const source[] = { 6, 7, 42 }; + EXPECT_EQ(memcmp(source, result, 3), 0); +} + +TEST_CASE(full_write_aligned) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + u8 const source[] = { 12, 13, 14 }; + + auto written_bytes = buffer.write({ source, 3 }); + EXPECT_EQ(written_bytes, 3ul); + + EXPECT_EQ(buffer.used_space(), capacity); + EXPECT_EQ(buffer.empty_space(), 0ul); + + for (unsigned i {}; i < capacity; ++i) + safe_read(buffer, i + 12); + + EXPECT_EQ(buffer.used_space(), 0ul); +} + +TEST_CASE(full_write_non_aligned) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + safe_write(buffer, 10); + safe_read(buffer, 10); + + u8 const source[] = { 12, 13, 14 }; + + auto written_bytes = buffer.write({ source, 3 }); + EXPECT_EQ(written_bytes, 3ul); + + EXPECT_EQ(buffer.used_space(), capacity); + EXPECT_EQ(buffer.empty_space(), 0ul); + + for (unsigned i {}; i < capacity; ++i) + safe_read(buffer, i + 12); + + EXPECT_EQ(buffer.used_space(), 0ul); +} + +TEST_CASE(create_from_bytebuffer) +{ + u8 const source[] = { 2, 4, 6 }; + auto byte_buffer_or_error = ByteBuffer::copy(source, AK::array_size(source)); + EXPECT(!byte_buffer_or_error.is_error()); + auto byte_buffer = byte_buffer_or_error.release_value(); + + auto circular_buffer_or_error = CircularBuffer::create_initialized(move(byte_buffer)); + EXPECT(!circular_buffer_or_error.is_error()); + auto circular_buffer = circular_buffer_or_error.release_value(); + EXPECT_EQ(circular_buffer.used_space(), circular_buffer.capacity()); + EXPECT_EQ(circular_buffer.used_space(), 3ul); + + safe_read(circular_buffer, 2); + safe_read(circular_buffer, 4); + safe_read(circular_buffer, 6); +} + +TEST_CASE(discard) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + safe_write(buffer, 11); + safe_write(buffer, 12); + + safe_discard(buffer, 1); + + safe_read(buffer, 12); + + EXPECT_EQ(buffer.used_space(), 0ul); + EXPECT_EQ(buffer.empty_space(), capacity); +} + +TEST_CASE(discard_on_edge) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + safe_write(buffer, 11); + safe_write(buffer, 12); + safe_write(buffer, 13); + + safe_discard(buffer, 2); + + safe_write(buffer, 14); + safe_write(buffer, 15); + + safe_discard(buffer, 2); + + safe_read(buffer, 15); + + EXPECT_EQ(buffer.used_space(), 0ul); + EXPECT_EQ(buffer.empty_space(), capacity); +} + +TEST_CASE(discard_too_much) +{ + constexpr size_t capacity = 3; + auto buffer = create_circular_buffer(capacity); + + safe_write(buffer, 11); + safe_write(buffer, 12); + + safe_discard(buffer, 2); + + auto result = buffer.discard(2); + EXPECT(result.is_error()); +} + +TEST_CASE(offset_of) +{ + auto const source = "Well Hello Friends!"sv; + auto byte_buffer_or_error = ByteBuffer::copy(source.bytes()); + EXPECT(!byte_buffer_or_error.is_error()); + auto byte_buffer = byte_buffer_or_error.release_value(); + + auto circular_buffer_or_error = CircularBuffer::create_initialized(byte_buffer); + EXPECT(!circular_buffer_or_error.is_error()); + auto circular_buffer = circular_buffer_or_error.release_value(); + + auto result = circular_buffer.offset_of("Well"sv); + EXPECT(result.has_value()); + EXPECT_EQ(result.value(), 0ul); + + result = circular_buffer.offset_of("Hello"sv); + EXPECT(result.has_value()); + EXPECT_EQ(result.value(), 5ul); + + safe_discard(circular_buffer, 5); + + auto written_bytes = circular_buffer.write(byte_buffer.span().trim(5)); + EXPECT_EQ(written_bytes, 5ul); + + result = circular_buffer.offset_of("!Well"sv); + EXPECT(result.has_value()); + EXPECT_EQ(result.value(), 13ul); + + result = circular_buffer.offset_of("!Well"sv, 12); + EXPECT(!result.has_value()); +}