From 17fc48aa26ec22fa4c2f09264f3ddea7bbf63299 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 15 Mar 2019 01:08:49 -0500 Subject: [PATCH] [ox/mc] Finish encoding for variable length integers --- deps/ox/src/ox/mc/CMakeLists.txt | 1 + deps/ox/src/ox/mc/intops.hpp | 90 +++++++++++++++++++++++++++ deps/ox/src/ox/mc/mc.hpp | 1 + deps/ox/src/ox/mc/read.hpp | 2 +- deps/ox/src/ox/mc/test/CMakeLists.txt | 1 + deps/ox/src/ox/mc/test/tests.cpp | 58 +++++++++++++++++ deps/ox/src/ox/mc/write.hpp | 61 ++++++++++++------ 7 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 deps/ox/src/ox/mc/intops.hpp diff --git a/deps/ox/src/ox/mc/CMakeLists.txt b/deps/ox/src/ox/mc/CMakeLists.txt index 7a7c7245..326e0e74 100644 --- a/deps/ox/src/ox/mc/CMakeLists.txt +++ b/deps/ox/src/ox/mc/CMakeLists.txt @@ -20,6 +20,7 @@ set_property( install( FILES + intops.hpp err.hpp mc.hpp presenceindicator.hpp diff --git a/deps/ox/src/ox/mc/intops.hpp b/deps/ox/src/ox/mc/intops.hpp new file mode 100644 index 00000000..a141971a --- /dev/null +++ b/deps/ox/src/ox/mc/intops.hpp @@ -0,0 +1,90 @@ +/* + * Copyright 2015 - 2018 gtalent2@gmail.com + * + * 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 http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include + +namespace ox::mc { + +/** + * Returns highest bit other than possible signed bit. + * Bit numbering starts at 0. + */ +template +[[nodiscard]] constexpr auto highestBit(I val) noexcept { + constexpr auto Bits = sizeof(I) * 8; + // find most significant non-sign indicator bit + std::size_t highestBit = Bits; + // start at one bit lower if signed + if constexpr(ox::is_signed) { + --highestBit; + } + for (int i = Bits - 1; i > -1; --i) { + const auto bitValue = (val >> i) & 1; + if (bitValue) { + highestBit = i; + break; + } + } + return highestBit; +} + +static_assert(highestBit(1) == 0); +static_assert(highestBit(2) == 1); +static_assert(highestBit(4) == 2); +static_assert(highestBit(8) == 3); +static_assert(highestBit(1 << 31) == 31); +static_assert(highestBit(uint64_t(1) << 63) == 63); + +struct McInt { + uint8_t data[9]; + // length of integer in bytes + std::size_t length = 0; +}; + +template +McInt encodeInteger(I val) noexcept { + McInt out; + if (val) { + // bits needed to represent number factoring in space possibly + // needed for signed bit + const auto bits = highestBit(val) + 1 + (ox::is_signed ? 1 : 0); + // bytes needed to store value + std::size_t bytes = bits / 8 + (bits % 8 != 0); + auto bitsAvailable = bytes * 8; + // factor in bits needed for bytesIndicator (does not affect bytesIndicator) + // bits for integer + bits neded to represent bytes > bits available + if (bits + bytes > bitsAvailable && bytes != 9) { + ++bytes; + bitsAvailable = bytes * 8; + } + const auto bytesIndicator = onMask(bytes - 1); // << (7 - bytes); + + // move sign bit + if constexpr(ox::is_signed) { + if (val < 0) { + val ^= int64_t(1) << (sizeof(I) * 8 - 1); + val |= int64_t(1) << (bitsAvailable - 1); + } + } + // ensure we are copying from little endian represenstation + LittleEndian leVal = val; + if (bytes == 9) { + out.data[0] = bytesIndicator; + *reinterpret_cast(&out.data[1]) = *reinterpret_cast(&leVal); + } else { + *reinterpret_cast(&out.data[0]) = (leVal << bytes) | bytesIndicator; + } + out.length = bytes; + } + return out; +} + +} diff --git a/deps/ox/src/ox/mc/mc.hpp b/deps/ox/src/ox/mc/mc.hpp index 145d5242..0032fe16 100644 --- a/deps/ox/src/ox/mc/mc.hpp +++ b/deps/ox/src/ox/mc/mc.hpp @@ -8,6 +8,7 @@ #pragma once +#include "intops.hpp" #include "read.hpp" #include "types.hpp" #include "walk.hpp" diff --git a/deps/ox/src/ox/mc/read.hpp b/deps/ox/src/ox/mc/read.hpp index 39414271..377165ea 100644 --- a/deps/ox/src/ox/mc/read.hpp +++ b/deps/ox/src/ox/mc/read.hpp @@ -171,7 +171,7 @@ Error MetalClawReader::op(const char*, ox::Vector *val) { } template -int readMC(uint8_t *buff, std::size_t buffLen, T *val) { +Error readMC(uint8_t *buff, std::size_t buffLen, T *val) { MetalClawReader reader(buff, buffLen); return ioOp(&reader, val); } diff --git a/deps/ox/src/ox/mc/test/CMakeLists.txt b/deps/ox/src/ox/mc/test/CMakeLists.txt index a4b1e270..a3c66141 100644 --- a/deps/ox/src/ox/mc/test/CMakeLists.txt +++ b/deps/ox/src/ox/mc/test/CMakeLists.txt @@ -10,3 +10,4 @@ target_link_libraries( add_test("Test\\ McTest\\ Writer" McTest MetalClawWriter) add_test("Test\\ McTest\\ Reader" McTest MetalClawReader) +add_test("Test\\ McTest\\ encodeInteger" McTest encodeInteger) diff --git a/deps/ox/src/ox/mc/test/tests.cpp b/deps/ox/src/ox/mc/test/tests.cpp index b974879c..6cb80611 100644 --- a/deps/ox/src/ox/mc/test/tests.cpp +++ b/deps/ox/src/ox/mc/test/tests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,63 @@ std::map tests = { return OxError(0); } }, + { + "encodeInteger", + [] { + using ox::MaxValue; + using ox::mc::McInt; + using ox::mc::encodeInteger; + + constexpr auto check = [](McInt val, std::vector &&expected) { + if (val.length != expected.size()) { + return OxError(1); + } + for (std::size_t i = 0; i < expected.size(); i++) { + if (expected[i] != val.data[i]) { + std::cout << static_cast(val.data[i]) << '\n'; + return OxError(1); + } + } + return OxError(0); + }; + constexpr auto check64 = [](McInt val, auto expected) { + if (val.length != 9) { + std::cout << "val.length: " << val.length << '\n'; + return OxError(1); + } + ox::LittleEndian decoded = *reinterpret_cast(&val.data[1]); + if (expected != decoded) { + std::cout << "decoded: " << decoded << ", expected: " << expected << '\n'; + return OxError(1); + } + return OxError(0); + }; + + oxAssert(check(encodeInteger(int64_t(1)), {0b0010}), "Encode 1 fail"); + oxAssert(check(encodeInteger(int64_t(2)), {0b0100}), "Encode 2 fail"); + oxAssert(check(encodeInteger(int64_t(3)), {0b0110}), "Encode 3 fail"); + oxAssert(check(encodeInteger(int64_t(4)), {0b1000}), "Encode 4 fail"); + oxAssert(check(encodeInteger(int64_t(128)), {0b0001, 0b10}), "Encode 128 fail"); + oxAssert(check(encodeInteger(int64_t(129)), {0b0101, 0b10}), "Encode 129 fail"); + oxAssert(check(encodeInteger(int64_t(130)), {0b1001, 0b10}), "Encode 130 fail"); + oxAssert(check(encodeInteger(int64_t(131)), {0b1101, 0b10}), "Encode 131 fail"); + + oxAssert(check(encodeInteger(uint64_t(1)), {0b0010}), "Encode 1 fail"); + oxAssert(check(encodeInteger(uint64_t(2)), {0b0100}), "Encode 2 fail"); + oxAssert(check(encodeInteger(uint64_t(3)), {0b0110}), "Encode 3 fail"); + oxAssert(check(encodeInteger(uint64_t(4)), {0b1000}), "Encode 4 fail"); + oxAssert(check(encodeInteger(uint64_t(128)), {0b0001, 0b10}), "Encode 128 fail"); + oxAssert(check(encodeInteger(uint64_t(129)), {0b0101, 0b10}), "Encode 129 fail"); + oxAssert(check(encodeInteger(uint64_t(130)), {0b1001, 0b10}), "Encode 130 fail"); + oxAssert(check(encodeInteger(uint64_t(131)), {0b1101, 0b10}), "Encode 131 fail"); + + // Signed check needs lambda templates to run correctly without + // code deduplication + //oxAssert(check64(encodeInteger(MaxValue), MaxValue), "Encode MaxValue fail"); + oxAssert(check64(encodeInteger(MaxValue), MaxValue), "Encode MaxValue fail"); + return OxError(0); + } + }, { "MetalClawDef", [] { diff --git a/deps/ox/src/ox/mc/write.hpp b/deps/ox/src/ox/mc/write.hpp index 7beb5d7b..7904e4f6 100644 --- a/deps/ox/src/ox/mc/write.hpp +++ b/deps/ox/src/ox/mc/write.hpp @@ -10,11 +10,13 @@ #include #include +#include #include #include #include #include +#include "intops.hpp" #include "err.hpp" #include "presenceindicator.hpp" #include "types.hpp" @@ -60,7 +62,7 @@ class MetalClawWriter { Error op(const char*, SerStr val); template - int op(const char*, T *val); + Error op(const char*, T *val); void setTypeInfo(const char *name, int fields); @@ -81,7 +83,7 @@ Error MetalClawWriter::op(const char *name, ox::BString *val) { } template -int MetalClawWriter::op(const char*, T *val) { +Error MetalClawWriter::op(const char*, T *val) { int err = 0; bool fieldSet = false; MetalClawWriter writer(m_buff + m_buffIt, m_buffLen - m_buffIt); @@ -102,24 +104,6 @@ Error MetalClawWriter::op(const char*, ox::Vector *val) { return op(nullptr, val->data(), val->size()); } -template -Error MetalClawWriter::appendInteger(I val) { - Error err = 0; - bool fieldSet = false; - if (val) { - if (m_buffIt + sizeof(I) < m_buffLen) { - *reinterpret_cast*>(&m_buff[m_buffIt]) = val; - fieldSet = true; - m_buffIt += sizeof(I); - } else { - err |= MC_BUFFENDED; - } - } - err |= m_fieldPresence.set(m_field, fieldSet); - m_field++; - return err; -} - template Error MetalClawWriter::op(const char*, T *val, std::size_t len) { Error err = 0; @@ -151,6 +135,43 @@ Error MetalClawWriter::op(const char*, T *val, std::size_t len) { return err; } +template +Error MetalClawWriter::appendInteger(I val) { + Error err = 0; + bool fieldSet = false; + if (val) { + if (m_buffIt + sizeof(I) < m_buffLen) { + LittleEndian leVal = val; + mc::encodeInteger(val); + // bits needed to represent number factoring in space possibly needed + // for signed bit + const auto bits = mc::highestBit(val) + (ox::is_signed ? 1 : 0) / 8; + // bytes needed to store value + std::size_t bytes = bits / 8 + (bits % 8 != 0); + const auto bytesIndicator = onMask(bytes - 1) << (7 - bytes); + // factor in bits needed for bytesIndicator (does not affect bytesIndicator) + // bits for integer + bits needed to represent bytes > bits available + if (bits + bytes > bytes * 8) { + ++bytes; + } + + if (bytes == 9) { + m_buff[m_buffIt++] = bytesIndicator; + + } else { + } + *reinterpret_cast*>(&m_buff[m_buffIt]) = leVal; + fieldSet = true; + m_buffIt += sizeof(I); + } else { + err |= MC_BUFFENDED; + } + } + err |= m_fieldPresence.set(m_field, fieldSet); + m_field++; + return err; +} + template Error writeMC(uint8_t *buff, std::size_t buffLen, T *val, std::size_t *sizeOut = nullptr) { MetalClawWriter writer(buff, buffLen);