Additional helpers methods for BlobReader/BlobWriter and related infrastructure fixes (for the upcoming code that will be using them). (#137)

This commit is contained in:
ivte-ms 2020-02-03 18:14:59 +00:00 коммит произвёл GitHub
Родитель 78e67e2ab3
Коммит 6bcbdaede3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 144 добавлений и 17 удалений

Просмотреть файл

@ -82,7 +82,7 @@ class RefPtr {
// Returns a pointer to the managed object and releases the ownership
// (ref count won't decrease after this operation).
constexpr T* release() {
[[nodiscard]] T* release() noexcept {
T* result = ptr_;
ptr_ = nullptr;
return result;

Просмотреть файл

@ -4,6 +4,7 @@
#pragma once
#include <Microsoft/MixedReality/Sharing/Common/Serialization/Serialization.h>
#include <optional>
#include <string_view>
namespace Microsoft::MixedReality::Sharing::Serialization {
@ -15,11 +16,20 @@ class BlobReader {
explicit BlobReader(std::string_view input) noexcept;
// Reads next bytes_count bytes (as encoded by BlobWriter).
// The returned string_view references the bytes of the input.
// Throws std::out_of_range if there is not enough input left.
// The behavior is undefined if the reader is reused after it
// had thrown an exception.
std::string_view ReadBytes(size_t bytes_count);
// Reads a blob of bytes with the number of bytes encoded
// as exponential-Golomb code; see BlobWriter::WriteBytesWithSize().
// The returned string_view references the bytes of the input.
// Throws std::out_of_range if there is not enough input left.
// The behavior is undefined if the reader is reused after it
// had thrown an exception.
std::string_view ReadBytesWithSize();
// Reads up to 32 bits from the bit stream.
// Throws std::out_of_range if there is not enough input left.
// The behavior is undefined if the reader is reused after it
@ -32,11 +42,20 @@ class BlobReader {
// had thrown an exception, or if bits_count is not in [1, 64].
uint64_t ReadBits64(bit_shift_t bits_count);
// Reads a single bit and returns it as a bool.
bool ReadBool();
// Reads an exponential-Golomb code (as encoded by BlobWriter).
// Throws std::out_of_range if there is not enough input left.
// The behavior is undefined if the reader is reused after it
// had thrown an exception.
uint64_t ReadExponentialGolombCode();
uint64_t ReadGolomb();
// Reads an optional exponential-Golomb code (as encoded by BlobWriter).
// Throws std::out_of_range if there is not enough input left.
// The behavior is undefined if the reader is reused after it
// had thrown an exception.
std::optional<uint64_t> ReadOptionalGolomb();
// Returns true if there are no more than 7 unread bits,
// and all of them are 0.
@ -74,4 +93,9 @@ bool BlobReader::ProbablyNoMoreData() const noexcept {
return unread_bytes_count_ == 0 && bit_buf_bits_count_ < 8 && bit_buf_ == 0;
}
MS_MR_SHARING_FORCEINLINE
bool BlobReader::ReadBool() {
return ReadBits32(1) == 1;
}
} // namespace Microsoft::MixedReality::Sharing::Serialization

Просмотреть файл

@ -5,6 +5,7 @@
#include <Microsoft/MixedReality/Sharing/Common/Serialization/Serialization.h>
#include <cstddef>
#include <optional>
namespace Microsoft::MixedReality::Sharing::Serialization {
@ -38,15 +39,43 @@ class BlobWriter {
void WriteBytes(const char* data, size_t size) noexcept;
void WriteBytes(std::string_view sv) noexcept;
// Writes the provided bytes to the byte stream and their count
// as exponential-Golomb code.
// The reader will be able to read this string without knowing
// its size in advance, see BlobReader::ReadBytesWithSize().
void WriteBytesWithSize(const std::byte* data, size_t size) noexcept;
void WriteBytesWithSize(const char* data, size_t size) noexcept;
void WriteBytesWithSize(std::string_view sv) noexcept;
// Appends the provided bits to the bit stream.
// Expects that the provided value fits into bits_count bits, otherwise the
// behavior is undefined.
void WriteBits(uint64_t value, bit_shift_t bits_count) noexcept;
// Writes a bool as a single bit.
void WriteBool(bool value) noexcept;
// Encodes the provided value as an order-0 exponential-Golomb code.
// Has a special shortened encoding for ~0ull since we are not interested
// in arbitrarily large codes.
void WriteExponentialGolombCode(uint64_t value) noexcept;
void WriteGolomb(uint64_t value) noexcept;
// Encodes the provided optional value as an order-0 exponential-Golomb code,
// offsetted so that the empty optional has the shortest possible encoding
// (1 bit) and all other values are encoded as a value that is 1 greater
// (so, for example, 5 will be encoded as 6).
// Has a special shortened encoding for ~0ull and ~1ull, so the offsetting
// procedure above doesn't overflow.
void WriteOptionalGolomb(
const std::optional<uint64_t> optional_value) noexcept;
// Equivalent to WriteOptionalGolomb with optional_value
// argument equal to present_value.
void WritePresentOptionalGolomb(uint64_t present_value) noexcept;
// Equivalent to WriteOptionalGolomb with optional_value
// argument being empty.
void WriteMissingOptionalGolomb() noexcept;
// The number of bytes the blob will occupy if the writer would be finalized
// with the current state.
@ -140,4 +169,34 @@ void BlobWriter::WriteBits(uint64_t value, bit_shift_t bits_count) noexcept {
}
}
MS_MR_SHARING_FORCEINLINE
void BlobWriter::WriteBytesWithSize(const char* data, size_t size) noexcept {
WriteBytesWithSize(reinterpret_cast<const std::byte*>(data), size);
}
MS_MR_SHARING_FORCEINLINE
void BlobWriter::WriteBytesWithSize(std::string_view sv) noexcept {
WriteBytesWithSize(reinterpret_cast<const std::byte*>(sv.data()), sv.size());
}
MS_MR_SHARING_FORCEINLINE
void BlobWriter::WriteMissingOptionalGolomb() noexcept {
WriteBits(1, 1);
}
MS_MR_SHARING_FORCEINLINE
void BlobWriter::WriteOptionalGolomb(
const std::optional<uint64_t> optional_value) noexcept {
if (optional_value) {
WritePresentOptionalGolomb(*optional_value);
} else {
WriteMissingOptionalGolomb();
}
}
MS_MR_SHARING_FORCEINLINE
void BlobWriter::WriteBool(bool value) noexcept {
WriteBits(value, 1);
}
} // namespace Microsoft::MixedReality::Sharing::Serialization

Просмотреть файл

@ -28,7 +28,7 @@ MS_MR_SHARING_FORCEINLINE
// obtained by calling pointer_to_enum64().
template <class To, class From>
MS_MR_SHARING_FORCEINLINE
typename std::enable_if_t<(sizeof(From) == 64) &&
typename std::enable_if_t<(sizeof(From) == 8) &&
(sizeof(To) <= sizeof(From)) &&
std::is_enum_v<From> && std::is_pointer_v<To>,
To>
@ -43,7 +43,7 @@ MS_MR_SHARING_FORCEINLINE
// is smaller. Stored pointers can be retrieved with enum64_to_pointer().
template <class To, class From>
MS_MR_SHARING_FORCEINLINE
typename std::enable_if_t<(sizeof(To) == 64) &&
typename std::enable_if_t<(sizeof(To) == 8) &&
(sizeof(To) >= sizeof(From)) &&
std::is_enum_v<To> && std::is_pointer_v<From>,
To>

Просмотреть файл

@ -184,7 +184,7 @@ uint64_t BlobReader::ReadBits64(bit_shift_t bits_count) {
return ReadBits<uint64_t>(bits_count);
}
uint64_t BlobReader::ReadExponentialGolombCode() {
uint64_t BlobReader::ReadGolomb() {
// Counting the number of zero bits to determine the length of the code.
// 64 zeros is a special case for ~0ull (see BlobWriter for details).
bit_shift_t zeroes_count = 0;
@ -222,4 +222,23 @@ uint64_t BlobReader::ReadExponentialGolombCode() {
return ReadBits<uint64_t>(zeroes_count + 1) - 1;
}
std::optional<uint64_t> BlobReader::ReadOptionalGolomb() {
uint64_t code = ReadGolomb();
if (code == 0)
return {};
if (code == ~0ull)
return ~ReadBits64(1); // Special encoding for ~0ull and ~1ull.
return code - 1;
}
std::string_view BlobReader::ReadBytesWithSize() {
uint64_t size = ReadGolomb();
if constexpr (sizeof(size_t) < sizeof(uint64_t)) {
if (size > std::numeric_limits<size_t>::max()) {
throw std::out_of_range("Not enough bytes in the blob");
}
}
return ReadBytes(static_cast<size_t>(size));
}
} // namespace Microsoft::MixedReality::Sharing::Serialization

Просмотреть файл

@ -51,7 +51,7 @@ void BlobWriter::Grow(size_t min_free_bytes_after_grow) noexcept {
buffer_end_ = buffer_ + new_elements_count;
}
void BlobWriter::WriteExponentialGolombCode(uint64_t value) noexcept {
void BlobWriter::WriteGolomb(uint64_t value) noexcept {
// The encoding increments the value by 1 and counts the number of bits in the
// result. Then it writes the number of zeros equal to the number of bits
// minus one, and then all the significant bits of the incremented number.
@ -92,6 +92,30 @@ void BlobWriter::WriteExponentialGolombCode(uint64_t value) noexcept {
WriteBits(value, index + 1);
}
void BlobWriter::WriteBytesWithSize(const std::byte* data,
size_t size) noexcept {
// Not reusing WriteBytes() to avoid the possibility of double reallocation
// (growing with 16 extra bytes ensures that we'll always be able to write
// the size as exponential-Golomb code).
if (free_bytes_count_ < size)
Grow(size + 16);
memcpy(bytes_scection_end_, data, size);
bytes_scection_end_ += size;
free_bytes_count_ -= size;
WriteGolomb(size);
}
void BlobWriter::WritePresentOptionalGolomb(uint64_t present_value) noexcept {
if (present_value >= ~1ull) {
// Special-casing these two values, so that they are always encoded with
// 65 bits.
WriteBits(0, 64);
WriteBits(~present_value, 1);
} else {
WriteGolomb(present_value + 1);
}
}
std::string_view BlobWriter::Finalize() noexcept {
const size_t bits_size = bits_section_size();
const size_t bytes_size = bytes_section_size();

Просмотреть файл

@ -9,6 +9,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string_view>

Просмотреть файл

@ -120,12 +120,12 @@ TEST(Serialization, blob_write_read_golomb_codes_short_sequences) {
BlobWriter writer;
writer.WriteBits(0, offset);
for (const auto& x : values) {
writer.WriteExponentialGolombCode(x.value);
writer.WriteGolomb(x.value);
}
BlobReader reader{writer.Finalize()};
ASSERT_EQ(reader.ReadBits64(offset), 0);
for (const auto& expected : values) {
uint64_t read_value = reader.ReadExponentialGolombCode();
uint64_t read_value = reader.ReadGolomb();
ASSERT_EQ(expected.value, read_value);
}
ASSERT_TRUE(reader.ProbablyNoMoreData());
@ -139,11 +139,11 @@ TEST(Serialization, blob_write_read_golomb_codes_long_sequence) {
auto values = GenerateRandomValues(20000);
BlobWriter writer;
for (const auto& x : values) {
writer.WriteExponentialGolombCode(x.value);
writer.WriteGolomb(x.value);
}
BlobReader reader{writer.Finalize()};
for (const auto& expected : values) {
uint64_t read_value = reader.ReadExponentialGolombCode();
uint64_t read_value = reader.ReadGolomb();
ASSERT_EQ(expected.value, read_value);
}
ASSERT_TRUE(reader.ProbablyNoMoreData());
@ -161,7 +161,7 @@ TEST(Serialization, blob_read_from_empty) {
}
BlobReader reader{{}};
ASSERT_THROW(reader.ReadExponentialGolombCode(), std::out_of_range);
ASSERT_THROW(reader.ReadGolomb(), std::out_of_range);
}
TEST(Serialization, blob_read_out_of_range) {
@ -179,8 +179,8 @@ TEST(Serialization, blob_read_out_of_range) {
TEST(Serialization, blob_read_golomb_out_of_range) {
BlobWriter writer;
writer.WriteExponentialGolombCode(6);
writer.WriteExponentialGolombCode(2);
writer.WriteGolomb(6);
writer.WriteGolomb(2);
auto sv = writer.Finalize();
// The order is: low bits => high bits.
@ -193,11 +193,11 @@ TEST(Serialization, blob_read_golomb_out_of_range) {
ASSERT_EQ(sv, "\x3B"sv);
BlobReader reader{sv};
ASSERT_EQ(reader.ReadExponentialGolombCode(), 6);
ASSERT_EQ(reader.ReadExponentialGolombCode(), 2);
ASSERT_EQ(reader.ReadGolomb(), 6);
ASSERT_EQ(reader.ReadGolomb(), 2);
// Can't read anything else.
ASSERT_THROW(reader.ReadExponentialGolombCode(), std::out_of_range);
ASSERT_THROW(reader.ReadGolomb(), std::out_of_range);
}
} // namespace Microsoft::MixedReality::Sharing::Serialization