203 lines
5.9 KiB
C++
203 lines
5.9 KiB
C++
/*
|
|
* Copyright 2015 - 2025 gary@drinkingtea.net
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <ox/std/array.hpp>
|
|
#include <ox/std/assert.hpp>
|
|
#include <ox/std/bit.hpp>
|
|
#include <ox/std/byteswap.hpp>
|
|
#include <ox/std/math.hpp>
|
|
#include <ox/std/memops.hpp>
|
|
#include <ox/std/reader.hpp>
|
|
|
|
namespace ox::mc {
|
|
|
|
template<typename T>
|
|
static constexpr auto Bits = sizeof(T) << 3;
|
|
|
|
/**
|
|
* Returns highest bit other than possible signed bit.
|
|
* Bit numbering starts at 0.
|
|
*/
|
|
template<typename I>
|
|
[[nodiscard]]
|
|
constexpr std::size_t highestBit(I val) noexcept {
|
|
unsigned shiftStart = sizeof(I) * 8 - 1;
|
|
// find most significant non-sign indicator bit
|
|
std::size_t highestBit = 0;
|
|
// start at one bit lower if signed
|
|
if constexpr(is_signed_v<I>) {
|
|
--shiftStart;
|
|
}
|
|
for (auto i = shiftStart; i > 0; --i) {
|
|
const auto bitValue = (val >> i) & 1;
|
|
if (bitValue) {
|
|
highestBit = i;
|
|
break;
|
|
}
|
|
}
|
|
return highestBit;
|
|
}
|
|
|
|
static_assert(highestBit(int8_t(0b10000000)) == 0);
|
|
static_assert(highestBit(~static_cast<int8_t>(-1)) == 0);
|
|
static_assert(highestBit(~static_cast<int8_t>(-2)) == 0);
|
|
static_assert(highestBit(~static_cast<int8_t>(-3)) == 1);
|
|
static_assert(highestBit(1) == 0);
|
|
static_assert(highestBit(2) == 1);
|
|
static_assert(highestBit(4) == 2);
|
|
static_assert(highestBit(8) == 3);
|
|
static_assert(highestBit(uint64_t(1) << 31) == 31);
|
|
static_assert(highestBit(uint64_t(1) << 63) == 63);
|
|
|
|
struct McInt {
|
|
ox::Array<uint8_t, 9> data{};
|
|
// length of integer in bytes
|
|
std::size_t length = 0;
|
|
};
|
|
|
|
template<typename I>
|
|
[[nodiscard]]
|
|
constexpr McInt encodeInteger(I pInput) noexcept {
|
|
auto const input = ox::ResizedInt_t<I, 64>{pInput};
|
|
McInt out;
|
|
const auto inputNegative = is_signed_v<I> && input < 0;
|
|
// move input to uint64_t to allow consistent bit manipulation, and to avoid
|
|
// overflow concerns
|
|
uint64_t val = 0;
|
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
|
ox::memcpy(&val, &input, sizeof(input));
|
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
|
if (val) {
|
|
// bits needed to represent number factoring in space possibly
|
|
// needed for signed bit
|
|
const auto highBit = inputNegative ? highestBit(~val) : highestBit(val);
|
|
const auto bits = highBit + 1 + (is_signed_v<I> ? 1 : 0);
|
|
// bytes needed to store value
|
|
std::size_t bytes = bits / 8 + (bits % 8 != 0);
|
|
const auto bitsAvailable = bytes * 8; // bits available to integer value
|
|
const auto bitsNeeded = bits + bytes;
|
|
// factor in bits needed for bytesIndicator (does not affect bytesIndicator)
|
|
// bits for integer + bits needed to represent bytes > bits available
|
|
if (bitsNeeded > bitsAvailable && bytes != 9) {
|
|
++bytes;
|
|
}
|
|
const auto bytesIndicator = onMask<uint8_t>(bytes - 1);
|
|
// ensure we are copying from little endian representation
|
|
LittleEndian<uint64_t> leVal = val;
|
|
if (inputNegative) {
|
|
leVal |= static_cast<uint64_t>(1 << (bitsNeeded - 1));
|
|
}
|
|
if (bytes == 9) {
|
|
out.data[0] = bytesIndicator;
|
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
|
ox::memcpy(&out.data[1], &leVal, 8);
|
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
|
if (inputNegative) {
|
|
out.data[1] |= 0b1000'0000;
|
|
}
|
|
} else {
|
|
const auto valBits = bytes * 8;
|
|
uint64_t negBit = inputNegative ? 1 : 0;
|
|
auto intermediate =
|
|
static_cast<uint64_t>(leVal.raw() | (negBit << (valBits - 1))) << bytes |
|
|
static_cast<uint64_t>(bytesIndicator);
|
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
|
ox::memcpy(&out.data[0], &intermediate, sizeof(intermediate));
|
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
|
}
|
|
out.length = bytes;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of bytes indicated by the bytes indicator of a variable
|
|
* length integer.
|
|
*/
|
|
[[nodiscard]]
|
|
constexpr std::size_t countBytes(unsigned b) noexcept {
|
|
std::size_t i = 0;
|
|
while ((b >> i) & 1) ++i;
|
|
return i + 1;
|
|
}
|
|
|
|
static_assert(countBytes(0b0000'0000) == 1);
|
|
static_assert(countBytes(0b0000'0001) == 2);
|
|
static_assert(countBytes(0b0000'0011) == 3);
|
|
static_assert(countBytes(0b0000'0111) == 4);
|
|
static_assert(countBytes(0b0000'1111) == 5);
|
|
static_assert(countBytes(0b0001'1111) == 6);
|
|
static_assert(countBytes(0b0011'1111) == 7);
|
|
static_assert(countBytes(0b0111'1111) == 8);
|
|
static_assert(countBytes(0b1111'1111) == 9);
|
|
|
|
template<typename I>
|
|
constexpr Result<I> decodeInteger(Reader_c auto&rdr, std::size_t *bytesRead) noexcept {
|
|
uint8_t firstByte = 0;
|
|
OX_RETURN_ERROR(rdr.read(&firstByte, 1));
|
|
OX_RETURN_ERROR(rdr.seekg(-1, ox::ios_base::cur));
|
|
const auto bytes = countBytes(firstByte);
|
|
if (bytes == 9) {
|
|
*bytesRead = bytes;
|
|
I out = 0;
|
|
OX_RETURN_ERROR(rdr.seekg(1, ox::ios_base::cur));
|
|
OX_RETURN_ERROR(rdr.read(&out, sizeof(I)));
|
|
return fromLittleEndian<I>(out);
|
|
}
|
|
*bytesRead = bytes;
|
|
uint64_t decoded = 0;
|
|
OX_RETURN_ERROR(rdr.read(&decoded, bytes));
|
|
decoded >>= bytes;
|
|
// move sign bit
|
|
if constexpr(is_signed_v<I>) {
|
|
const auto negBit = bytes * 8 - bytes - 1;
|
|
// move sign
|
|
const auto negative = (decoded >> negBit) == 1;
|
|
if (negative) {
|
|
// fill in all bits between encoded sign and real sign with 1s
|
|
// split it up because the 32-bit ARM can't shift more than 32 bits
|
|
ox::Array<uint32_t, 2> d = {};
|
|
//d[0] = decoded & 0xffff'ffff;
|
|
//d[1] = decoded >> 32;
|
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
|
ox::memcpy(&d[0], &decoded, sizeof(decoded));
|
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
|
auto bit = negBit;
|
|
for (; bit < ox::min<std::size_t>(Bits<I>, 32); ++bit) {
|
|
d[0] |= 1 << bit;
|
|
}
|
|
bit -= 32;
|
|
for (; bit < Bits<I>; ++bit) {
|
|
d[1] |= 1 << bit;
|
|
}
|
|
I out = 0;
|
|
if constexpr(ox::defines::BigEndian) {
|
|
const auto d0Tmp = d[0];
|
|
d[0] = d[1];
|
|
d[1] = d0Tmp;
|
|
}
|
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
|
ox::memcpy(&out, &d[0], sizeof(out));
|
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
|
return out;
|
|
}
|
|
}
|
|
return static_cast<I>(decoded);
|
|
}
|
|
|
|
template<typename I>
|
|
Result<I> decodeInteger(McInt m) noexcept {
|
|
std::size_t bytesRead{};
|
|
BufferReader br({reinterpret_cast<const char*>(m.data.data()), 9});
|
|
return decodeInteger<I>(br, &bytesRead);
|
|
}
|
|
|
|
}
|