diff --git a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/RefPtr.h b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/RefPtr.h index db3b37d..5ab3bbb 100644 --- a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/RefPtr.h +++ b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/RefPtr.h @@ -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; diff --git a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobReader.h b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobReader.h index 09e7b37..8559e8f 100644 --- a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobReader.h +++ b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobReader.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include 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 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 diff --git a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobWriter.h b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobWriter.h index 04ca09b..fcc43e4 100644 --- a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobWriter.h +++ b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/Serialization/BlobWriter.h @@ -5,6 +5,7 @@ #include #include +#include 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 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(data), size); +} + +MS_MR_SHARING_FORCEINLINE +void BlobWriter::WriteBytesWithSize(std::string_view sv) noexcept { + WriteBytesWithSize(reinterpret_cast(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 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 diff --git a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/bit_cast.h b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/bit_cast.h index 0c0e6a9..2394859 100644 --- a/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/bit_cast.h +++ b/libs/Common-cpp/include/Microsoft/MixedReality/Sharing/Common/bit_cast.h @@ -28,7 +28,7 @@ MS_MR_SHARING_FORCEINLINE // obtained by calling pointer_to_enum64(). template 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 && std::is_pointer_v, To> @@ -43,7 +43,7 @@ MS_MR_SHARING_FORCEINLINE // is smaller. Stored pointers can be retrieved with enum64_to_pointer(). template 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 && std::is_pointer_v, To> diff --git a/libs/Common-cpp/src/Serialization/BlobReader.cpp b/libs/Common-cpp/src/Serialization/BlobReader.cpp index 27ebbb5..666c028 100644 --- a/libs/Common-cpp/src/Serialization/BlobReader.cpp +++ b/libs/Common-cpp/src/Serialization/BlobReader.cpp @@ -184,7 +184,7 @@ uint64_t BlobReader::ReadBits64(bit_shift_t bits_count) { return ReadBits(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(zeroes_count + 1) - 1; } +std::optional 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::max()) { + throw std::out_of_range("Not enough bytes in the blob"); + } + } + return ReadBytes(static_cast(size)); +} + } // namespace Microsoft::MixedReality::Sharing::Serialization diff --git a/libs/Common-cpp/src/Serialization/BlobWriter.cpp b/libs/Common-cpp/src/Serialization/BlobWriter.cpp index 6feef70..4b8ea17 100644 --- a/libs/Common-cpp/src/Serialization/BlobWriter.cpp +++ b/libs/Common-cpp/src/Serialization/BlobWriter.cpp @@ -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(); diff --git a/libs/Common-cpp/src/pch.h b/libs/Common-cpp/src/pch.h index b2947d5..ba1e97b 100644 --- a/libs/Common-cpp/src/pch.h +++ b/libs/Common-cpp/src/pch.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/Common-cpp/tests/Serialization-test.cpp b/libs/Common-cpp/tests/Serialization-test.cpp index 8c53bdf..d08750c 100644 --- a/libs/Common-cpp/tests/Serialization-test.cpp +++ b/libs/Common-cpp/tests/Serialization-test.cpp @@ -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