diff --git a/AK/MemoryStream.h b/AK/MemoryStream.h index 4211bc0284e..fba629bee2e 100644 --- a/AK/MemoryStream.h +++ b/AK/MemoryStream.h @@ -1,11 +1,13 @@ /* * Copyright (c) 2021, kleines Filmröllchen . + * Copyright (c) 2023, Sam Atkins . * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include #include @@ -34,6 +36,39 @@ public: size_t offset() const; size_t remaining() const; + /// Read a value, but referring to the stream's underlying data instead of copying it. + /// Of course, only use this if you know the lifetime of the data will exceed the value's. + // FIXME: Would be nicer to be able to return T& but Variant (and thus ErrorOr) can't hold references. + template + requires(Traits::is_trivially_serializable()) + ErrorOr read_in_place() + { + if constexpr (!IsConst) { + if (!m_writing_enabled) + return Error::from_string_view_or_print_error_and_return_errno("Tried to obtain a non-const reference from a read-only FixedMemoryStream"sv, EINVAL); + } + + T* value = reinterpret_cast(m_bytes.offset_pointer(m_offset)); + TRY(discard(sizeof(T))); + return value; + } + + /// Read a span of values, referring to the stream's underlying data instead of copying it. + /// Of course, only use this if you know the lifetime of the data will exceed the span's. + template + requires(Traits::is_trivially_serializable()) + ErrorOr> read_in_place(size_t count) + { + if constexpr (!IsConst) { + if (!m_writing_enabled) + return Error::from_string_view_or_print_error_and_return_errno("Tried to obtain a non-const span from a read-only FixedMemoryStream"sv, EINVAL); + } + + Span span { m_bytes.offset_pointer(m_offset), count }; + TRY(discard(sizeof(T) * count)); + return span; + } + private: Bytes m_bytes; size_t m_offset { 0 }; diff --git a/Tests/AK/TestMemoryStream.cpp b/Tests/AK/TestMemoryStream.cpp index cda14cb71c5..3a01076bd70 100644 --- a/Tests/AK/TestMemoryStream.cpp +++ b/Tests/AK/TestMemoryStream.cpp @@ -243,3 +243,30 @@ TEST_CASE(fixed_memory_truncate) EXPECT(stream.truncate(999).is_error()); } + +TEST_CASE(fixed_memory_read_in_place) +{ + constexpr auto some_words = "These are some words"sv; + + FixedMemoryStream readonly_stream { ReadonlyBytes { some_words.bytes() } }; + + // Trying to read mutable values from a read-only stream should fail. + EXPECT(readonly_stream.read_in_place(1).is_error()); + EXPECT_EQ(readonly_stream.offset(), 0u); + + // Reading const values should succeed. + auto characters = TRY_OR_FAIL(readonly_stream.read_in_place(20)); + EXPECT_EQ(characters, some_words.bytes()); + EXPECT(readonly_stream.is_eof()); + + FixedMemoryStream mutable_stream { Bytes { const_cast(some_words.bytes().data()), some_words.bytes().size() }, true }; + // Trying to read mutable values from a mutable stream should succeed. + TRY_OR_FAIL(mutable_stream.read_in_place(1)); + EXPECT_EQ(mutable_stream.offset(), 1u); + TRY_OR_FAIL(mutable_stream.seek(0)); + + // Reading const values should succeed. + auto characters_again = TRY_OR_FAIL(mutable_stream.read_in_place(20)); + EXPECT_EQ(characters_again, some_words.bytes()); + EXPECT(mutable_stream.is_eof()); +}