Skip to content

Commit

Permalink
AK: Port LEB128 to the new AK::Stream
Browse files Browse the repository at this point in the history
  • Loading branch information
timschumi authored and ADKaster committed Feb 5, 2023
1 parent 31f5985 commit 787f4d6
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 209 deletions.
7 changes: 0 additions & 7 deletions AK/DeprecatedMemoryStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include <AK/ByteBuffer.h>
#include <AK/DeprecatedStream.h>
#include <AK/LEB128.h>
#include <AK/MemMem.h>
#include <AK/Vector.h>

Expand Down Expand Up @@ -74,12 +73,6 @@ class DeprecatedInputMemoryStream final : public DeprecatedInputStream {
return m_bytes[m_offset];
}

template<typename ValueType>
bool read_LEB128_unsigned(ValueType& result) { return LEB128::read_unsigned(*this, result); }

template<typename ValueType>
bool read_LEB128_signed(ValueType& result) { return LEB128::read_signed(*this, result); }

ReadonlyBytes bytes() const { return m_bytes; }
size_t offset() const { return m_offset; }
size_t remaining() const { return m_bytes.size() - m_offset; }
Expand Down
42 changes: 17 additions & 25 deletions AK/LEB128.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,44 @@

#pragma once

#include <AK/DeprecatedStream.h>
#include <AK/NumericLimits.h>
#include <AK/Stream.h>
#include <AK/Types.h>

namespace AK {

struct LEB128 {
template<typename ValueType = size_t>
static bool read_unsigned(DeprecatedInputStream& stream, ValueType& result)
static ErrorOr<void> read_unsigned(AK::Stream& stream, ValueType& result)
{
result = 0;
size_t num_bytes = 0;
while (true) {
if (stream.unreliable_eof()) {
stream.set_fatal_error();
return false;
}
u8 byte = 0;
stream >> byte;
if (stream.has_any_error())
return false;
if (stream.is_eof())
return Error::from_string_literal("Stream reached end-of-file while reading LEB128 value");

auto byte = TRY(stream.read_value<u8>());

ValueType masked_byte = byte & ~(1 << 7);
bool const shift_too_large_for_result = num_bytes * 7 > sizeof(ValueType) * 8;
if (shift_too_large_for_result)
return false;
return Error::from_string_literal("Read value contains more bits than fit the chosen ValueType");

bool const shift_too_large_for_byte = ((masked_byte << (num_bytes * 7)) >> (num_bytes * 7)) != masked_byte;
if (shift_too_large_for_byte)
return false;
return Error::from_string_literal("Read byte is too large to fit the chosen ValueType");

result = (result) | (masked_byte << (num_bytes * 7));
if (!(byte & (1 << 7)))
break;
++num_bytes;
}

return true;
return {};
}

template<typename ValueType = ssize_t>
static bool read_signed(DeprecatedInputStream& stream, ValueType& result)
static ErrorOr<void> read_signed(AK::Stream& stream, ValueType& result)
{
// Note: We read into a u64 to simplify the parsing logic;
// result is range checked into ValueType after parsing.
Expand All @@ -59,24 +55,20 @@ struct LEB128 {
result = 0;

do {
if (stream.unreliable_eof()) {
stream.set_fatal_error();
return false;
}
if (stream.is_eof())
return Error::from_string_literal("Stream reached end-of-file while reading LEB128 value");

stream >> byte;
if (stream.has_any_error())
return false;
byte = TRY(stream.read_value<u8>());

// note: 64 bit assumptions!
u64 masked_byte = byte & ~(1 << 7);
bool const shift_too_large_for_result = num_bytes * 7 >= 64;
if (shift_too_large_for_result)
return false;
return Error::from_string_literal("Read value contains more bits than fit the chosen ValueType");

bool const shift_too_large_for_byte = (num_bytes * 7) == 63 && masked_byte != 0x00 && masked_byte != 0x7Fu;
if (shift_too_large_for_byte)
return false;
return Error::from_string_literal("Read byte is too large to fit the chosen ValueType");

temp = (temp) | (masked_byte << (num_bytes * 7));
++num_bytes;
Expand All @@ -90,12 +82,12 @@ struct LEB128 {
// Now that we've accumulated into an i64, make sure it fits into result
if constexpr (sizeof(ValueType) < sizeof(u64)) {
if (temp > NumericLimits<ValueType>::max() || temp < NumericLimits<ValueType>::min())
return false;
return Error::from_string_literal("Temporary value does not fit the result type");
}

result = static_cast<ValueType>(temp);

return true;
return {};
}
};

Expand Down
126 changes: 51 additions & 75 deletions Tests/AK/TestLEB128.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/DeprecatedMemoryStream.h>
#include <AK/LEB128.h>
#include <AK/MemoryStream.h>
#include <AK/NumericLimits.h>
#include <LibTest/TestCase.h>

Expand All @@ -14,48 +14,42 @@ TEST_CASE(single_byte)
u32 output = {};
i32 output_signed = {};
u8 buf[] = { 0x00 };
DeprecatedInputMemoryStream stream({ buf, sizeof(buf) });
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { buf, sizeof(buf) }));

// less than/eq 0b0011_1111, signed == unsigned == raw byte
for (u8 i = 0u; i <= 0x3F; ++i) {
buf[0] = i;

stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, output));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, output).is_error());
EXPECT_EQ(output, i);
EXPECT(!stream.handle_any_error());

stream.seek(0);
EXPECT(LEB128::read_signed(stream, output_signed));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, output_signed).is_error());
EXPECT_EQ(output_signed, i);
EXPECT(!stream.handle_any_error());
}

// 0b0100_0000 to 0b0111_1111 unsigned == byte, signed = {{ 26'b(-1), 6'b(byte) }}
for (u8 i = 0x40u; i < 0x80; ++i) {
buf[0] = i;

stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, output));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, output).is_error());
EXPECT_EQ(output, i);
EXPECT(!stream.handle_any_error());

stream.seek(0);
EXPECT(LEB128::read_signed(stream, output_signed));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, output_signed).is_error());
EXPECT_EQ(output_signed, (i | (-1 & (~0x3F))));
EXPECT(!stream.handle_any_error());
}
// MSB set, but input too short
for (u16 i = 0x80; i <= 0xFF; ++i) {
buf[0] = static_cast<u8>(i);

stream.seek(0);
EXPECT(!LEB128::read_unsigned(stream, output));
EXPECT(stream.handle_any_error());
MUST(stream->seek(0));
EXPECT(LEB128::read_unsigned(*stream, output).is_error());

stream.seek(0);
EXPECT(!LEB128::read_signed(stream, output_signed));
EXPECT(stream.handle_any_error());
MUST(stream->seek(0));
EXPECT(LEB128::read_signed(*stream, output_signed).is_error());
}
}

Expand All @@ -64,7 +58,7 @@ TEST_CASE(two_bytes)
u32 output = {};
i32 output_signed = {};
u8 buf[] = { 0x00, 0x1 };
DeprecatedInputMemoryStream stream({ buf, sizeof(buf) });
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { buf, sizeof(buf) }));

// Only test with first byte expecting more, otherwise equivalent to single byte case
for (u16 i = 0x80; i <= 0xFF; ++i) {
Expand All @@ -74,43 +68,37 @@ TEST_CASE(two_bytes)
for (u8 j = 0u; j <= 0x3F; ++j) {
buf[1] = j;

stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, output));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, output).is_error());
EXPECT_EQ(output, (static_cast<u32>(j) << 7) + (i & 0x7F));
EXPECT(!stream.handle_any_error());

stream.seek(0);
EXPECT(LEB128::read_signed(stream, output_signed));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, output_signed).is_error());
EXPECT_EQ(output_signed, (static_cast<i32>(j) << 7) + (i & 0x7F));
EXPECT(!stream.handle_any_error());
}

// 0b0100_0000 to 0b0111_1111: unsigned == (j << 7) + (7 MSB of i), signed == {{ 19'b(-1), 6'b(j), 7'b(i) }}
for (u8 j = 0x40u; j < 0x80; ++j) {
buf[1] = j;

stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, output));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, output).is_error());
EXPECT_EQ(output, (static_cast<u32>(j) << 7) + (i & 0x7F));
EXPECT(!stream.handle_any_error());

stream.seek(0);
EXPECT(LEB128::read_signed(stream, output_signed));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, output_signed).is_error());
EXPECT_EQ(output_signed, ((static_cast<i32>(j) << 7) + (i & 0x7F)) | (-1 & (~0x3FFF)));
EXPECT(!stream.handle_any_error());
}

// MSB set on last byte, but input too short
for (u16 j = 0x80; j <= 0xFF; ++j) {
buf[1] = static_cast<u8>(j);

stream.seek(0);
EXPECT(!LEB128::read_unsigned(stream, output));
EXPECT(stream.handle_any_error());
MUST(stream->seek(0));
EXPECT(LEB128::read_unsigned(*stream, output).is_error());

stream.seek(0);
EXPECT(!LEB128::read_signed(stream, output_signed));
EXPECT(stream.handle_any_error());
MUST(stream->seek(0));
EXPECT(LEB128::read_signed(*stream, output_signed).is_error());
}
}
}
Expand All @@ -120,31 +108,27 @@ TEST_CASE(overflow_sizeof_output_unsigned)
u8 u32_max_plus_one[] = { 0x80, 0x80, 0x80, 0x80, 0x10 };
{
u32 out = 0;
DeprecatedInputMemoryStream stream({ u32_max_plus_one, sizeof(u32_max_plus_one) });
EXPECT(!LEB128::read_unsigned(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { u32_max_plus_one, sizeof(u32_max_plus_one) }));
EXPECT(LEB128::read_unsigned(*stream, out).is_error());
EXPECT_EQ(out, 0u);
EXPECT(!stream.handle_any_error());

u64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, out64).is_error());
EXPECT_EQ(out64, static_cast<u64>(NumericLimits<u32>::max()) + 1);
EXPECT(!stream.handle_any_error());
}

u8 u32_max[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F };
{
u32 out = 0;
DeprecatedInputMemoryStream stream({ u32_max, sizeof(u32_max) });
EXPECT(LEB128::read_unsigned(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { u32_max, sizeof(u32_max) }));
EXPECT(!LEB128::read_unsigned(*stream, out).is_error());
EXPECT_EQ(out, NumericLimits<u32>::max());
EXPECT(!stream.handle_any_error());

u64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_unsigned(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_unsigned(*stream, out64).is_error());
EXPECT_EQ(out64, NumericLimits<u32>::max());
EXPECT(!stream.handle_any_error());
}
}

Expand All @@ -153,60 +137,52 @@ TEST_CASE(overflow_sizeof_output_signed)
u8 i32_max_plus_one[] = { 0x80, 0x80, 0x80, 0x80, 0x08 };
{
i32 out = 0;
DeprecatedInputMemoryStream stream({ i32_max_plus_one, sizeof(i32_max_plus_one) });
EXPECT(!LEB128::read_signed(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { i32_max_plus_one, sizeof(i32_max_plus_one) }));
EXPECT(LEB128::read_signed(*stream, out).is_error());
EXPECT_EQ(out, 0);
EXPECT(!stream.handle_any_error());

i64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_signed(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, out64).is_error());
EXPECT_EQ(out64, static_cast<i64>(NumericLimits<i32>::max()) + 1);
EXPECT(!stream.handle_any_error());
}

u8 i32_max[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 };
{
i32 out = 0;
DeprecatedInputMemoryStream stream({ i32_max, sizeof(i32_max) });
EXPECT(LEB128::read_signed(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { i32_max, sizeof(i32_max) }));
EXPECT(!LEB128::read_signed(*stream, out).is_error());
EXPECT_EQ(out, NumericLimits<i32>::max());
EXPECT(!stream.handle_any_error());

i64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_signed(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, out64).is_error());
EXPECT_EQ(out64, NumericLimits<i32>::max());
EXPECT(!stream.handle_any_error());
}

u8 i32_min_minus_one[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x77 };
{
i32 out = 0;
DeprecatedInputMemoryStream stream({ i32_min_minus_one, sizeof(i32_min_minus_one) });
EXPECT(!LEB128::read_signed(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { i32_min_minus_one, sizeof(i32_min_minus_one) }));
EXPECT(LEB128::read_signed(*stream, out).is_error());
EXPECT_EQ(out, 0);
EXPECT(!stream.handle_any_error());

i64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_signed(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, out64).is_error());
EXPECT_EQ(out64, static_cast<i64>(NumericLimits<i32>::min()) - 1);
EXPECT(!stream.handle_any_error());
}

u8 i32_min[] = { 0x80, 0x80, 0x80, 0x80, 0x78 };
{
i32 out = 0;
DeprecatedInputMemoryStream stream({ i32_min, sizeof(i32_min) });
EXPECT(LEB128::read_signed(stream, out));
auto stream = MUST(FixedMemoryStream::construct(ReadonlyBytes { i32_min, sizeof(i32_min) }));
EXPECT(!LEB128::read_signed(*stream, out).is_error());
EXPECT_EQ(out, NumericLimits<i32>::min());
EXPECT(!stream.handle_any_error());

i64 out64 = 0;
stream.seek(0);
EXPECT(LEB128::read_signed(stream, out64));
MUST(stream->seek(0));
EXPECT(!LEB128::read_signed(*stream, out64).is_error());
EXPECT_EQ(out64, NumericLimits<i32>::min());
EXPECT(!stream.handle_any_error());
}
}
Loading

0 comments on commit 787f4d6

Please sign in to comment.