Skip to content

Commit

Permalink
common/util.cpp: Optimize variable int de-/encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
janhenke committed Dec 30, 2023
1 parent 3110b5d commit f29a16e
Showing 1 changed file with 57 additions and 74 deletions.
131 changes: 57 additions & 74 deletions common/src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,82 +7,68 @@
#include <array>
#include <bit>
#include <cstddef>
#include <cstring>
#include <stdexcept>

namespace libmumble_protocol::common {

std::tuple<std::size_t, std::int64_t> decodeVariableInteger(std::span<const std::byte> span) {
// n.b. this code assumes little endian
std::tuple<std::size_t, std::int64_t> decodeVariableInteger(std::span<const std::byte> buffer) {

const std::size_t span_size = std::size(span);
std::array<std::byte, sizeof(std::int64_t)> buffer{};
buffer.fill(std::byte{0x00});
const std::size_t spanSize = std::size(buffer);

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

if ((span[0] & std::byte{0x80}) == std::byte{0x00}) {
buffer[0] = span[0] & std::byte{0x7f};
return {1, std::bit_cast<std::int64_t>(buffer)};
} else if ((span[0] & std::byte{0xc0}) == std::byte{0x80}) {
if (span_size < 2) { throw std::out_of_range{"Input span contains too few elements."}; }
buffer[1] = span[0] & std::byte{0x3f};
buffer[0] = span[1];
return {2, std::bit_cast<std::int64_t>(buffer)};
} else if ((span[0] & std::byte{0xe0}) == std::byte{0xc0}) {
if (span_size < 3) { throw std::out_of_range{"Input span contains too few elements."}; }
buffer[2] = span[0] & std::byte{0x1f};
buffer[1] = span[1];
buffer[0] = span[2];
return {3, std::bit_cast<std::int64_t>(buffer)};
} else if ((span[0] & std::byte{0xf0}) == std::byte{0xe0}) {
if (span_size < 4) { throw std::out_of_range{"Input span contains too few elements."}; }
buffer[3] = span[0] & std::byte{0x0f};
buffer[2] = span[1];
buffer[1] = span[2];
buffer[0] = span[3];
return {4, std::bit_cast<std::int64_t>(buffer)};
} else if ((span[0] & std::byte{0xf0}) == std::byte{0xf0}) {
if ((buffer[0] & std::byte{0x80}) == std::byte{0x00}) {
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."}; }
std::uint16_t result = 0;
std::memcpy(&result, buffer.data(), 2);
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."}; }
std::uint32_t result = 0;
std::memcpy(&result, buffer.data(), 3);
result = swapNetworkBytes(result);
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."}; }
std::uint32_t result = 0;
std::memcpy(&result, buffer.data(), 4);
return {4, swapNetworkBytes(result) & 0x0fff'ffff};
} else if ((buffer[0] & std::byte{0xf0}) == std::byte{0xf0}) {

// handle recursive call
if ((span[0] & std::byte{0xfc}) == std::byte{0xf8}) {
const auto [count, result] = decodeVariableInteger(span.last(span_size - 1));
if ((buffer[0] & std::byte{0xfc}) == std::byte{0xf8}) {
const auto [count, result] = decodeVariableInteger(buffer.last(spanSize - 1));
return {count + 1, ~result};
}

switch (std::to_integer<std::uint8_t>(span[0] & std::byte{0xfc})) {
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 (span_size < 5) { throw std::out_of_range{"Input span contains too few elements."}; }
buffer[3] = span[1];
buffer[2] = span[2];
buffer[1] = span[3];
buffer[0] = span[4];
return {5, std::bit_cast<std::int64_t>(buffer)};
if (spanSize < 5) { throw std::out_of_range{"Input buffer contains too few elements."}; }
std::memcpy(&i32, buffer.data() + 1, 4);
return {5, swapNetworkBytes(i32)};
case 0xf4:
if (span_size < 9) { throw std::out_of_range{"Input span contains too few elements."}; }
buffer[7] = span[1];
buffer[6] = span[2];
buffer[5] = span[3];
buffer[4] = span[4];
buffer[3] = span[5];
buffer[2] = span[6];
buffer[1] = span[7];
buffer[0] = span[8];
return {9, std::bit_cast<std::int64_t>(buffer)};
case 0xfc: buffer[0] = span[0] & std::byte{0x03}; return {1, ~std::bit_cast<std::int64_t>(buffer)};
if (spanSize < 9) { throw std::out_of_range{"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 {0, 0};
}

std::size_t encodeVariableInteger(std::span<std::byte> buffer, std::int64_t value) {
// n.b. this code assumes little endian
std::size_t encodeVariableInteger(std::span<std::byte> buffer, const std::int64_t value) {

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

std::int64_t input = value;
std::size_t offset = 0;

if ((input & 0x8000'0000'0000'0000LL) && (~input < 0x1'0000'0000LL)) {
if ((input < 0) && (~input < 0x1'0000'0000LL)) {
// Signed number.
input = ~input;
if (input <= 0x3) {
Expand All @@ -95,48 +81,45 @@ std::size_t encodeVariableInteger(std::span<std::byte> buffer, std::int64_t valu
}
}

const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::int64_t)>>(input);
if (input < 0x80) {
// Need top bit clear
buffer[offset] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::uint8_t)>>(static_cast<std::uint8_t>(input));
std::memcpy(buffer.data() + offset, bytes.data(), 1);
return offset + 1;
} else if (input < 0x4000) {
// Need top two bits clear
buffer[offset] = bytes[1] | std::byte{0x80};
buffer[offset + 1] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::uint16_t)>>(
swapNetworkBytes(static_cast<std::uint16_t>(input)));
std::memcpy(buffer.data() + offset, bytes.data(), 2);
buffer[offset] = buffer[offset] | std::byte{0x80};
return offset + 2;
} else if (input < 0x200000) {
// Need top three bits clear
buffer[offset] = bytes[2] | std::byte{0xC0};
buffer[offset + 1] = bytes[1];
buffer[offset + 2] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::uint32_t)>>(
swapNetworkBytes(static_cast<std::uint32_t>(input << 8)));
std::memcpy(buffer.data() + offset, bytes.data(), 3);
buffer[offset] = buffer[offset] | std::byte{0xC0};
return offset + 3;
} else if (input < 0x10000000) {
// Need top four bits clear
buffer[offset] = bytes[3] | std::byte{0xE0};
buffer[offset + 1] = bytes[2];
buffer[offset + 2] = bytes[1];
buffer[offset + 3] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::uint32_t)>>(
swapNetworkBytes(static_cast<std::uint32_t>(input)));
std::memcpy(buffer.data() + offset, bytes.data(), 4);
buffer[offset] = buffer[offset] | std::byte{0xE0};
return offset + 4;
} else if (input < 0x100000000LL) {
// It's a full 32-bit integer.
buffer[offset] = std::byte(0xF0);
buffer[offset + 1] = bytes[3];
buffer[offset + 2] = bytes[2];
buffer[offset + 3] = bytes[1];
buffer[offset + 4] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::uint32_t)>>(
swapNetworkBytes(static_cast<std::uint32_t>(input)));
std::memcpy(buffer.data() + offset + 1, bytes.data(), 4);
return offset + 5;
} else {
// It's a 64-bit value.
buffer[offset] = std::byte(0xF4);
buffer[offset + 1] = bytes[7];
buffer[offset + 2] = bytes[6];
buffer[offset + 3] = bytes[5];
buffer[offset + 4] = bytes[4];
buffer[offset + 5] = bytes[3];
buffer[offset + 6] = bytes[2];
buffer[offset + 7] = bytes[1];
buffer[offset + 8] = bytes[0];
const auto bytes = std::bit_cast<std::array<std::byte, sizeof(std::int64_t)>>(
swapNetworkBytes(static_cast<std::int64_t>(input)));
std::memcpy(buffer.data() + offset + 1, bytes.data(), 8);
return offset + 9;
}
}
Expand Down

0 comments on commit f29a16e

Please sign in to comment.