| /* |
| * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "logging/rtc_event_log/encoder/optional_blob_encoding.h" |
| |
| #include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| |
| namespace webrtc { |
| namespace { |
| |
| class BitBuilder { |
| public: |
| BitBuilder& Bit(uint8_t bit) { |
| if (total_bits_ % 8 == 0) { |
| bits_.push_back(0); |
| } |
| bits_[total_bits_ / 8] |= bit << (7 - (total_bits_ % 8)); |
| ++total_bits_; |
| return *this; |
| } |
| |
| BitBuilder& Bytes(const std::vector<uint8_t>& bytes) { |
| for (uint8_t byte : bytes) { |
| for (int i = 1; i <= 8; ++i) { |
| uint8_t bit = (byte >> (8 - i)) & 1; |
| Bit(bit); |
| } |
| } |
| return *this; |
| } |
| |
| BitBuilder& ByteAlign() { |
| while (total_bits_ % 8 > 0) { |
| Bit(0); |
| } |
| return *this; |
| } |
| |
| std::string AsString() { return std::string(bits_.begin(), bits_.end()); } |
| |
| private: |
| std::vector<uint8_t> bits_; |
| uint64_t total_bits_ = 0; |
| }; |
| |
| TEST(OptionalBlobEncoding, AllBlobsPresent) { |
| std::string encoded = EncodeOptionalBlobs({"a", "b", "c"}); |
| std::string expected = BitBuilder() |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x01, 'a'}) |
| .Bytes({0x01, 'b'}) |
| .Bytes({0x01, 'c'}) |
| .AsString(); |
| EXPECT_EQ(encoded, expected); |
| } |
| |
| TEST(OptionalBlobEncoding, SomeBlobsPresent) { |
| std::string encoded = EncodeOptionalBlobs({"a", std::nullopt, "c"}); |
| std::string expected = BitBuilder() |
| .Bit(0) |
| .Bit(1) |
| .Bit(0) |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x01, 'a'}) |
| .Bytes({0x01, 'c'}) |
| .AsString(); |
| EXPECT_EQ(encoded, expected); |
| } |
| |
| TEST(OptionalBlobEncoding, NoBlobsPresent) { |
| std::string encoded = |
| EncodeOptionalBlobs({std::nullopt, std::nullopt, std::nullopt}); |
| EXPECT_THAT(encoded, IsEmpty()); |
| } |
| |
| TEST(OptionalBlobEncoding, EmptyBlobsPresent) { |
| std::string encoded = EncodeOptionalBlobs({std::nullopt, "", std::nullopt}); |
| std::string expected = BitBuilder() |
| .Bit(0) |
| .Bit(0) |
| .Bit(1) |
| .Bit(0) |
| .ByteAlign() |
| .Bytes({0x0}) |
| .AsString(); |
| EXPECT_EQ(encoded, expected); |
| } |
| |
| TEST(OptionalBlobEncoding, ZeroBlobs) { |
| std::string encoded = EncodeOptionalBlobs({}); |
| EXPECT_EQ(encoded, std::string()); |
| } |
| |
| TEST(OptionalBlobEncoding, LongBlobs) { |
| std::string medium_string(100, 'a'); |
| std::string long_string(200, 'b'); |
| std::string encoded = EncodeOptionalBlobs({medium_string, long_string}); |
| std::string expected = |
| BitBuilder() |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x64}) |
| .Bytes({medium_string.begin(), medium_string.end()}) |
| .Bytes({0xC8, 0x01}) |
| .Bytes({long_string.begin(), long_string.end()}) |
| .AsString(); |
| EXPECT_EQ(encoded, expected); |
| } |
| |
| TEST(OptionalBlobDecoding, AllBlobsPresent) { |
| std::string encoded = BitBuilder() |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x01, 'a'}) |
| .Bytes({0x01, 'b'}) |
| .Bytes({0x01, 'c'}) |
| .AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 3); |
| EXPECT_THAT(decoded, ElementsAre("a", "b", "c")); |
| } |
| |
| TEST(OptionalBlobDecoding, SomeBlobsPresent) { |
| std::string encoded = BitBuilder() |
| .Bit(0) |
| .Bit(1) |
| .Bit(0) |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x01, 'a'}) |
| .Bytes({0x01, 'c'}) |
| .AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 3); |
| EXPECT_THAT(decoded, ElementsAre("a", std::nullopt, "c")); |
| } |
| |
| TEST(OptionalBlobDecoding, NoBlobsPresent) { |
| auto decoded = DecodeOptionalBlobs("", 3); |
| EXPECT_THAT(decoded, ElementsAre(std::nullopt, std::nullopt, std::nullopt)); |
| } |
| |
| TEST(OptionalBlobDecoding, EmptyBlobsPresent) { |
| std::string encoded = BitBuilder() |
| .Bit(0) |
| .Bit(0) |
| .Bit(1) |
| .Bit(0) |
| .ByteAlign() |
| .Bytes({0x0}) |
| .AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 3); |
| EXPECT_THAT(decoded, ElementsAre(std::nullopt, "", std::nullopt)); |
| } |
| |
| TEST(OptionalBlobDecoding, ZeroBlobs) { |
| std::string encoded; |
| auto decoded = DecodeOptionalBlobs(encoded, 0); |
| EXPECT_THAT(decoded, IsEmpty()); |
| } |
| |
| TEST(OptionalBlobDecoding, LongBlobs) { |
| std::string medium_string(100, 'a'); |
| std::string long_string(200, 'b'); |
| std::string encoded = BitBuilder() |
| .Bit(1) |
| .ByteAlign() |
| .Bytes({0x64}) |
| .Bytes({medium_string.begin(), medium_string.end()}) |
| .Bytes({0xC8, 0x01}) |
| .Bytes({long_string.begin(), long_string.end()}) |
| .AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 2); |
| EXPECT_THAT(decoded, ElementsAre(medium_string, long_string)); |
| } |
| |
| TEST(OptionalBlobDecoding, TooShortEncodedBlobLength) { |
| std::string encoded = |
| BitBuilder().Bit(1).ByteAlign().Bytes({0x01, 'a', 'b'}).AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 1); |
| EXPECT_THAT(decoded, IsEmpty()); |
| } |
| |
| TEST(OptionalBlobDecoding, TooLongEncodedBlobLength) { |
| std::string encoded = |
| BitBuilder().Bit(1).ByteAlign().Bytes({0x03, 'a', 'b'}).AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 1); |
| EXPECT_THAT(decoded, IsEmpty()); |
| } |
| |
| TEST(OptionalBlobDecoding, TooLongEncodedBufferLength) { |
| std::string encoded = BitBuilder().Bytes({0x00, 0x00, 0x00}).AsString(); |
| auto decoded = DecodeOptionalBlobs(encoded, 8); |
| EXPECT_THAT(decoded, IsEmpty()); |
| } |
| |
| } // namespace |
| } // namespace webrtc |