Skip to content

Make uintvar more class like to be used as members in other classes. #537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/uintvar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static void
UIntVar_FromBytes(benchmark::State& state)
{
constexpr auto var = quicr::UintVar(0x123456789);
auto bytes = std::span{ var };
constexpr auto bytes = std::bit_cast<std::array<std::uint8_t, sizeof(uint64_t)>>(var);
for ([[maybe_unused]] const auto& _ : state) {
auto value = quicr::UintVar(bytes);
benchmark::DoNotOptimize(value);
Expand Down
83 changes: 54 additions & 29 deletions include/quicr/detail/uintvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,17 @@

namespace quicr {
namespace {
constexpr bool kIsBigEndian = std::endian::native == std::endian::big;

constexpr std::uint16_t SwapBytes(const std::uint16_t value)
{
if constexpr (kIsBigEndian)
if constexpr (std::endian::native == std::endian::big)
return value;

return ((value >> 8) & 0x00FF) | ((value << 8) & 0xFF00);
}

constexpr std::uint32_t SwapBytes(const std::uint32_t value)
{
if constexpr (kIsBigEndian)
if constexpr (std::endian::native == std::endian::big)
return value;

return ((value >> 24) & 0x000000FF) | ((value >> 8) & 0x0000FF00) | ((value << 8) & 0x00FF0000) |
Expand All @@ -33,7 +31,7 @@ namespace quicr {

constexpr std::uint64_t SwapBytes(const std::uint64_t value)
{
if constexpr (kIsBigEndian)
if constexpr (std::endian::native == std::endian::big)
return value;

return ((value >> 56) & 0x00000000000000FF) | ((value >> 40) & 0x000000000000FF00) |
Expand All @@ -47,42 +45,65 @@ namespace quicr {
{
public:
constexpr UintVar(uint64_t value)
: be_value_{ SwapBytes(value) }
: be_value_{ std::bit_cast<std::array<std::uint8_t, sizeof(std::uint64_t)>>(SwapBytes(value)) }
{
constexpr uint64_t kLen1 = (static_cast<uint64_t>(-1) << (64 - 6) >> (64 - 6));
constexpr uint64_t kLen2 = (static_cast<uint64_t>(-1) << (64 - 14) >> (64 - 14));
constexpr uint64_t kLen4 = (static_cast<uint64_t>(-1) << (64 - 30) >> (64 - 30));
constexpr uint64_t k14bitLength = (static_cast<uint64_t>(-1) << (64 - 6) >> (64 - 6));
constexpr uint64_t k30bitLength = (static_cast<uint64_t>(-1) << (64 - 14) >> (64 - 14));
constexpr uint64_t k62bitLength = (static_cast<uint64_t>(-1) << (64 - 30) >> (64 - 30));

if (static_cast<uint8_t>(be_value_) & 0xC0u) { // Check if invalid
if (be_value_.front() & 0xC0u) { // Check if invalid
throw std::invalid_argument("Value greater than uintvar maximum");
}

if (value > kLen4) { // 62 bit encoding (8 bytes)
be_value_ |= 0xC0ull;
} else if (value > kLen2) { // 30 bit encoding (4 bytes)
be_value_ >>= 32;
be_value_ |= 0x80ull;
} else if (value > kLen1) { // 14 bit encoding (2 bytes)
be_value_ >>= 48;
be_value_ |= 0x40ull;
std::uint64_t be_v = std::bit_cast<std::uint64_t>(be_value_);
if (value > k62bitLength) { // 62 bit encoding (8 bytes)
be_v |= 0xC0ull;
} else if (value > k30bitLength) { // 30 bit encoding (4 bytes)
be_v >>= 32;
be_v |= 0x80ull;
} else if (value > k14bitLength) { // 14 bit encoding (2 bytes)
be_v >>= 48;
be_v |= 0x40ull;
} else {
be_value_ >>= 56;
be_v >>= 56;
}

be_value_ = std::bit_cast<std::array<std::uint8_t, sizeof(std::uint64_t)>>(be_v);
}

UintVar(std::span<const uint8_t> bytes)
constexpr UintVar(std::span<const std::uint8_t> bytes)
: be_value_{ 0 }
{
if (bytes.empty() || bytes.size() < Size(bytes[0])) {
if (bytes.empty() || bytes.size() < Size(bytes.front())) {
throw std::invalid_argument("Invalid bytes for uintvar");
}

std::memcpy(&be_value_, bytes.data(), Size(bytes[0]));
const std::size_t size = Size(bytes.front());
if (std::is_constant_evaluated()) {
for (std::size_t i = 0; i < size; ++i) {
be_value_[i] = bytes.data()[i];
}
} else {
std::memcpy(&be_value_, bytes.data(), size);
}
}

constexpr UintVar(const UintVar&) noexcept = default;
constexpr UintVar(UintVar&&) noexcept = default;
constexpr UintVar& operator=(const UintVar&) noexcept = default;
constexpr UintVar& operator=(UintVar&&) noexcept = default;

constexpr UintVar& operator=(std::uint64_t value)
{
UintVar t(value);
this->be_value_ = t.be_value_;
return *this;
}

explicit constexpr operator uint64_t() const noexcept
constexpr std::uint64_t Get() const noexcept
{
return SwapBytes((be_value_ & SwapBytes(uint64_t(~(~0x3Full << 56)))) << (sizeof(uint64_t) - Size()) * 8);
return SwapBytes((std::bit_cast<std::uint64_t>(be_value_) & SwapBytes(uint64_t(~(~0x3Full << 56))))
<< (sizeof(uint64_t) - Size()) * 8);
}

static constexpr std::size_t Size(uint8_t msb_bytes) noexcept
Expand All @@ -98,16 +119,20 @@ namespace quicr {
return sizeof(uint8_t);
}

constexpr std::size_t Size() const noexcept { return UintVar::Size(static_cast<uint8_t>(be_value_)); }
constexpr std::size_t Size() const noexcept { return UintVar::Size(be_value_.front()); }

// NOLINTBEGIN(readability-identifier-naming)
auto data() const noexcept { return reinterpret_cast<const uint8_t*>(&be_value_); }
constexpr const std::uint8_t* data() const noexcept { return be_value_.data(); }
constexpr std::size_t size() const noexcept { return Size(); }
auto begin() const noexcept { return data(); }
auto end() const noexcept { return data() + Size(); }
constexpr auto begin() const noexcept { return data(); }
constexpr auto end() const noexcept { return data() + Size(); }
// NOLINTEND(readability-identifier-naming)

explicit constexpr operator uint64_t() const noexcept { return Get(); }

constexpr auto operator<=>(const UintVar&) const noexcept = default;

private:
uint64_t be_value_;
std::array<std::uint8_t, sizeof(std::uint64_t)> be_value_;
};
}
9 changes: 9 additions & 0 deletions test/uintvar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ namespace var {
const std::vector<uint8_t> kValue8ByteEncoded = { 0xC0, 0, 0, 0x1, 0x23, 0x45, 0x67, 0x89 };
}

TEST_CASE("Check UintVar")
{
CHECK_EQ(sizeof(std::uint64_t), sizeof(quicr::UintVar));
CHECK(std::is_trivially_copy_constructible_v<quicr::UintVar>);
CHECK(std::is_trivially_copy_assignable_v<quicr::UintVar>);
CHECK(std::is_trivially_move_constructible_v<quicr::UintVar>);
CHECK(std::is_trivially_move_assignable_v<quicr::UintVar>);
}

TEST_CASE("Encode/Decode UintVar Uint64")
{
CHECK_EQ(var::kValue1Byte, uint64_t(quicr::UintVar(var::kValue1Byte)));
Expand Down