|  | /* | 
|  | *  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 |