summaryrefslogtreecommitdiff
path: root/AK/MemoryStream.h
diff options
context:
space:
mode:
Diffstat (limited to 'AK/MemoryStream.h')
-rw-r--r--AK/MemoryStream.h299
1 files changed, 299 insertions, 0 deletions
diff --git a/AK/MemoryStream.h b/AK/MemoryStream.h
new file mode 100644
index 0000000000..96df59f3c9
--- /dev/null
+++ b/AK/MemoryStream.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/MemMem.h>
+#include <AK/Stream.h>
+#include <AK/Vector.h>
+
+namespace AK {
+
+class InputMemoryStream final : public InputStream {
+public:
+ InputMemoryStream(ReadonlyBytes bytes)
+ : m_bytes(bytes)
+ {
+ }
+
+ bool eof() const override { return m_offset >= m_bytes.size(); }
+
+ size_t read(Bytes bytes) override
+ {
+ const auto count = min(bytes.size(), remaining());
+ __builtin_memcpy(bytes.data(), m_bytes.data() + m_offset, count);
+ m_offset += count;
+ return count;
+ }
+
+ bool read_or_error(Bytes bytes) override
+ {
+ if (remaining() < bytes.size()) {
+ set_recoverable_error();
+ return false;
+ }
+
+ __builtin_memcpy(bytes.data(), m_bytes.data() + m_offset, bytes.size());
+ m_offset += bytes.size();
+ return true;
+ }
+
+ bool discard_or_error(size_t count) override
+ {
+ if (remaining() < count) {
+ set_recoverable_error();
+ return false;
+ }
+
+ m_offset += count;
+ return true;
+ }
+
+ void seek(size_t offset)
+ {
+ ASSERT(offset < m_bytes.size());
+ m_offset = offset;
+ }
+
+ u8 peek_or_error() const
+ {
+ if (remaining() == 0) {
+ set_recoverable_error();
+ return 0;
+ }
+
+ return m_bytes[m_offset];
+ }
+
+ // LEB128 is a variable-length encoding for integers
+ bool read_LEB128_unsigned(size_t& result)
+ {
+ const auto backup = m_offset;
+
+ result = 0;
+ size_t num_bytes = 0;
+ while (true) {
+ // Note. The implementation in AK::BufferStream::read_LEB128_unsigned read one
+ // past the end, this is fixed here.
+ if (eof()) {
+ m_offset = backup;
+ set_recoverable_error();
+ return false;
+ }
+
+ const u8 byte = m_bytes[m_offset];
+ result = (result) | (static_cast<size_t>(byte & ~(1 << 7)) << (num_bytes * 7));
+ ++m_offset;
+ if (!(byte & (1 << 7)))
+ break;
+ ++num_bytes;
+ }
+
+ return true;
+ }
+
+ // LEB128 is a variable-length encoding for integers
+ bool read_LEB128_signed(ssize_t& result)
+ {
+ const auto backup = m_offset;
+
+ result = 0;
+ size_t num_bytes = 0;
+ u8 byte = 0;
+
+ do {
+ // Note. The implementation in AK::BufferStream::read_LEB128_unsigned read one
+ // past the end, this is fixed here.
+ if (eof()) {
+ m_offset = backup;
+ set_recoverable_error();
+ return false;
+ }
+
+ byte = m_bytes[m_offset];
+ result = (result) | (static_cast<size_t>(byte & ~(1 << 7)) << (num_bytes * 7));
+ ++m_offset;
+ ++num_bytes;
+ } while (byte & (1 << 7));
+
+ if (num_bytes * 7 < sizeof(size_t) * 4 && (byte & 0x40)) {
+ // sign extend
+ result |= ((size_t)(-1) << (num_bytes * 7));
+ }
+
+ return true;
+ }
+
+ ReadonlyBytes bytes() const { return m_bytes; }
+ size_t offset() const { return m_offset; }
+ size_t remaining() const { return m_bytes.size() - m_offset; }
+
+private:
+ ReadonlyBytes m_bytes;
+ size_t m_offset { 0 };
+};
+
+// All data written to this stream can be read from it. Reading and writing is done
+// using different offsets, meaning that it is not necessary to seek to the start
+// before reading; this behaviour differs from BufferStream.
+class DuplexMemoryStream final : public DuplexStream {
+public:
+ static constexpr size_t chunk_size = 4 * 1024;
+
+ bool eof() const override { return m_write_offset == m_read_offset; }
+
+ bool discard_or_error(size_t count) override
+ {
+ if (m_write_offset - m_read_offset < count) {
+ set_recoverable_error();
+ return false;
+ }
+
+ m_read_offset += count;
+ try_discard_chunks();
+ return true;
+ }
+
+ Optional<size_t> offset_of(ReadonlyBytes value) const
+ {
+ if (value.size() > remaining())
+ return {};
+
+ // First, find which chunk we're in.
+ auto chunk_index = (m_read_offset - m_base_offset) / chunk_size;
+ auto last_written_chunk_index = (m_write_offset - m_base_offset) / chunk_size;
+ auto first_chunk_index = chunk_index;
+ auto last_written_chunk_offset = m_write_offset % chunk_size;
+ auto first_chunk_offset = m_read_offset % chunk_size;
+ size_t last_chunk_offset = 0;
+ auto found_value = false;
+
+ for (; chunk_index <= last_written_chunk_index; ++chunk_index) {
+ auto chunk_bytes = m_chunks[chunk_index].bytes();
+ size_t chunk_offset = 0;
+ if (chunk_index == last_written_chunk_index) {
+ chunk_bytes = chunk_bytes.slice(0, last_written_chunk_offset);
+ }
+ if (chunk_index == first_chunk_index) {
+ chunk_bytes = chunk_bytes.slice(first_chunk_offset);
+ chunk_offset = first_chunk_offset;
+ }
+
+ // See if 'value' is in this chunk,
+ auto position = AK::memmem(chunk_bytes.data(), chunk_bytes.size(), value.data(), value.size());
+ if (!position)
+ continue; // Not in this chunk either :(
+
+ // We found it!
+ found_value = true;
+ last_chunk_offset = (const u8*)position - chunk_bytes.data() + chunk_offset;
+ break;
+ }
+
+ if (found_value) {
+ if (first_chunk_index == chunk_index)
+ return last_chunk_offset - first_chunk_offset;
+
+ return (chunk_index - first_chunk_index) * chunk_size + last_chunk_offset - first_chunk_offset;
+ }
+
+ // No dice.
+ return {};
+ }
+
+ size_t read(Bytes bytes) override
+ {
+ size_t nread = 0;
+ while (bytes.size() - nread > 0 && m_write_offset - m_read_offset - nread > 0) {
+ const auto chunk_index = (m_read_offset - m_base_offset) / chunk_size;
+ const auto chunk_bytes = m_chunks[chunk_index].bytes().slice(m_read_offset % chunk_size).trim(m_write_offset - m_read_offset - nread);
+ nread += chunk_bytes.copy_trimmed_to(bytes.slice(nread));
+ }
+
+ m_read_offset += nread;
+
+ try_discard_chunks();
+
+ return nread;
+ }
+
+ bool read_or_error(Bytes bytes) override
+ {
+ if (m_write_offset - m_read_offset < bytes.size()) {
+ set_recoverable_error();
+ return false;
+ }
+
+ read(bytes);
+ return true;
+ }
+
+ size_t write(ReadonlyBytes bytes) override
+ {
+ size_t nwritten = 0;
+ while (bytes.size() - nwritten > 0) {
+ if ((m_write_offset + nwritten) % chunk_size == 0)
+ m_chunks.append(ByteBuffer::create_uninitialized(chunk_size));
+
+ nwritten += bytes.copy_trimmed_to(m_chunks.last().bytes().slice(m_write_offset % chunk_size));
+ }
+
+ m_write_offset += nwritten;
+ return nwritten;
+ }
+
+ bool write_or_error(ReadonlyBytes bytes) override
+ {
+ write(bytes);
+ return true;
+ }
+
+ size_t roffset() const { return m_read_offset; }
+ size_t woffset() const { return m_write_offset; }
+
+ size_t remaining() const { return m_write_offset - m_read_offset; }
+
+private:
+ void try_discard_chunks()
+ {
+ while (m_read_offset - m_base_offset >= chunk_size) {
+ m_chunks.take_first();
+ m_base_offset += chunk_size;
+ }
+ }
+
+ Vector<ByteBuffer> m_chunks;
+ size_t m_write_offset { 0 };
+ size_t m_read_offset { 0 };
+ size_t m_base_offset { 0 };
+};
+
+}
+
+using AK::DuplexMemoryStream;
+using AK::InputMemoryStream;
+using AK::InputStream;