Skip to content

Commit

Permalink
Change varint functions from throwing to std::expected
Browse files Browse the repository at this point in the history
  • Loading branch information
janhenke committed Mar 31, 2024
1 parent f14a46b commit fb83e06
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 40 deletions.
48 changes: 29 additions & 19 deletions common/src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,62 @@

namespace libmumble_protocol::common {

std::tuple<std::size_t, std::int64_t> DecodeVariableInteger(std::span<const std::byte> buffer) {
std::expected<std::tuple<std::size_t, std::int64_t>, std::u8string>
DecodeVariableInteger(std::span<const std::byte> buffer) {

const std::size_t spanSize = std::size(buffer);

if (buffer.empty()) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (buffer.empty()) { std::unexpected{u8"Input buffer contains too few elements."}; }

if ((buffer[0] & std::byte{0x80}) == std::byte{0x00}) {
return {1, std::bit_cast<std::uint8_t>(buffer[0] & std::byte{0x7f})};
return {{1, std::bit_cast<std::uint8_t>(buffer[0] & std::byte{0x7f})}};
} else if ((buffer[0] & std::byte{0xc0}) == std::byte{0x80}) {
if (spanSize < 2) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (spanSize < 2) { std::unexpected{u8"Input buffer contains too few elements."}; }
std::uint16_t result = 0;
std::memcpy(&result, buffer.data(), 2);
return {2, SwapNetworkBytes(result) & 0x3fff};
return {{2, SwapNetworkBytes(result) & 0x3fff}};
} else if ((buffer[0] & std::byte{0xe0}) == std::byte{0xc0}) {
if (spanSize < 3) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (spanSize < 3) { std::unexpected{u8"Input buffer contains too few elements."}; }
std::uint32_t result = 0;
std::memcpy(&result, buffer.data(), 3);
result = SwapNetworkBytes(result);
return {3, (result >> 8) & 0x1f'ffff};
return {{3, (result >> 8) & 0x1f'ffff}};
} else if ((buffer[0] & std::byte{0xf0}) == std::byte{0xe0}) {
if (spanSize < 4) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (spanSize < 4) { std::unexpected{u8"Input buffer contains too few elements."}; }
std::uint32_t result = 0;
std::memcpy(&result, buffer.data(), 4);
return {4, SwapNetworkBytes(result) & 0x0fff'ffff};
return {{4, SwapNetworkBytes(result) & 0x0fff'ffff}};
} else if ((buffer[0] & std::byte{0xf0}) == std::byte{0xf0}) {

// handle recursive call
if ((buffer[0] & std::byte{0xfc}) == std::byte{0xf8}) {
const auto [count, result] = DecodeVariableInteger(buffer.last(spanSize - 1));
return {count + 1, ~result};
return DecodeVariableInteger(buffer.last(spanSize - 1))
.transform([](std::tuple<std::size_t, std::int64_t> tuple) {
const auto [count, result] = tuple;
return std::make_tuple(count + 1, ~result);
});
}

std::int32_t i32 = 0;
std::int64_t i64 = 0;
switch (std::to_integer<std::uint8_t>(buffer[0] & std::byte{0xfc})) {
case 0xf0:
if (spanSize < 5) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (spanSize < 5) { std::unexpected{u8"Input buffer contains too few elements."}; }
std::memcpy(&i32, buffer.data() + 1, 4);
return {5, SwapNetworkBytes(i32)};
return {{5, SwapNetworkBytes(i32)}};
case 0xf4:
if (spanSize < 9) { throw std::out_of_range{"Input buffer contains too few elements."}; }
if (spanSize < 9) { std::unexpected{u8"Input buffer contains too few elements."}; }
std::memcpy(&i64, buffer.data() + 1, 8);
return {9, SwapNetworkBytes(i64)};
case 0xfc: return {1, ~std::bit_cast<std::uint8_t>(buffer[0] & std::byte{0x03})};
return {{9, SwapNetworkBytes(i64)}};
case 0xfc: return {{1, ~std::bit_cast<std::uint8_t>(buffer[0] & std::byte{0x03})}};
}
}
return {0, 0};
return {{0, 0}};
}

std::size_t EncodeVariableInteger(std::span<std::byte> buffer, std::int64_t value) {
std::expected<std::size_t, std::u8string> EncodeVariableInteger(std::span<std::byte> buffer, std::int64_t value) {

if (buffer.empty()) { throw std::out_of_range{"Invalid range specified."}; }
if (buffer.empty()) { std::unexpected{u8"Invalid range specified."}; }

std::int64_t input = value;
std::size_t offset = 0;
Expand All @@ -83,35 +87,41 @@ std::size_t EncodeVariableInteger(std::span<std::byte> buffer, std::int64_t valu

if (input < 0x80) {
// Need top bit clear
if (buffer.size() < offset + 1) { return std::unexpected{u8"Destination buffer too small."}; }
const auto temp = static_cast<std::uint8_t>(input);
std::memcpy(buffer.data() + offset, &temp, 1);
return offset + 1;
} else if (input < 0x4000) {
// Need top two bits clear
if (buffer.size() < offset + 2) { return std::unexpected{u8"Destination buffer too small."}; }
const auto temp = SwapNetworkBytes(static_cast<std::uint16_t>(input));
std::memcpy(buffer.data() + offset, &temp, 2);
buffer[offset] = buffer[offset] | std::byte{0x80};
return offset + 2;
} else if (input < 0x200000) {
// Need top three bits clear
if (buffer.size() < offset + 3) { return std::unexpected{u8"Destination buffer too small."}; }
const auto temp = SwapNetworkBytes(static_cast<std::uint32_t>(input << 8));
std::memcpy(buffer.data() + offset, &temp, 3);
buffer[offset] = buffer[offset] | std::byte{0xC0};
return offset + 3;
} else if (input < 0x10000000) {
// Need top four bits clear
if (buffer.size() < offset + 4) { return std::unexpected{u8"Destination buffer too small."}; }
const auto temp = SwapNetworkBytes(static_cast<std::uint32_t>(input));
std::memcpy(buffer.data() + offset, &temp, 4);
buffer[offset] = buffer[offset] | std::byte{0xE0};
return offset + 4;
} else if (input < 0x100000000LL) {
// It's a full 32-bit integer.
if (buffer.size() < offset + 5) { return std::unexpected{u8"Destination buffer too small."}; }
buffer[offset] = std::byte(0xF0);
const auto temp = SwapNetworkBytes(static_cast<std::uint32_t>(input));
std::memcpy(buffer.data() + offset + 1, &temp, 4);
return offset + 5;
} else {
// It's a 64-bit value.
if (buffer.size() < offset + 9) { return std::unexpected{u8"Destination buffer too small."}; }
buffer[offset] = std::byte(0xF4);
const auto temp = SwapNetworkBytes(static_cast<std::int64_t>(input));
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::int64_t)>>(temp);
Expand Down
9 changes: 6 additions & 3 deletions common/src/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
#include <concepts>
#include <cstdint>
#include <cstring>
#include <expected>
#include <span>
#include <stdexcept>
#include <string>
#include <tuple>

namespace libmumble_protocol::common {
Expand All @@ -25,9 +27,10 @@ MUMBLE_PROTOCOL_COMMON_EXPORT auto SwapNetworkBytes(std::integral auto const i)
}
}

MUMBLE_PROTOCOL_COMMON_EXPORT std::tuple<std::size_t, std::int64_t>
DecodeVariableInteger(std::span<const std::byte> buffer);
MUMBLE_PROTOCOL_COMMON_EXPORT std::expected<std::tuple<std::size_t, std::int64_t>, std::u8string>
DecodeVariableInteger(std::span<const std::byte> tuple);

MUMBLE_PROTOCOL_COMMON_EXPORT std::size_t EncodeVariableInteger(std::span<std::byte> buffer, std::int64_t value);
MUMBLE_PROTOCOL_COMMON_EXPORT std::expected<std::size_t, std::u8string>
EncodeVariableInteger(std::span<std::byte> buffer, std::int64_t value);

}// namespace libmumble_protocol::common
36 changes: 18 additions & 18 deletions common/test/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b0111'1111}};
const std::int64_t expected = 0x000007fLL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -24,7 +24,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b1011'1111}, std::byte{0x00}};
const std::int64_t expected = 0x00003f00LL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -34,7 +34,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b1101'1111}, std::byte{0x00}, std::byte{0xff}};
const std::int64_t expected = 0x001f00ffLL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -44,7 +44,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b1110'1111}, std::byte{0x00}, std::byte{0xff}, std::byte{0x00}};
const std::int64_t expected = 0x0f00ff00LL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -55,7 +55,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
std::array{std::byte{0b1111'0000}, std::byte{0x00}, std::byte{0xff}, std::byte{0x00}, std::byte{0xff}};
const std::int64_t expected = 0x00ff00ffULL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -67,7 +67,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
std::byte{0x00}, std::byte{0xff}, std::byte{0x00}, std::byte{0xff}};
const std::int64_t expected = 0x00ff00ff00ff00ffULL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -77,7 +77,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b1111'1000}, std::byte{0b0111'1111}};
const std::int64_t expected = ~0x0000007fLL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -87,7 +87,7 @@ TEST_CASE("Test the mumble protocol variable integer decode function", "[common]
const auto data = std::array{std::byte{0b1111'1101}};
const std::int64_t expected = ~0x0000001LL;

const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data);
const auto [bytes, result] = libmumble_protocol::common::DecodeVariableInteger(data).value();

REQUIRE(bytes == std::size(data));
REQUIRE(result == expected);
Expand All @@ -101,7 +101,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b0111'1111}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -112,7 +112,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1011'1111}, std::byte{0x00}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -123,7 +123,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1101'1111}, std::byte{0x00}, std::byte{0xff}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -134,7 +134,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1110'1111}, std::byte{0x00}, std::byte{0xff}, std::byte{0x00}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -146,7 +146,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
std::array{std::byte{0b1111'0000}, std::byte{0xff}, std::byte{0x00}, std::byte{0xff}, std::byte{0x00}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -159,7 +159,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
std::byte{0xff}, std::byte{0x00}, std::byte{0xff}, std::byte{0x00}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -170,7 +170,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1111'1101}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -181,7 +181,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1111'1000}, std::byte{0b0111'1111}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -192,7 +192,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1111'1101}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand All @@ -203,7 +203,7 @@ TEST_CASE("Test the mumble protocol variable integer encode function", "[common]
const auto expectedData = std::array{std::byte{0b1111'1000}, std::byte{4}};
std::array<std::byte, sizeof(expectedData)> buffer{};

const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value);
const auto count = libmumble_protocol::common::EncodeVariableInteger(buffer, value).value();

REQUIRE(count == std::size(expectedData));
REQUIRE(buffer == expectedData);
Expand Down

0 comments on commit fb83e06

Please sign in to comment.