diff options
Diffstat (limited to '')
21 files changed, 7008 insertions, 0 deletions
diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.cc new file mode 100644 index 0000000000..e8748d3db3 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.cc @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 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/bit_writer.h" + +namespace webrtc { + +namespace { +size_t BitsToBytes(size_t bits) { + return (bits / 8) + (bits % 8 > 0 ? 1 : 0); +} +} // namespace + +void BitWriter::WriteBits(uint64_t val, size_t bit_count) { + RTC_DCHECK(valid_); + const bool success = bit_writer_.WriteBits(val, bit_count); + RTC_DCHECK(success); + written_bits_ += bit_count; +} + +void BitWriter::WriteBits(absl::string_view input) { + RTC_DCHECK(valid_); + for (char c : input) { + WriteBits(static_cast<unsigned char>(c), CHAR_BIT); + } +} + +// Returns everything that was written so far. +// Nothing more may be written after this is called. +std::string BitWriter::GetString() { + RTC_DCHECK(valid_); + valid_ = false; + + buffer_.resize(BitsToBytes(written_bits_)); + written_bits_ = 0; + + std::string result; + std::swap(buffer_, result); + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.h new file mode 100644 index 0000000000..421e7c4370 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/bit_writer.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_BIT_WRITER_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_BIT_WRITER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "rtc_base/bit_buffer.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +// Wrap BitBufferWriter and extend its functionality by (1) keeping track of +// the number of bits written and (2) owning its buffer. +class BitWriter final { + public: + explicit BitWriter(size_t byte_count) + : buffer_(byte_count, '\0'), + bit_writer_(reinterpret_cast<uint8_t*>(&buffer_[0]), buffer_.size()), + written_bits_(0), + valid_(true) { + RTC_DCHECK_GT(byte_count, 0); + } + + BitWriter(const BitWriter&) = delete; + BitWriter& operator=(const BitWriter&) = delete; + + void WriteBits(uint64_t val, size_t bit_count); + + void WriteBits(absl::string_view input); + + // Returns everything that was written so far. + // Nothing more may be written after this is called. + std::string GetString(); + + private: + std::string buffer_; + rtc::BitBufferWriter bit_writer_; + // Note: Counting bits instead of bytes wraps around earlier than it has to, + // which means the maximum length is lower than it could be. We don't expect + // to go anywhere near the limit, though, so this is good enough. + size_t written_bits_; + bool valid_; +}; + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_BIT_WRITER_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.cc new file mode 100644 index 0000000000..96699dc96a --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.cc @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 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/blob_encoding.h" + +#include <cstdint> + +#include "logging/rtc_event_log/encoder/var_int.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +std::string EncodeBlobs(const std::vector<std::string>& blobs) { + RTC_DCHECK(!blobs.empty()); + + size_t result_length_bound = kMaxVarIntLengthBytes * blobs.size(); + for (const auto& blob : blobs) { + // Providing an input so long that it would cause a wrap-around is an error. + RTC_DCHECK_GE(result_length_bound + blob.length(), result_length_bound); + result_length_bound += blob.length(); + } + + std::string result; + result.reserve(result_length_bound); + + // First, encode all of the lengths. + for (absl::string_view blob : blobs) { + result += EncodeVarInt(blob.length()); + } + + // Second, encode the actual blobs. + for (absl::string_view blob : blobs) { + result.append(blob.data(), blob.length()); + } + + RTC_DCHECK_LE(result.size(), result_length_bound); + return result; +} + +std::vector<absl::string_view> DecodeBlobs(absl::string_view encoded_blobs, + size_t num_of_blobs) { + if (encoded_blobs.empty()) { + RTC_LOG(LS_WARNING) << "Corrupt input; empty input."; + return std::vector<absl::string_view>(); + } + + if (num_of_blobs == 0u) { + RTC_LOG(LS_WARNING) + << "Corrupt input; number of blobs must be greater than 0."; + return std::vector<absl::string_view>(); + } + + // Read the lengths of all blobs. + std::vector<uint64_t> lengths(num_of_blobs); + for (size_t i = 0; i < num_of_blobs; ++i) { + bool success = false; + std::tie(success, encoded_blobs) = DecodeVarInt(encoded_blobs, &lengths[i]); + if (!success) { + RTC_LOG(LS_WARNING) << "Corrupt input; varint decoding failed."; + return std::vector<absl::string_view>(); + } + } + + // Read the blobs themselves. + std::vector<absl::string_view> blobs(num_of_blobs); + for (size_t i = 0; i < num_of_blobs; ++i) { + if (lengths[i] > encoded_blobs.length()) { + RTC_LOG(LS_WARNING) << "Corrupt input; blob sizes exceed input size."; + return std::vector<absl::string_view>(); + } + + blobs[i] = encoded_blobs.substr(0, lengths[i]); + encoded_blobs = encoded_blobs.substr(lengths[i]); + } + + if (!encoded_blobs.empty()) { + RTC_LOG(LS_WARNING) << "Corrupt input; unrecognized trailer."; + return std::vector<absl::string_view>(); + } + + return blobs; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.h new file mode 100644 index 0000000000..123fffe8e8 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_BLOB_ENCODING_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_BLOB_ENCODING_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" + +namespace webrtc { + +// Encode/decode a sequence of strings, whose length is not known to be +// discernable from the blob itself (i.e. without being transmitted OOB), +// in a way that would allow us to separate them again on the decoding side. +// The number of blobs is assumed to be transmitted OOB. For example, if +// multiple sequences of different blobs are sent, but all sequences contain +// the same number of blobs, it is beneficial to not encode the number of blobs. +// +// EncodeBlobs() must be given a non-empty vector. The blobs themselves may +// be equal to "", though. +// EncodeBlobs() may not fail. +// EncodeBlobs() never returns the empty string. +// +// Calling DecodeBlobs() on an empty string, or with `num_of_blobs` set to 0, +// is an error. +// DecodeBlobs() returns an empty vector if it fails, e.g. due to a mismatch +// between `num_of_blobs` and `encoded_blobs`, which can happen if +// `encoded_blobs` is corrupted. +// When successful, DecodeBlobs() returns a vector of string_view objects, +// which refer to the original input (`encoded_blobs`), and therefore may +// not outlive it. +// +// Note that the returned std::string might have been reserved for significantly +// more memory than it ends up using. If the caller to EncodeBlobs() intends +// to store the result long-term, they should consider shrink_to_fit()-ing it. +std::string EncodeBlobs(const std::vector<std::string>& blobs); +std::vector<absl::string_view> DecodeBlobs(absl::string_view encoded_blobs, + size_t num_of_blobs); + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_BLOB_ENCODING_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding_unittest.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding_unittest.cc new file mode 100644 index 0000000000..a25923f22d --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/blob_encoding_unittest.cc @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2018 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/blob_encoding.h" + +#include <string> +#include <vector> + +#include "logging/rtc_event_log/encoder/var_int.h" +#include "rtc_base/checks.h" +#include "test/gtest.h" + +using CharT = std::string::value_type; + +namespace webrtc { + +namespace { + +void TestEncodingAndDecoding(const std::vector<std::string>& blobs) { + RTC_DCHECK(!blobs.empty()); + + const std::string encoded = EncodeBlobs(blobs); + ASSERT_FALSE(encoded.empty()); + + const std::vector<absl::string_view> decoded = + DecodeBlobs(encoded, blobs.size()); + + ASSERT_EQ(decoded.size(), blobs.size()); + for (size_t i = 0; i < decoded.size(); ++i) { + ASSERT_EQ(decoded[i], blobs[i]); + } +} + +void TestGracefulErrorHandling(absl::string_view encoded_blobs, + size_t num_of_blobs) { + const std::vector<absl::string_view> decoded = + DecodeBlobs(encoded_blobs, num_of_blobs); + EXPECT_TRUE(decoded.empty()); +} + +} // namespace + +TEST(BlobEncoding, EmptyBlob) { + TestEncodingAndDecoding({""}); +} + +TEST(BlobEncoding, SingleCharacterBlob) { + TestEncodingAndDecoding({"a"}); +} + +TEST(BlobEncoding, LongBlob) { + std::string blob = ""; + for (size_t i = 0; i < 100000; ++i) { + blob += std::to_string(i + 1) + " Mississippi\n"; + } + TestEncodingAndDecoding({blob}); +} + +TEST(BlobEncoding, BlobsOfVariousLengths) { + constexpr size_t kJump = 0xf032d; // Arbitrary. + constexpr size_t kMax = 0xffffff; // Arbitrary. + + std::string blob; + blob.reserve(kMax); + + for (size_t i = 0; i < kMax; i += kJump) { + blob.append(kJump, 'x'); + TestEncodingAndDecoding({blob}); + } +} + +TEST(BlobEncoding, MultipleBlobs) { + std::vector<std::string> blobs; + for (size_t i = 0; i < 100000; ++i) { + blobs.push_back(std::to_string(i + 1) + " Mississippi\n"); + } + TestEncodingAndDecoding(blobs); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyEmptyInput) { + TestGracefulErrorHandling("", 1); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyZeroBlobs) { + const std::string encoded = EncodeBlobs({"a"}); + ASSERT_FALSE(encoded.empty()); + TestGracefulErrorHandling(encoded, 0); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyBlobLengthTooSmall) { + std::string encoded = EncodeBlobs({"ab"}); + ASSERT_FALSE(encoded.empty()); + ASSERT_EQ(encoded[0], 0x02); + encoded[0] = 0x01; + TestGracefulErrorHandling(encoded, 1); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyBlobLengthTooLarge) { + std::string encoded = EncodeBlobs({"a"}); + ASSERT_FALSE(encoded.empty()); + ASSERT_EQ(encoded[0], 0x01); + encoded[0] = 0x02; + TestGracefulErrorHandling(encoded, 1); +} + +TEST(BlobEncoding, + DecodeBlobsHandlesErrorsGracefullyNumberOfBlobsIncorrectlyHigh) { + const std::vector<std::string> blobs = {"a", "b"}; + const std::string encoded = EncodeBlobs(blobs); + // Test focus - two empty strings encoded, but DecodeBlobs() told way more + // blobs are in the strings than could be expected. + TestGracefulErrorHandling(encoded, 1000); + + // Test sanity - show that DecodeBlobs() would have worked if it got the + // correct input. + TestEncodingAndDecoding(blobs); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyDefectiveVarInt) { + std::string defective_varint; + for (size_t i = 0; i < kMaxVarIntLengthBytes; ++i) { + ASSERT_LE(kMaxVarIntLengthBytes, 0xffu); + defective_varint += static_cast<CharT>(static_cast<size_t>(0x80u) | i); + } + defective_varint += 0x01u; + + const std::string defective_encoded = defective_varint + "whatever"; + + TestGracefulErrorHandling(defective_encoded, 1); +} + +TEST(BlobEncoding, DecodeBlobsHandlesErrorsGracefullyLengthSumWrapAround) { + std::string max_size_varint; + for (size_t i = 0; i < kMaxVarIntLengthBytes - 1; ++i) { + max_size_varint += 0xffu; + } + max_size_varint += 0x7fu; + + const std::string defective_encoded = + max_size_varint + max_size_varint + "whatever"; + + TestGracefulErrorHandling(defective_encoded, 2); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.cc new file mode 100644 index 0000000000..c80424574c --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.cc @@ -0,0 +1,839 @@ +/* + * Copyright (c) 2018 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/delta_encoding.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "logging/rtc_event_log/encoder/bit_writer.h" +#include "logging/rtc_event_log/encoder/var_int.h" +#include "rtc_base/bit_buffer.h" +#include "rtc_base/bitstream_reader.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace { + +// TODO(eladalon): Only build the decoder in tools and unit tests. + +bool g_force_unsigned_for_testing = false; +bool g_force_signed_for_testing = false; + +size_t BitsToBytes(size_t bits) { + return (bits / 8) + (bits % 8 > 0 ? 1 : 0); +} + +// TODO(eladalon): Replace by something more efficient. +uint64_t UnsignedBitWidth(uint64_t input, bool zero_val_as_zero_width = false) { + if (zero_val_as_zero_width && input == 0) { + return 0; + } + + uint64_t width = 0; + do { // input == 0 -> width == 1 + width += 1; + input >>= 1; + } while (input != 0); + return width; +} + +uint64_t SignedBitWidth(uint64_t max_pos_magnitude, + uint64_t max_neg_magnitude) { + const uint64_t bitwidth_pos = UnsignedBitWidth(max_pos_magnitude, true); + const uint64_t bitwidth_neg = + (max_neg_magnitude > 0) ? UnsignedBitWidth(max_neg_magnitude - 1, true) + : 0; + return 1 + std::max(bitwidth_pos, bitwidth_neg); +} + +// Return the maximum integer of a given bit width. +// Examples: +// MaxUnsignedValueOfBitWidth(1) = 0x01 +// MaxUnsignedValueOfBitWidth(6) = 0x3f +// MaxUnsignedValueOfBitWidth(8) = 0xff +// MaxUnsignedValueOfBitWidth(32) = 0xffffffff +uint64_t MaxUnsignedValueOfBitWidth(uint64_t bit_width) { + RTC_DCHECK_GE(bit_width, 1); + RTC_DCHECK_LE(bit_width, 64); + return (bit_width == 64) ? std::numeric_limits<uint64_t>::max() + : ((static_cast<uint64_t>(1) << bit_width) - 1); +} + +// Computes the delta between `previous` and `current`, under the assumption +// that wrap-around occurs after `width` is exceeded. +uint64_t UnsignedDelta(uint64_t previous, uint64_t current, uint64_t bit_mask) { + return (current - previous) & bit_mask; +} + +// Determines the encoding type (e.g. fixed-size encoding). +// Given an encoding type, may also distinguish between some variants of it +// (e.g. which fields of the fixed-size encoding are explicitly mentioned by +// the header, and which are implicitly assumed to hold certain default values). +enum class EncodingType { + kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt = 0, + kFixedSizeSignedDeltasEarlyWrapAndOptSupported = 1, + kReserved1 = 2, + kReserved2 = 3, + kNumberOfEncodingTypes // Keep last +}; + +// The width of each field in the encoding header. Note that this is the +// width in case the field exists; not all fields occur in all encoding types. +constexpr size_t kBitsInHeaderForEncodingType = 2; +constexpr size_t kBitsInHeaderForDeltaWidthBits = 6; +constexpr size_t kBitsInHeaderForSignedDeltas = 1; +constexpr size_t kBitsInHeaderForValuesOptional = 1; +constexpr size_t kBitsInHeaderForValueWidthBits = 6; + +static_assert(static_cast<size_t>(EncodingType::kNumberOfEncodingTypes) <= + 1 << kBitsInHeaderForEncodingType, + "Not all encoding types fit."); + +// Default values for when the encoding header does not specify explicitly. +constexpr bool kDefaultSignedDeltas = false; +constexpr bool kDefaultValuesOptional = false; +constexpr uint64_t kDefaultValueWidthBits = 64; + +// Parameters for fixed-size delta-encoding/decoding. +// These are tailored for the sequence which will be encoded (e.g. widths). +class FixedLengthEncodingParameters final { + public: + static bool ValidParameters(uint64_t delta_width_bits, + bool signed_deltas, + bool values_optional, + uint64_t value_width_bits) { + return (1 <= delta_width_bits && delta_width_bits <= 64 && + 1 <= value_width_bits && value_width_bits <= 64 && + delta_width_bits <= value_width_bits); + } + + FixedLengthEncodingParameters(uint64_t delta_width_bits, + bool signed_deltas, + bool values_optional, + uint64_t value_width_bits) + : delta_width_bits_(delta_width_bits), + signed_deltas_(signed_deltas), + values_optional_(values_optional), + value_width_bits_(value_width_bits), + delta_mask_(MaxUnsignedValueOfBitWidth(delta_width_bits_)), + value_mask_(MaxUnsignedValueOfBitWidth(value_width_bits_)) { + RTC_DCHECK(ValidParameters(delta_width_bits, signed_deltas, values_optional, + value_width_bits)); + } + + // Number of bits necessary to hold the widest(*) of the deltas between the + // values in the sequence. + // (*) - Widest might not be the largest, if signed deltas are used. + uint64_t delta_width_bits() const { return delta_width_bits_; } + + // Whether deltas are signed. + bool signed_deltas() const { return signed_deltas_; } + + // Whether the values of the sequence are optional. That is, it may be + // that some of them do not have a value (not even a sentinel value indicating + // invalidity). + bool values_optional() const { return values_optional_; } + + // Number of bits necessary to hold the largest value in the sequence. + uint64_t value_width_bits() const { return value_width_bits_; } + + // Masks where only the bits relevant to the deltas/values are turned on. + uint64_t delta_mask() const { return delta_mask_; } + uint64_t value_mask() const { return value_mask_; } + + void SetSignedDeltas(bool signed_deltas) { signed_deltas_ = signed_deltas; } + void SetDeltaWidthBits(uint64_t delta_width_bits) { + delta_width_bits_ = delta_width_bits; + delta_mask_ = MaxUnsignedValueOfBitWidth(delta_width_bits); + } + + private: + uint64_t delta_width_bits_; // Normally const, but mutable in tests. + bool signed_deltas_; // Normally const, but mutable in tests. + const bool values_optional_; + const uint64_t value_width_bits_; + + uint64_t delta_mask_; // Normally const, but mutable in tests. + const uint64_t value_mask_; +}; + +// Performs delta-encoding of a single (non-empty) sequence of values, using +// an encoding where all deltas are encoded using the same number of bits. +// (With the exception of optional elements; those are encoded as a bit vector +// with one bit per element, plus a fixed number of bits for every element that +// has a value.) +class FixedLengthDeltaEncoder final { + public: + // See webrtc::EncodeDeltas() for general details. + // This function return a bit pattern that would allow the decoder to + // determine whether it was produced by FixedLengthDeltaEncoder, and can + // therefore be decoded by FixedLengthDeltaDecoder, or whether it was produced + // by a different encoder. + static std::string EncodeDeltas( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values); + + FixedLengthDeltaEncoder(const FixedLengthDeltaEncoder&) = delete; + FixedLengthDeltaEncoder& operator=(const FixedLengthDeltaEncoder&) = delete; + + private: + // Calculate min/max values of unsigned/signed deltas, given the bit width + // of all the values in the series. + static void CalculateMinAndMaxDeltas( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + uint64_t bit_width, + uint64_t* max_unsigned_delta, + uint64_t* max_pos_signed_delta, + uint64_t* min_neg_signed_delta); + + // No effect outside of unit tests. + // In unit tests, may lead to forcing signed/unsigned deltas, etc. + static void ConsiderTestOverrides(FixedLengthEncodingParameters* params, + uint64_t delta_width_bits_signed, + uint64_t delta_width_bits_unsigned); + + // FixedLengthDeltaEncoder objects are to be created by EncodeDeltas() and + // released by it before it returns. They're mostly a convenient way to + // avoid having to pass a lot of state between different functions. + // Therefore, it was deemed acceptable to let them have a reference to + // `values`, whose lifetime must exceed the lifetime of `this`. + FixedLengthDeltaEncoder(const FixedLengthEncodingParameters& params, + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + size_t existent_values_count); + + // Perform delta-encoding using the parameters given to the ctor on the + // sequence of values given to the ctor. + std::string Encode(); + + // Exact lengths. + size_t OutputLengthBytes(size_t existent_values_count) const; + size_t HeaderLengthBits() const; + size_t EncodedDeltasLengthBits(size_t existent_values_count) const; + + // Encode the compression parameters into the stream. + void EncodeHeader(); + + // Encode a given delta into the stream. + void EncodeDelta(uint64_t previous, uint64_t current); + void EncodeUnsignedDelta(uint64_t previous, uint64_t current); + void EncodeSignedDelta(uint64_t previous, uint64_t current); + + // The parameters according to which encoding will be done (width of + // fields, whether signed deltas should be used, etc.) + const FixedLengthEncodingParameters params_; + + // The encoding scheme assumes that at least one value is transmitted OOB, + // so that the first value can be encoded as a delta from that OOB value, + // which is `base_`. + const absl::optional<uint64_t> base_; + + // The values to be encoded. + // Note: This is a non-owning reference. See comment above ctor for details. + const std::vector<absl::optional<uint64_t>>& values_; + + // Buffer into which encoded values will be written. + // This is created dynmically as a way to enforce that the rest of the + // ctor has finished running when this is constructed, so that the lower + // bound on the buffer size would be guaranteed correct. + std::unique_ptr<BitWriter> writer_; +}; + +// TODO(eladalon): Reduce the number of passes. +std::string FixedLengthDeltaEncoder::EncodeDeltas( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values) { + RTC_DCHECK(!values.empty()); + + // As a special case, if all of the elements are identical to the base, + // (including, for optional fields, about their existence/non-existence), + // the empty string is used to signal that. + if (std::all_of( + values.cbegin(), values.cend(), + [base](absl::optional<uint64_t> val) { return val == base; })) { + return std::string(); + } + + bool non_decreasing = true; + uint64_t max_value_including_base = base.value_or(0u); + size_t existent_values_count = 0; + { + uint64_t previous = base.value_or(0u); + for (size_t i = 0; i < values.size(); ++i) { + if (!values[i].has_value()) { + continue; + } + ++existent_values_count; + non_decreasing &= (previous <= values[i].value()); + max_value_including_base = + std::max(max_value_including_base, values[i].value()); + previous = values[i].value(); + } + } + + // If the sequence is non-decreasing, it may be assumed to have width = 64; + // there's no reason to encode the actual max width in the encoding header. + const uint64_t value_width_bits = + non_decreasing ? 64 : UnsignedBitWidth(max_value_including_base); + + uint64_t max_unsigned_delta; + uint64_t max_pos_signed_delta; + uint64_t min_neg_signed_delta; + CalculateMinAndMaxDeltas(base, values, value_width_bits, &max_unsigned_delta, + &max_pos_signed_delta, &min_neg_signed_delta); + + const uint64_t delta_width_bits_unsigned = + UnsignedBitWidth(max_unsigned_delta); + const uint64_t delta_width_bits_signed = + SignedBitWidth(max_pos_signed_delta, min_neg_signed_delta); + + // Note: Preference for unsigned if the two have the same width (efficiency). + const bool signed_deltas = + delta_width_bits_signed < delta_width_bits_unsigned; + const uint64_t delta_width_bits = + signed_deltas ? delta_width_bits_signed : delta_width_bits_unsigned; + + const bool values_optional = + !base.has_value() || (existent_values_count < values.size()); + + FixedLengthEncodingParameters params(delta_width_bits, signed_deltas, + values_optional, value_width_bits); + + // No effect in production. + ConsiderTestOverrides(¶ms, delta_width_bits_signed, + delta_width_bits_unsigned); + + FixedLengthDeltaEncoder encoder(params, base, values, existent_values_count); + return encoder.Encode(); +} + +void FixedLengthDeltaEncoder::CalculateMinAndMaxDeltas( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + uint64_t bit_width, + uint64_t* max_unsigned_delta_out, + uint64_t* max_pos_signed_delta_out, + uint64_t* min_neg_signed_delta_out) { + RTC_DCHECK(!values.empty()); + RTC_DCHECK(max_unsigned_delta_out); + RTC_DCHECK(max_pos_signed_delta_out); + RTC_DCHECK(min_neg_signed_delta_out); + + const uint64_t bit_mask = MaxUnsignedValueOfBitWidth(bit_width); + + uint64_t max_unsigned_delta = 0; + uint64_t max_pos_signed_delta = 0; + uint64_t min_neg_signed_delta = 0; + + absl::optional<uint64_t> prev = base; + for (size_t i = 0; i < values.size(); ++i) { + if (!values[i].has_value()) { + continue; + } + + if (!prev.has_value()) { + // If the base is non-existent, the first existent value is encoded as + // a varint, rather than as a delta. + RTC_DCHECK(!base.has_value()); + prev = values[i]; + continue; + } + + const uint64_t current = values[i].value(); + + const uint64_t forward_delta = UnsignedDelta(*prev, current, bit_mask); + const uint64_t backward_delta = UnsignedDelta(current, *prev, bit_mask); + + max_unsigned_delta = std::max(max_unsigned_delta, forward_delta); + + if (forward_delta < backward_delta) { + max_pos_signed_delta = std::max(max_pos_signed_delta, forward_delta); + } else { + min_neg_signed_delta = std::max(min_neg_signed_delta, backward_delta); + } + + prev = current; + } + + *max_unsigned_delta_out = max_unsigned_delta; + *max_pos_signed_delta_out = max_pos_signed_delta; + *min_neg_signed_delta_out = min_neg_signed_delta; +} + +void FixedLengthDeltaEncoder::ConsiderTestOverrides( + FixedLengthEncodingParameters* params, + uint64_t delta_width_bits_signed, + uint64_t delta_width_bits_unsigned) { + if (g_force_unsigned_for_testing) { + params->SetDeltaWidthBits(delta_width_bits_unsigned); + params->SetSignedDeltas(false); + } else if (g_force_signed_for_testing) { + params->SetDeltaWidthBits(delta_width_bits_signed); + params->SetSignedDeltas(true); + } else { + // Unchanged. + } +} + +FixedLengthDeltaEncoder::FixedLengthDeltaEncoder( + const FixedLengthEncodingParameters& params, + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + size_t existent_values_count) + : params_(params), base_(base), values_(values) { + RTC_DCHECK(!values_.empty()); + writer_ = + std::make_unique<BitWriter>(OutputLengthBytes(existent_values_count)); +} + +std::string FixedLengthDeltaEncoder::Encode() { + EncodeHeader(); + + if (params_.values_optional()) { + // Encode which values exist and which don't. + for (absl::optional<uint64_t> value : values_) { + writer_->WriteBits(value.has_value() ? 1u : 0u, 1); + } + } + + absl::optional<uint64_t> previous = base_; + for (absl::optional<uint64_t> value : values_) { + if (!value.has_value()) { + RTC_DCHECK(params_.values_optional()); + continue; + } + + if (!previous.has_value()) { + // If the base is non-existent, the first existent value is encoded as + // a varint, rather than as a delta. + RTC_DCHECK(!base_.has_value()); + writer_->WriteBits(EncodeVarInt(value.value())); + } else { + EncodeDelta(previous.value(), value.value()); + } + + previous = value; + } + + return writer_->GetString(); +} + +size_t FixedLengthDeltaEncoder::OutputLengthBytes( + size_t existent_values_count) const { + return BitsToBytes(HeaderLengthBits() + + EncodedDeltasLengthBits(existent_values_count)); +} + +size_t FixedLengthDeltaEncoder::HeaderLengthBits() const { + if (params_.signed_deltas() == kDefaultSignedDeltas && + params_.values_optional() == kDefaultValuesOptional && + params_.value_width_bits() == kDefaultValueWidthBits) { + return kBitsInHeaderForEncodingType + kBitsInHeaderForDeltaWidthBits; + } else { + return kBitsInHeaderForEncodingType + kBitsInHeaderForDeltaWidthBits + + kBitsInHeaderForSignedDeltas + kBitsInHeaderForValuesOptional + + kBitsInHeaderForValueWidthBits; + } +} + +size_t FixedLengthDeltaEncoder::EncodedDeltasLengthBits( + size_t existent_values_count) const { + if (!params_.values_optional()) { + return values_.size() * params_.delta_width_bits(); + } else { + RTC_DCHECK_EQ(std::count_if(values_.begin(), values_.end(), + [](absl::optional<uint64_t> val) { + return val.has_value(); + }), + existent_values_count); + // One bit for each delta, to indicate if the value exists, and delta_width + // for each existent value, to indicate the delta itself. + // If base_ is non-existent, the first value (if any) is encoded as a varint + // rather than as a delta. + const size_t existence_bitmap_size_bits = 1 * values_.size(); + const bool first_value_is_varint = + !base_.has_value() && existent_values_count >= 1; + const size_t first_value_varint_size_bits = 8 * kMaxVarIntLengthBytes; + const size_t deltas_count = existent_values_count - first_value_is_varint; + const size_t deltas_size_bits = deltas_count * params_.delta_width_bits(); + return existence_bitmap_size_bits + first_value_varint_size_bits + + deltas_size_bits; + } +} + +void FixedLengthDeltaEncoder::EncodeHeader() { + RTC_DCHECK(writer_); + + const EncodingType encoding_type = + (params_.value_width_bits() == kDefaultValueWidthBits && + params_.signed_deltas() == kDefaultSignedDeltas && + params_.values_optional() == kDefaultValuesOptional) + ? EncodingType::kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt + : EncodingType::kFixedSizeSignedDeltasEarlyWrapAndOptSupported; + + writer_->WriteBits(static_cast<uint64_t>(encoding_type), + kBitsInHeaderForEncodingType); + + // Note: Since it's meaningless for a field to be of width 0, when it comes + // to fields that relate widths, we encode width 1 as 0, width 2 as 1, + + writer_->WriteBits(params_.delta_width_bits() - 1, + kBitsInHeaderForDeltaWidthBits); + + if (encoding_type == EncodingType::kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt) { + return; + } + + writer_->WriteBits(static_cast<uint64_t>(params_.signed_deltas()), + kBitsInHeaderForSignedDeltas); + writer_->WriteBits(static_cast<uint64_t>(params_.values_optional()), + kBitsInHeaderForValuesOptional); + writer_->WriteBits(params_.value_width_bits() - 1, + kBitsInHeaderForValueWidthBits); +} + +void FixedLengthDeltaEncoder::EncodeDelta(uint64_t previous, uint64_t current) { + if (params_.signed_deltas()) { + EncodeSignedDelta(previous, current); + } else { + EncodeUnsignedDelta(previous, current); + } +} + +void FixedLengthDeltaEncoder::EncodeUnsignedDelta(uint64_t previous, + uint64_t current) { + RTC_DCHECK(writer_); + const uint64_t delta = UnsignedDelta(previous, current, params_.value_mask()); + writer_->WriteBits(delta, params_.delta_width_bits()); +} + +void FixedLengthDeltaEncoder::EncodeSignedDelta(uint64_t previous, + uint64_t current) { + RTC_DCHECK(writer_); + + const uint64_t forward_delta = + UnsignedDelta(previous, current, params_.value_mask()); + const uint64_t backward_delta = + UnsignedDelta(current, previous, params_.value_mask()); + + uint64_t delta; + if (forward_delta <= backward_delta) { + delta = forward_delta; + } else { + // Compute the unsigned representation of a negative delta. + // This is the two's complement representation of this negative value, + // when deltas are of width params_.delta_mask(). + RTC_DCHECK_GE(params_.delta_mask(), backward_delta); + RTC_DCHECK_LT(params_.delta_mask() - backward_delta, params_.delta_mask()); + delta = params_.delta_mask() - backward_delta + 1; + RTC_DCHECK_LE(delta, params_.delta_mask()); + } + + writer_->WriteBits(delta, params_.delta_width_bits()); +} + +// Perform decoding of a a delta-encoded stream, extracting the original +// sequence of values. +class FixedLengthDeltaDecoder final { + public: + // Checks whether FixedLengthDeltaDecoder is a suitable decoder for this + // bitstream. Note that this does NOT imply that stream is valid, and will + // be decoded successfully. It DOES imply that all other decoder classes + // will fail to decode this input, though. + static bool IsSuitableDecoderFor(absl::string_view input); + + // Assuming that `input` is the result of fixed-size delta-encoding + // that took place with the same value to `base` and over `num_of_deltas` + // original values, this will return the sequence of original values. + // If an error occurs (can happen if `input` is corrupt), an empty + // vector will be returned. + static std::vector<absl::optional<uint64_t>> DecodeDeltas( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas); + + FixedLengthDeltaDecoder(const FixedLengthDeltaDecoder&) = delete; + FixedLengthDeltaDecoder& operator=(const FixedLengthDeltaDecoder&) = delete; + + private: + // Reads the encoding header in `input` and returns a FixedLengthDeltaDecoder + // with the corresponding configuration, that can be used to decode the + // values in `input`. + // If the encoding header is corrupt (contains an illegal configuration), + // nullptr will be returned. + // When a valid FixedLengthDeltaDecoder is returned, this does not mean that + // the entire stream is free of error. Rather, only the encoding header is + // examined and guaranteed. + static std::unique_ptr<FixedLengthDeltaDecoder> Create( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas); + + // FixedLengthDeltaDecoder objects are to be created by DecodeDeltas() and + // released by it before it returns. They're mostly a convenient way to + // avoid having to pass a lot of state between different functions. + // Therefore, it was deemed acceptable that `reader` does not own the buffer + // it reads, meaning the lifetime of `this` must not exceed the lifetime + // of `reader`'s underlying buffer. + FixedLengthDeltaDecoder(BitstreamReader reader, + const FixedLengthEncodingParameters& params, + absl::optional<uint64_t> base, + size_t num_of_deltas); + + // Perform the decoding using the parameters given to the ctor. + std::vector<absl::optional<uint64_t>> Decode(); + + // Add `delta` to `base` to produce the next value in a sequence. + // The delta is applied as signed/unsigned depending on the parameters + // given to the ctor. Wrap-around is taken into account according to the + // values' width, as specified by the aforementioned encoding parameters. + uint64_t ApplyDelta(uint64_t base, uint64_t delta) const; + + // Helpers for ApplyDelta(). + uint64_t ApplyUnsignedDelta(uint64_t base, uint64_t delta) const; + uint64_t ApplySignedDelta(uint64_t base, uint64_t delta) const; + + // Reader of the input stream to be decoded. Does not own that buffer. + // See comment above ctor for details. + BitstreamReader reader_; + + // The parameters according to which encoding will be done (width of + // fields, whether signed deltas should be used, etc.) + const FixedLengthEncodingParameters params_; + + // The encoding scheme assumes that at least one value is transmitted OOB, + // so that the first value can be encoded as a delta from that OOB value, + // which is `base_`. + const absl::optional<uint64_t> base_; + + // The number of values to be known to be decoded. + const size_t num_of_deltas_; +}; + +bool FixedLengthDeltaDecoder::IsSuitableDecoderFor(absl::string_view input) { + BitstreamReader reader(input); + uint64_t encoding_type_bits = reader.ReadBits(kBitsInHeaderForEncodingType); + if (!reader.Ok()) { + return false; + } + + const auto encoding_type = static_cast<EncodingType>(encoding_type_bits); + return encoding_type == + EncodingType::kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt || + encoding_type == + EncodingType::kFixedSizeSignedDeltasEarlyWrapAndOptSupported; +} + +std::vector<absl::optional<uint64_t>> FixedLengthDeltaDecoder::DecodeDeltas( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas) { + auto decoder = FixedLengthDeltaDecoder::Create(input, base, num_of_deltas); + if (!decoder) { + return std::vector<absl::optional<uint64_t>>(); + } + + return decoder->Decode(); +} + +std::unique_ptr<FixedLengthDeltaDecoder> FixedLengthDeltaDecoder::Create( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas) { + BitstreamReader reader(input); + // Encoding type + uint32_t encoding_type_bits = reader.ReadBits(kBitsInHeaderForEncodingType); + if (!reader.Ok()) { + return nullptr; + } + + const EncodingType encoding = static_cast<EncodingType>(encoding_type_bits); + if (encoding != EncodingType::kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt && + encoding != + EncodingType::kFixedSizeSignedDeltasEarlyWrapAndOptSupported) { + RTC_LOG(LS_WARNING) << "Unrecognized encoding type."; + return nullptr; + } + + // See encoding for +1's rationale. + const uint64_t delta_width_bits = + reader.ReadBits(kBitsInHeaderForDeltaWidthBits) + 1; + RTC_DCHECK_LE(delta_width_bits, 64); + + // signed_deltas, values_optional, value_width_bits + bool signed_deltas; + bool values_optional; + uint64_t value_width_bits; + if (encoding == EncodingType::kFixedSizeUnsignedDeltasNoEarlyWrapNoOpt) { + signed_deltas = kDefaultSignedDeltas; + values_optional = kDefaultValuesOptional; + value_width_bits = kDefaultValueWidthBits; + } else { + signed_deltas = reader.Read<bool>(); + values_optional = reader.Read<bool>(); + // See encoding for +1's rationale. + value_width_bits = reader.ReadBits(kBitsInHeaderForValueWidthBits) + 1; + RTC_DCHECK_LE(value_width_bits, 64); + } + + if (!reader.Ok()) { + return nullptr; + } + + // Note: Because of the way the parameters are read, it is not possible + // for illegal values to be read. We check nevertheless, in case the code + // changes in the future in a way that breaks this promise. + if (!FixedLengthEncodingParameters::ValidParameters( + delta_width_bits, signed_deltas, values_optional, value_width_bits)) { + RTC_LOG(LS_WARNING) << "Corrupt log; illegal encoding parameters."; + return nullptr; + } + + FixedLengthEncodingParameters params(delta_width_bits, signed_deltas, + values_optional, value_width_bits); + return absl::WrapUnique( + new FixedLengthDeltaDecoder(reader, params, base, num_of_deltas)); +} + +FixedLengthDeltaDecoder::FixedLengthDeltaDecoder( + BitstreamReader reader, + const FixedLengthEncodingParameters& params, + absl::optional<uint64_t> base, + size_t num_of_deltas) + : reader_(reader), + params_(params), + base_(base), + num_of_deltas_(num_of_deltas) { + RTC_DCHECK(reader_.Ok()); +} + +std::vector<absl::optional<uint64_t>> FixedLengthDeltaDecoder::Decode() { + RTC_DCHECK(reader_.Ok()); + std::vector<bool> existing_values(num_of_deltas_); + if (params_.values_optional()) { + for (size_t i = 0; i < num_of_deltas_; ++i) { + existing_values[i] = reader_.Read<bool>(); + } + } else { + std::fill(existing_values.begin(), existing_values.end(), true); + } + + absl::optional<uint64_t> previous = base_; + std::vector<absl::optional<uint64_t>> values(num_of_deltas_); + + for (size_t i = 0; i < num_of_deltas_; ++i) { + if (!existing_values[i]) { + RTC_DCHECK(params_.values_optional()); + continue; + } + + if (!previous) { + // If the base is non-existent, the first existent value is encoded as + // a varint, rather than as a delta. + RTC_DCHECK(!base_.has_value()); + values[i] = DecodeVarInt(reader_); + } else { + uint64_t delta = reader_.ReadBits(params_.delta_width_bits()); + values[i] = ApplyDelta(*previous, delta); + } + + previous = values[i]; + } + + if (!reader_.Ok()) { + values = {}; + } + + return values; +} + +uint64_t FixedLengthDeltaDecoder::ApplyDelta(uint64_t base, + uint64_t delta) const { + RTC_DCHECK_LE(base, MaxUnsignedValueOfBitWidth(params_.value_width_bits())); + RTC_DCHECK_LE(delta, MaxUnsignedValueOfBitWidth(params_.delta_width_bits())); + return params_.signed_deltas() ? ApplySignedDelta(base, delta) + : ApplyUnsignedDelta(base, delta); +} + +uint64_t FixedLengthDeltaDecoder::ApplyUnsignedDelta(uint64_t base, + uint64_t delta) const { + // Note: May still be used if signed deltas used. + RTC_DCHECK_LE(base, MaxUnsignedValueOfBitWidth(params_.value_width_bits())); + RTC_DCHECK_LE(delta, MaxUnsignedValueOfBitWidth(params_.delta_width_bits())); + return (base + delta) & params_.value_mask(); +} + +uint64_t FixedLengthDeltaDecoder::ApplySignedDelta(uint64_t base, + uint64_t delta) const { + RTC_DCHECK(params_.signed_deltas()); + RTC_DCHECK_LE(base, MaxUnsignedValueOfBitWidth(params_.value_width_bits())); + RTC_DCHECK_LE(delta, MaxUnsignedValueOfBitWidth(params_.delta_width_bits())); + + const uint64_t top_bit = static_cast<uint64_t>(1) + << (params_.delta_width_bits() - 1); + + const bool positive_delta = ((delta & top_bit) == 0); + if (positive_delta) { + return ApplyUnsignedDelta(base, delta); + } + + const uint64_t delta_abs = (~delta & params_.delta_mask()) + 1; + return (base - delta_abs) & params_.value_mask(); +} + +} // namespace + +std::string EncodeDeltas(absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values) { + // TODO(eladalon): Support additional encodings. + return FixedLengthDeltaEncoder::EncodeDeltas(base, values); +} + +std::vector<absl::optional<uint64_t>> DecodeDeltas( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas) { + RTC_DCHECK_GT(num_of_deltas, 0); // Allows empty vector to indicate error. + + // The empty string is a special case indicating that all values were equal + // to the base. + if (input.empty()) { + std::vector<absl::optional<uint64_t>> result(num_of_deltas); + std::fill(result.begin(), result.end(), base); + return result; + } + + if (FixedLengthDeltaDecoder::IsSuitableDecoderFor(input)) { + return FixedLengthDeltaDecoder::DecodeDeltas(input, base, num_of_deltas); + } + + RTC_LOG(LS_WARNING) << "Could not decode delta-encoded stream."; + return std::vector<absl::optional<uint64_t>>(); +} + +void SetFixedLengthEncoderDeltaSignednessForTesting(bool signedness) { + g_force_unsigned_for_testing = !signedness; + g_force_signed_for_testing = signedness; +} + +void UnsetFixedLengthEncoderDeltaSignednessForTesting() { + g_force_unsigned_for_testing = false; + g_force_signed_for_testing = false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.h new file mode 100644 index 0000000000..779cdc6b2f --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_DELTA_ENCODING_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_DELTA_ENCODING_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace webrtc { + +// Encode `values` as a sequence of deltas following on `base` and return it. +// If all of the values were equal to the base, an empty string will be +// returned; this is a valid encoding of that edge case. +// `base` is not guaranteed to be written into `output`, and must therefore +// be provided separately to the decoder. +// This function never fails. +// TODO(eladalon): Split into optional and non-optional variants (efficiency). +std::string EncodeDeltas(absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values); + +// EncodeDeltas() and DecodeDeltas() are inverse operations; +// invoking DecodeDeltas() over the output of EncodeDeltas(), will return +// the input originally given to EncodeDeltas(). +// `num_of_deltas` must be greater than zero. If input is not a valid encoding +// of `num_of_deltas` elements based on `base`, the function returns an empty +// vector, which signals an error. +// TODO(eladalon): Split into optional and non-optional variants (efficiency). +std::vector<absl::optional<uint64_t>> DecodeDeltas( + absl::string_view input, + absl::optional<uint64_t> base, + size_t num_of_deltas); + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_DELTA_ENCODING_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc new file mode 100644 index 0000000000..d0f7fb93db --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc @@ -0,0 +1,693 @@ +/* + * Copyright (c) 2018 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/delta_encoding.h" + +#include <algorithm> +#include <limits> +#include <numeric> +#include <string> +#include <tuple> +#include <vector> + +#include "absl/types/optional.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { + +void SetFixedLengthEncoderDeltaSignednessForTesting(bool signedness); +void UnsetFixedLengthEncoderDeltaSignednessForTesting(); + +namespace { + +enum class DeltaSignedness { kNoOverride, kForceUnsigned, kForceSigned }; + +void MaybeSetSignedness(DeltaSignedness signedness) { + switch (signedness) { + case DeltaSignedness::kNoOverride: + UnsetFixedLengthEncoderDeltaSignednessForTesting(); + return; + case DeltaSignedness::kForceUnsigned: + SetFixedLengthEncoderDeltaSignednessForTesting(false); + return; + case DeltaSignedness::kForceSigned: + SetFixedLengthEncoderDeltaSignednessForTesting(true); + return; + } + RTC_DCHECK_NOTREACHED(); +} + +uint64_t RandomWithMaxBitWidth(Random* prng, uint64_t max_width) { + RTC_DCHECK_GE(max_width, 1u); + RTC_DCHECK_LE(max_width, 64u); + + const uint64_t low = prng->Rand(std::numeric_limits<uint32_t>::max()); + const uint64_t high = + max_width > 32u ? prng->Rand(std::numeric_limits<uint32_t>::max()) : 0u; + + const uint64_t random_before_mask = (high << 32) | low; + + if (max_width < 64) { + return random_before_mask & ((static_cast<uint64_t>(1) << max_width) - 1); + } else { + return random_before_mask; + } +} + +// Encodes `values` based on `base`, then decodes the result and makes sure +// that it is equal to the original input. +// If `encoded_string` is non-null, the encoded result will also be written +// into it. +void TestEncodingAndDecoding( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + std::string* encoded_string = nullptr) { + const std::string encoded = EncodeDeltas(base, values); + if (encoded_string) { + *encoded_string = encoded; + } + + const std::vector<absl::optional<uint64_t>> decoded = + DecodeDeltas(encoded, base, values.size()); + + EXPECT_EQ(decoded, values); +} + +std::vector<absl::optional<uint64_t>> CreateSequenceByFirstValue( + uint64_t first, + size_t sequence_length) { + std::vector<absl::optional<uint64_t>> sequence(sequence_length); + std::iota(sequence.begin(), sequence.end(), first); + return sequence; +} + +std::vector<absl::optional<uint64_t>> CreateSequenceByLastValue( + uint64_t last, + size_t num_values) { + const uint64_t first = last - num_values + 1; + std::vector<absl::optional<uint64_t>> result(num_values); + std::iota(result.begin(), result.end(), first); + return result; +} + +// If `sequence_length` is greater than the number of deltas, the sequence of +// deltas will wrap around. +std::vector<absl::optional<uint64_t>> CreateSequenceByOptionalDeltas( + uint64_t first, + const std::vector<absl::optional<uint64_t>>& deltas, + size_t sequence_length) { + RTC_DCHECK_GE(sequence_length, 1); + + std::vector<absl::optional<uint64_t>> sequence(sequence_length); + + uint64_t previous = first; + for (size_t i = 0, next_delta_index = 0; i < sequence.size(); ++i) { + if (deltas[next_delta_index].has_value()) { + sequence[i] = + absl::optional<uint64_t>(previous + deltas[next_delta_index].value()); + previous = sequence[i].value(); + } + next_delta_index = (next_delta_index + 1) % deltas.size(); + } + + return sequence; +} + +size_t EncodingLengthUpperBound(size_t delta_max_bit_width, + size_t num_of_deltas, + DeltaSignedness signedness_override) { + absl::optional<size_t> smallest_header_size_bytes; + switch (signedness_override) { + case DeltaSignedness::kNoOverride: + case DeltaSignedness::kForceUnsigned: + smallest_header_size_bytes = 1; + break; + case DeltaSignedness::kForceSigned: + smallest_header_size_bytes = 2; + break; + } + RTC_DCHECK(smallest_header_size_bytes); + + return delta_max_bit_width * num_of_deltas + *smallest_header_size_bytes; +} + +// If `sequence_length` is greater than the number of deltas, the sequence of +// deltas will wrap around. +std::vector<absl::optional<uint64_t>> CreateSequenceByDeltas( + uint64_t first, + const std::vector<uint64_t>& deltas, + size_t sequence_length) { + RTC_DCHECK(!deltas.empty()); + std::vector<absl::optional<uint64_t>> optional_deltas(deltas.size()); + for (size_t i = 0; i < deltas.size(); ++i) { + optional_deltas[i] = absl::optional<uint64_t>(deltas[i]); + } + return CreateSequenceByOptionalDeltas(first, optional_deltas, + sequence_length); +} + +// Tests of the delta encoding, parameterized by the number of values +// in the sequence created by the test. +class DeltaEncodingTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, size_t, bool, uint64_t>> { + public: + DeltaEncodingTest() + : signedness_(std::get<0>(GetParam())), + num_of_values_(std::get<1>(GetParam())), + optional_values_(std::get<2>(GetParam())), + partial_random_seed_(std::get<3>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * num_of_values_ + + 5 * optional_values_ + 7 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t num_of_values_; + const bool optional_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +TEST_P(DeltaEncodingTest, AllValuesEqualToExistentBaseValue) { + const absl::optional<uint64_t> base(3432); + std::vector<absl::optional<uint64_t>> values(num_of_values_); + std::fill(values.begin(), values.end(), base); + std::string encoded; + TestEncodingAndDecoding(base, values, &encoded); + + // Additional requirement - the encoding should be efficient in this + // case - the empty string will be used. + EXPECT_TRUE(encoded.empty()); +} + +TEST_P(DeltaEncodingTest, AllValuesEqualToNonExistentBaseValue) { + if (!optional_values_) { + return; // Test irrelevant for this case. + } + + const absl::optional<uint64_t> base; + std::vector<absl::optional<uint64_t>> values(num_of_values_); + std::fill(values.begin(), values.end(), base); + std::string encoded; + TestEncodingAndDecoding(base, values, &encoded); + + // Additional requirement - the encoding should be efficient in this + // case - the empty string will be used. + EXPECT_TRUE(encoded.empty()); +} + +TEST_P(DeltaEncodingTest, BaseNonExistentButSomeOtherValuesExist) { + if (!optional_values_) { + return; // Test irrelevant for this case. + } + + const absl::optional<uint64_t> base; + std::vector<absl::optional<uint64_t>> values(num_of_values_); + + Random prng(Seed()); + + const uint64_t max_bit_width = 1 + prng.Rand(63); // [1, 64] + + for (size_t i = 0; i < values.size();) { + // Leave a random number of values as non-existent. + const size_t non_existent_count = prng.Rand(values.size() - i - 1); + i += non_existent_count; + + // Assign random values to a random number of values. (At least one, to + // prevent this iteration of the outer loop from being a no-op.) + const size_t existent_count = + std::max<size_t>(prng.Rand(values.size() - i - 1), 1); + for (size_t j = 0; j < existent_count; ++j) { + values[i + j] = RandomWithMaxBitWidth(&prng, max_bit_width); + } + i += existent_count; + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MinDeltaNoWrapAround) { + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByFirstValue(base.value() + 1, num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, BigDeltaNoWrapAround) { + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(3432); + + auto values = + CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MaxDeltaNoWrapAround) { + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByLastValue(std::numeric_limits<uint64_t>::max(), + num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max()); + + auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); + ASSERT_LT(values[0], base) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - 2); + + auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); + ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +// Suppress "integral constant overflow" warning; this is the test's focus. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4307) +#endif +TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - + kBigDelta + 3); + + auto values = + CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); + ASSERT_LT(values[0], base.value()) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - + kBigDelta + 3); + + auto values = CreateSequenceByFirstValue(std::numeric_limits<uint64_t>::max(), + num_of_values_); + ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const absl::optional<uint64_t> base(3432); + auto values = CreateSequenceByFirstValue(*base - 1, num_of_values_); + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByDeltas( + *base, {0, std::numeric_limits<uint64_t>::max(), 3}, num_of_values_); + // Wraps around continuously by virtue of being max(); will not ASSERT. + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +// If num_of_values_ == 1, a zero delta will yield an empty string; that's +// already covered by AllValuesEqualToExistentBaseValue, but it doesn't hurt to +// test again. For all other cases, we have a new test. +TEST_P(DeltaEncodingTest, ZeroDelta) { + const absl::optional<uint64_t> base(3432); + + // Arbitrary sequence of deltas with intentional zero deltas, as well as + // consecutive zeros. + const std::vector<uint64_t> deltas = {0, 312, 11, 1, 1, 0, 0, 12, + 400321, 3, 3, 12, 5, 0, 6}; + auto values = CreateSequenceByDeltas(base.value(), deltas, num_of_values_); + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndNumberOfValuesInSequence, + DeltaEncodingTest, + ::testing::Combine(::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 2, 100, 10000), + ::testing::Bool(), + ::testing::Values(10, 20, 30))); + +// Tests over the quality of the compression (as opposed to its correctness). +// Not to be confused with tests of runtime efficiency. +class DeltaEncodingCompressionQualityTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, uint64_t, uint64_t>> { + public: + DeltaEncodingCompressionQualityTest() + : signedness_(std::get<0>(GetParam())), + delta_max_bit_width_(std::get<1>(GetParam())), + num_of_values_(std::get<2>(GetParam())), + partial_random_seed_(std::get<3>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingCompressionQualityTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + + 5 * delta_max_bit_width_ + 7 * num_of_values_ + + 11 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t delta_max_bit_width_; + const uint64_t num_of_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +// If no wrap-around occurs in the stream, the width of the values does not +// matter to compression performance; only the deltas matter. +TEST_P(DeltaEncodingCompressionQualityTest, + BaseDoesNotAffectEfficiencyIfNoWrapAround) { + // 1. Bases which will not produce a wrap-around. + // 2. The last base - 0xffffffffffffffff - does cause a wrap-around, but + // that still works, because the width is 64 anyway, and does not + // need to be conveyed explicitly in the encoding header. + const uint64_t bases[] = {0, 0x55, 0xffffffff, + std::numeric_limits<uint64_t>::max()}; + const size_t kIntendedWrapAroundBaseIndex = arraysize(bases); + + std::vector<uint64_t> deltas(num_of_values_); + + // Allows us to make sure that the deltas do not produce a wrap-around. + uint64_t last_element[arraysize(bases)]; + memcpy(last_element, bases, sizeof(bases)); + + // Avoid empty `deltas` due to first element causing wrap-around. + deltas[0] = 1; + for (size_t i = 0; i < arraysize(last_element); ++i) { + last_element[i] += 1; + } + + Random prng(Seed()); + + for (size_t i = 1; i < deltas.size(); ++i) { + const uint64_t delta = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + + bool wrap_around = false; + for (size_t j = 0; j < arraysize(last_element); ++j) { + if (j == kIntendedWrapAroundBaseIndex) { + continue; + } + + last_element[j] += delta; + if (last_element[j] < bases[j]) { + wrap_around = true; + break; + } + } + + if (wrap_around) { + deltas.resize(i); + break; + } + + deltas[i] = delta; + } + + std::string encodings[arraysize(bases)]; + + for (size_t i = 0; i < arraysize(bases); ++i) { + const auto values = + CreateSequenceByDeltas(bases[i], deltas, num_of_values_); + // Produce the encoding and write it to encodings[i]. + // By using TestEncodingAndDecoding() to do this, we also sanity-test + // the encoding/decoding, though that is not the test's focus. + TestEncodingAndDecoding(bases[i], values, &encodings[i]); + EXPECT_LE(encodings[i].length(), + EncodingLengthUpperBound(delta_max_bit_width_, num_of_values_, + signedness_)); + } + + // Test focus - all of the encodings should be the same, as they are based + // on the same delta sequence, and do not contain a wrap-around. + for (size_t i = 1; i < arraysize(encodings); ++i) { + EXPECT_EQ(encodings[i], encodings[0]); + } +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, + DeltaEncodingCompressionQualityTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Values(1, 2, 100, 10000), + ::testing::Values(11, 12, 13))); + +// Similar to DeltaEncodingTest, but instead of semi-surgically producing +// specific cases, produce large amount of semi-realistic inputs. +class DeltaEncodingFuzzerLikeTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, uint64_t, bool, uint64_t>> { + public: + DeltaEncodingFuzzerLikeTest() + : signedness_(std::get<0>(GetParam())), + delta_max_bit_width_(std::get<1>(GetParam())), + num_of_values_(std::get<2>(GetParam())), + optional_values_(std::get<3>(GetParam())), + partial_random_seed_(std::get<4>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingFuzzerLikeTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + + 5 * delta_max_bit_width_ + 7 * num_of_values_ + + 11 * static_cast<uint64_t>(optional_values_) + + 13 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t delta_max_bit_width_; + const uint64_t num_of_values_; + const bool optional_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +TEST_P(DeltaEncodingFuzzerLikeTest, Test) { + const absl::optional<uint64_t> base(3432); + + Random prng(Seed()); + std::vector<absl::optional<uint64_t>> deltas(num_of_values_); + for (size_t i = 0; i < deltas.size(); ++i) { + if (!optional_values_ || prng.Rand<bool>()) { + deltas[i] = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + } + } + const auto values = + CreateSequenceByOptionalDeltas(base.value(), deltas, num_of_values_); + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, + DeltaEncodingFuzzerLikeTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Values(1, 2, 100, 10000), + ::testing::Bool(), + ::testing::Values(21, 22, 23))); + +class DeltaEncodingSpecificEdgeCasesTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, bool>> { + public: + DeltaEncodingSpecificEdgeCasesTest() { + UnsetFixedLengthEncoderDeltaSignednessForTesting(); + } + + ~DeltaEncodingSpecificEdgeCasesTest() override = default; +}; + +// This case is special because it produces identical forward/backward deltas. +TEST_F(DeltaEncodingSpecificEdgeCasesTest, SignedDeltaWithOnlyTopBitOn) { + MaybeSetSignedness(DeltaSignedness::kForceSigned); + + const absl::optional<uint64_t> base(3432); + + const uint64_t delta = static_cast<uint64_t>(1) << 63; + const std::vector<absl::optional<uint64_t>> values = {base.value() + delta}; + + TestEncodingAndDecoding(base, values); +} + +TEST_F(DeltaEncodingSpecificEdgeCasesTest, MaximumUnsignedDelta) { + MaybeSetSignedness(DeltaSignedness::kForceUnsigned); + + const absl::optional<uint64_t> base((static_cast<uint64_t>(1) << 63) + 0x123); + + const std::vector<absl::optional<uint64_t>> values = {base.value() - 1}; + + TestEncodingAndDecoding(base, values); +} + +// Check that, if all deltas are set to -1, things still work. +TEST_P(DeltaEncodingSpecificEdgeCasesTest, ReverseSequence) { + MaybeSetSignedness(std::get<0>(GetParam())); + const uint64_t width = std::get<1>(GetParam()); + const bool wrap_around = std::get<2>(GetParam()); + + const uint64_t value_mask = (width == 64) + ? std::numeric_limits<uint64_t>::max() + : ((static_cast<uint64_t>(1) << width) - 1); + + const uint64_t base = wrap_around ? 1u : (0xf82d3 & value_mask); + const std::vector<absl::optional<uint64_t>> values = { + (base - 1u) & value_mask, (base - 2u) & value_mask, + (base - 3u) & value_mask}; + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + _, + DeltaEncodingSpecificEdgeCasesTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Bool())); + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder.h new file mode 100644 index 0000000000..3c3dc78990 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_H_ + +#include <deque> +#include <memory> +#include <string> + +#include "api/rtc_event_log/rtc_event.h" + +namespace webrtc { +class RtcEventLogEncoder { + public: + virtual ~RtcEventLogEncoder() = default; + + virtual std::string EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) = 0; + virtual std::string EncodeLogEnd(int64_t timestamp_us) = 0; + + virtual std::string EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) = 0; +}; + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc new file mode 100644 index 0000000000..7aea47611d --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 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/rtc_event_log_encoder_common.h" + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { +// We use 0x3fff because that gives decent precision (compared to the underlying +// measurement producing the packet loss fraction) on the one hand, while +// allowing us to use no more than 2 bytes in varint form on the other hand. +// (We might also fixed-size encode using at most 14 bits.) +constexpr uint32_t kPacketLossFractionRange = (1 << 14) - 1; // 0x3fff +constexpr float kPacketLossFractionRangeFloat = + static_cast<float>(kPacketLossFractionRange); +} // namespace + +uint32_t ConvertPacketLossFractionToProtoFormat(float packet_loss_fraction) { + RTC_DCHECK_GE(packet_loss_fraction, 0); + RTC_DCHECK_LE(packet_loss_fraction, 1); + return static_cast<uint32_t>(packet_loss_fraction * kPacketLossFractionRange); +} + +bool ParsePacketLossFractionFromProtoFormat(uint32_t proto_packet_loss_fraction, + float* output) { + if (proto_packet_loss_fraction >= kPacketLossFractionRange) { + return false; + } + *output = proto_packet_loss_fraction / kPacketLossFractionRangeFloat; + return true; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h new file mode 100644 index 0000000000..c167a8eb8f --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_ + +#include <stdint.h> + +#include <limits> +#include <type_traits> + +namespace webrtc { + +// Convert between the packet fraction loss (a floating point number in +// the range [0.0, 1.0]), and a uint32_t with up to a fixed number of bits. +// The latter can be more efficiently stored in a protobuf and/or delta-encoded. +uint32_t ConvertPacketLossFractionToProtoFormat(float packet_loss_fraction); +bool ParsePacketLossFractionFromProtoFormat(uint32_t proto_packet_loss_fraction, + float* output); + +} // namespace webrtc + +namespace webrtc_event_logging { + +// Produce an unsigned representation of a signed integer. On two's complement +// machines, this is equivalent to: +// static_cast<uint64_t>(static_cast<std::make_unsigned<T>>(y)) +template <typename T> +uint64_t ToUnsigned(T y) { + static_assert(std::is_integral<T>::value, ""); + static_assert(std::is_signed<T>::value, ""); + + // Note that a signed integer whose width is N bits, has N-1 digits. + static_assert(std::numeric_limits<T>::digits < 64, ""); + + constexpr T MIN_T = std::numeric_limits<T>::min(); + constexpr T MAX_T = std::numeric_limits<T>::max(); + + static_assert(MAX_T + MIN_T + 1 >= 0, "MAX_T >= abs(MIN_T) - 1"); + + if (y >= 0) { + return static_cast<uint64_t>(y); + } else { + // y is in the range [MIN_T, -1], so (y - MIN_T) is in the + // range [0, abs(MIN_T) - 1]. This is representable in a T + // because MAX_T >= abs(MIN_T) - 1, as per the static_assert above. + return static_cast<uint64_t>(MAX_T) + 1 + static_cast<uint64_t>(y - MIN_T); + } +} + +// Assuming x = ToUnsigned(y), return `y`. +// Note: static_cast<T>(x) would work on most platforms and compilers, but +// involves undefined behavior. This function is well-defined, and can be +// optimized to a noop for 64 bit types, or a few arithmetic +// instructions and a single conditional jump for narrower types. +template <typename T> +bool ToSigned(uint64_t x, T* y) { + static_assert(std::is_integral<T>::value, ""); + static_assert(std::is_signed<T>::value, ""); + + // Note that a signed integer whose width is N bits, has N-1 digits. + static_assert(std::numeric_limits<T>::digits < 64, ""); + + constexpr T MIN_T = std::numeric_limits<T>::min(); + constexpr T MAX_T = std::numeric_limits<T>::max(); + + using UNSIGNED_T = typename std::make_unsigned<T>::type; + constexpr auto MAX_UNSIGNED_T = std::numeric_limits<UNSIGNED_T>::max(); + if (x > static_cast<uint64_t>(MAX_UNSIGNED_T)) { + return false; // `x` cannot be represented using a T. + } + + if (x <= static_cast<uint64_t>(MAX_T)) { + // The original value was positive, so it is safe to just static_cast. + *y = static_cast<T>(x); + } else { // x > static_cast<uint64_t>(MAX_T) + const uint64_t neg_x = x - static_cast<uint64_t>(MAX_T) - 1; + *y = static_cast<T>(neg_x) + MIN_T; + } + + return true; +} + +} // namespace webrtc_event_logging + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc new file mode 100644 index 0000000000..0afa5368d1 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 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/rtc_event_log_encoder_common.h" + +#include <cstdint> +#include <limits> +#include <type_traits> +#include <vector> + +#include "test/gtest.h" + +namespace webrtc_event_logging { +namespace { + +template <typename T> +class SignednessConversionTest : public ::testing::Test { + public: + static_assert(std::is_integral<T>::value, ""); + static_assert(std::is_signed<T>::value, ""); +}; + +TYPED_TEST_SUITE_P(SignednessConversionTest); + +TYPED_TEST_P(SignednessConversionTest, CorrectlyConvertsLegalValues) { + using T = TypeParam; + std::vector<T> legal_values = {std::numeric_limits<T>::min(), + std::numeric_limits<T>::min() + 1, + -1, + 0, + 1, + std::numeric_limits<T>::max() - 1, + std::numeric_limits<T>::max()}; + for (T val : legal_values) { + const auto unsigned_val = ToUnsigned(val); + T signed_val; + ASSERT_TRUE(ToSigned<T>(unsigned_val, &signed_val)) + << "Failed on " << static_cast<uint64_t>(unsigned_val) << "."; + EXPECT_EQ(val, signed_val) + << "Failed on " << static_cast<uint64_t>(unsigned_val) << "."; + } +} + +TYPED_TEST_P(SignednessConversionTest, FailsOnConvertingIllegalValues) { + using T = TypeParam; + + // Note that a signed integer whose width is N bits, has N-1 digits. + constexpr bool width_is_64 = std::numeric_limits<T>::digits == 63; + + if (width_is_64) { + return; // Test irrelevant; illegal values do not exist. + } + + const uint64_t max_legal_value = ToUnsigned(static_cast<T>(-1)); + + const std::vector<uint64_t> illegal_values = { + max_legal_value + 1u, max_legal_value + 2u, + std::numeric_limits<uint64_t>::max() - 1u, + std::numeric_limits<uint64_t>::max()}; + + for (uint64_t unsigned_val : illegal_values) { + T signed_val; + EXPECT_FALSE(ToSigned<T>(unsigned_val, &signed_val)) + << "Failed on " << static_cast<uint64_t>(unsigned_val) << "."; + } +} + +REGISTER_TYPED_TEST_SUITE_P(SignednessConversionTest, + CorrectlyConvertsLegalValues, + FailsOnConvertingIllegalValues); + +using Types = ::testing::Types<int8_t, int16_t, int32_t, int64_t>; + +INSTANTIATE_TYPED_TEST_SUITE_P(_, SignednessConversionTest, Types); + +} // namespace +} // namespace webrtc_event_logging diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.cc new file mode 100644 index 0000000000..5619827246 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.cc @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2017 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/rtc_event_log_encoder_legacy.h" + +#include <string.h> + +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/network_state_predictor.h" +#include "api/rtp_headers.h" +#include "api/rtp_parameters.h" +#include "api/transport/network_types.h" +#include "logging/rtc_event_log/events/rtc_event_alr_state.h" +#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h" +#include "logging/rtc_event_log/events/rtc_event_audio_playout.h" +#include "logging/rtc_event_log/events/rtc_event_audio_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" +#include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" +#include "logging/rtc_event_log/events/rtc_event_remote_estimate.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h" +#include "logging/rtc_event_log/rtc_stream_config.h" +#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sdes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "rtc_base/checks.h" +#include "rtc_base/ignore_wundef.h" +#include "rtc_base/logging.h" + +// *.pb.h files are generated at build-time by the protobuf compiler. +RTC_PUSH_IGNORING_WUNDEF() +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/logging/rtc_event_log/rtc_event_log.pb.h" +#else +#include "logging/rtc_event_log/rtc_event_log.pb.h" +#endif +RTC_POP_IGNORING_WUNDEF() + +namespace webrtc { + +namespace { +rtclog::DelayBasedBweUpdate::DetectorState ConvertDetectorState( + BandwidthUsage state) { + switch (state) { + case BandwidthUsage::kBwNormal: + return rtclog::DelayBasedBweUpdate::BWE_NORMAL; + case BandwidthUsage::kBwUnderusing: + return rtclog::DelayBasedBweUpdate::BWE_UNDERUSING; + case BandwidthUsage::kBwOverusing: + return rtclog::DelayBasedBweUpdate::BWE_OVERUSING; + case BandwidthUsage::kLast: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::DelayBasedBweUpdate::BWE_NORMAL; +} + +rtclog::BweProbeResult::ResultType ConvertProbeResultType( + ProbeFailureReason failure_reason) { + switch (failure_reason) { + case ProbeFailureReason::kInvalidSendReceiveInterval: + return rtclog::BweProbeResult::INVALID_SEND_RECEIVE_INTERVAL; + case ProbeFailureReason::kInvalidSendReceiveRatio: + return rtclog::BweProbeResult::INVALID_SEND_RECEIVE_RATIO; + case ProbeFailureReason::kTimeout: + return rtclog::BweProbeResult::TIMEOUT; + case ProbeFailureReason::kLast: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::BweProbeResult::SUCCESS; +} + +rtclog::VideoReceiveConfig_RtcpMode ConvertRtcpMode(RtcpMode rtcp_mode) { + switch (rtcp_mode) { + case RtcpMode::kCompound: + return rtclog::VideoReceiveConfig::RTCP_COMPOUND; + case RtcpMode::kReducedSize: + return rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE; + case RtcpMode::kOff: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::VideoReceiveConfig::RTCP_COMPOUND; +} + +rtclog::IceCandidatePairConfig::IceCandidatePairConfigType +ConvertIceCandidatePairConfigType(IceCandidatePairConfigType type) { + switch (type) { + case IceCandidatePairConfigType::kAdded: + return rtclog::IceCandidatePairConfig::ADDED; + case IceCandidatePairConfigType::kUpdated: + return rtclog::IceCandidatePairConfig::UPDATED; + case IceCandidatePairConfigType::kDestroyed: + return rtclog::IceCandidatePairConfig::DESTROYED; + case IceCandidatePairConfigType::kSelected: + return rtclog::IceCandidatePairConfig::SELECTED; + case IceCandidatePairConfigType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairConfig::ADDED; +} + +rtclog::IceCandidatePairConfig::IceCandidateType ConvertIceCandidateType( + IceCandidateType type) { + switch (type) { + case IceCandidateType::kUnknown: + return rtclog::IceCandidatePairConfig::UNKNOWN_CANDIDATE_TYPE; + case IceCandidateType::kLocal: + return rtclog::IceCandidatePairConfig::LOCAL; + case IceCandidateType::kStun: + return rtclog::IceCandidatePairConfig::STUN; + case IceCandidateType::kPrflx: + return rtclog::IceCandidatePairConfig::PRFLX; + case IceCandidateType::kRelay: + return rtclog::IceCandidatePairConfig::RELAY; + case IceCandidateType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairConfig::UNKNOWN_CANDIDATE_TYPE; +} + +rtclog::IceCandidatePairConfig::Protocol ConvertIceCandidatePairProtocol( + IceCandidatePairProtocol protocol) { + switch (protocol) { + case IceCandidatePairProtocol::kUnknown: + return rtclog::IceCandidatePairConfig::UNKNOWN_PROTOCOL; + case IceCandidatePairProtocol::kUdp: + return rtclog::IceCandidatePairConfig::UDP; + case IceCandidatePairProtocol::kTcp: + return rtclog::IceCandidatePairConfig::TCP; + case IceCandidatePairProtocol::kSsltcp: + return rtclog::IceCandidatePairConfig::SSLTCP; + case IceCandidatePairProtocol::kTls: + return rtclog::IceCandidatePairConfig::TLS; + case IceCandidatePairProtocol::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairConfig::UNKNOWN_PROTOCOL; +} + +rtclog::IceCandidatePairConfig::AddressFamily +ConvertIceCandidatePairAddressFamily( + IceCandidatePairAddressFamily address_family) { + switch (address_family) { + case IceCandidatePairAddressFamily::kUnknown: + return rtclog::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY; + case IceCandidatePairAddressFamily::kIpv4: + return rtclog::IceCandidatePairConfig::IPV4; + case IceCandidatePairAddressFamily::kIpv6: + return rtclog::IceCandidatePairConfig::IPV6; + case IceCandidatePairAddressFamily::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY; +} + +rtclog::IceCandidatePairConfig::NetworkType ConvertIceCandidateNetworkType( + IceCandidateNetworkType network_type) { + switch (network_type) { + case IceCandidateNetworkType::kUnknown: + return rtclog::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE; + case IceCandidateNetworkType::kEthernet: + return rtclog::IceCandidatePairConfig::ETHERNET; + case IceCandidateNetworkType::kLoopback: + return rtclog::IceCandidatePairConfig::LOOPBACK; + case IceCandidateNetworkType::kWifi: + return rtclog::IceCandidatePairConfig::WIFI; + case IceCandidateNetworkType::kVpn: + return rtclog::IceCandidatePairConfig::VPN; + case IceCandidateNetworkType::kCellular: + return rtclog::IceCandidatePairConfig::CELLULAR; + case IceCandidateNetworkType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE; +} + +rtclog::IceCandidatePairEvent::IceCandidatePairEventType +ConvertIceCandidatePairEventType(IceCandidatePairEventType type) { + switch (type) { + case IceCandidatePairEventType::kCheckSent: + return rtclog::IceCandidatePairEvent::CHECK_SENT; + case IceCandidatePairEventType::kCheckReceived: + return rtclog::IceCandidatePairEvent::CHECK_RECEIVED; + case IceCandidatePairEventType::kCheckResponseSent: + return rtclog::IceCandidatePairEvent::CHECK_RESPONSE_SENT; + case IceCandidatePairEventType::kCheckResponseReceived: + return rtclog::IceCandidatePairEvent::CHECK_RESPONSE_RECEIVED; + case IceCandidatePairEventType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog::IceCandidatePairEvent::CHECK_SENT; +} + +} // namespace + +std::string RtcEventLogEncoderLegacy::EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(timestamp_us); + rtclog_event.set_type(rtclog::Event::LOG_START); + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeLogEnd(int64_t timestamp_us) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(timestamp_us); + rtclog_event.set_type(rtclog::Event::LOG_END); + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) { + std::string encoded_output; + for (auto it = begin; it != end; ++it) { + // TODO(terelius): Can we avoid the slight inefficiency of reallocating the + // string? + RTC_CHECK(it->get() != nullptr); + encoded_output += Encode(**it); + } + return encoded_output; +} + +std::string RtcEventLogEncoderLegacy::Encode(const RtcEvent& event) { + switch (event.GetType()) { + case RtcEvent::Type::AudioNetworkAdaptation: { + auto& rtc_event = + static_cast<const RtcEventAudioNetworkAdaptation&>(event); + return EncodeAudioNetworkAdaptation(rtc_event); + } + + case RtcEvent::Type::AlrStateEvent: { + auto& rtc_event = static_cast<const RtcEventAlrState&>(event); + return EncodeAlrState(rtc_event); + } + + case RtcEvent::Type::AudioPlayout: { + auto& rtc_event = static_cast<const RtcEventAudioPlayout&>(event); + return EncodeAudioPlayout(rtc_event); + } + + case RtcEvent::Type::AudioReceiveStreamConfig: { + auto& rtc_event = + static_cast<const RtcEventAudioReceiveStreamConfig&>(event); + return EncodeAudioReceiveStreamConfig(rtc_event); + } + + case RtcEvent::Type::AudioSendStreamConfig: { + auto& rtc_event = + static_cast<const RtcEventAudioSendStreamConfig&>(event); + return EncodeAudioSendStreamConfig(rtc_event); + } + + case RtcEvent::Type::BweUpdateDelayBased: { + auto& rtc_event = static_cast<const RtcEventBweUpdateDelayBased&>(event); + return EncodeBweUpdateDelayBased(rtc_event); + } + + case RtcEvent::Type::BweUpdateLossBased: { + auto& rtc_event = static_cast<const RtcEventBweUpdateLossBased&>(event); + return EncodeBweUpdateLossBased(rtc_event); + } + + case RtcEvent::Type::DtlsTransportState: { + return ""; + } + + case RtcEvent::Type::DtlsWritableState: { + return ""; + } + + case RtcEvent::Type::IceCandidatePairConfig: { + auto& rtc_event = + static_cast<const RtcEventIceCandidatePairConfig&>(event); + return EncodeIceCandidatePairConfig(rtc_event); + } + + case RtcEvent::Type::IceCandidatePairEvent: { + auto& rtc_event = static_cast<const RtcEventIceCandidatePair&>(event); + return EncodeIceCandidatePairEvent(rtc_event); + } + + case RtcEvent::Type::ProbeClusterCreated: { + auto& rtc_event = static_cast<const RtcEventProbeClusterCreated&>(event); + return EncodeProbeClusterCreated(rtc_event); + } + + case RtcEvent::Type::ProbeResultFailure: { + auto& rtc_event = static_cast<const RtcEventProbeResultFailure&>(event); + return EncodeProbeResultFailure(rtc_event); + } + + case RtcEvent::Type::ProbeResultSuccess: { + auto& rtc_event = static_cast<const RtcEventProbeResultSuccess&>(event); + return EncodeProbeResultSuccess(rtc_event); + } + + case RtcEvent::Type::RemoteEstimateEvent: { + auto& rtc_event = static_cast<const RtcEventRemoteEstimate&>(event); + return EncodeRemoteEstimate(rtc_event); + } + + case RtcEvent::Type::RtcpPacketIncoming: { + auto& rtc_event = static_cast<const RtcEventRtcpPacketIncoming&>(event); + return EncodeRtcpPacketIncoming(rtc_event); + } + + case RtcEvent::Type::RtcpPacketOutgoing: { + auto& rtc_event = static_cast<const RtcEventRtcpPacketOutgoing&>(event); + return EncodeRtcpPacketOutgoing(rtc_event); + } + + case RtcEvent::Type::RtpPacketIncoming: { + auto& rtc_event = static_cast<const RtcEventRtpPacketIncoming&>(event); + return EncodeRtpPacketIncoming(rtc_event); + } + + case RtcEvent::Type::RtpPacketOutgoing: { + auto& rtc_event = static_cast<const RtcEventRtpPacketOutgoing&>(event); + return EncodeRtpPacketOutgoing(rtc_event); + } + + case RtcEvent::Type::VideoReceiveStreamConfig: { + auto& rtc_event = + static_cast<const RtcEventVideoReceiveStreamConfig&>(event); + return EncodeVideoReceiveStreamConfig(rtc_event); + } + + case RtcEvent::Type::VideoSendStreamConfig: { + auto& rtc_event = + static_cast<const RtcEventVideoSendStreamConfig&>(event); + return EncodeVideoSendStreamConfig(rtc_event); + } + case RtcEvent::Type::BeginV3Log: + case RtcEvent::Type::EndV3Log: + // These special events are written as part of starting + // and stopping the log, and only as part of version 3 of the format. + RTC_DCHECK_NOTREACHED(); + break; + case RtcEvent::Type::FakeEvent: + // Fake event used for unit test. + RTC_DCHECK_NOTREACHED(); + break; + case RtcEvent::Type::RouteChangeEvent: + case RtcEvent::Type::GenericPacketReceived: + case RtcEvent::Type::GenericPacketSent: + case RtcEvent::Type::GenericAckReceived: + case RtcEvent::Type::FrameDecoded: + case RtcEvent::Type::NetEqSetMinimumDelay: + // These are unsupported in the old format, but shouldn't crash. + return ""; + } + + int event_type = static_cast<int>(event.GetType()); + RTC_DCHECK_NOTREACHED() << "Unknown event type (" << event_type << ")"; + return ""; +} + +std::string RtcEventLogEncoderLegacy::EncodeAlrState( + const RtcEventAlrState& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::ALR_STATE_EVENT); + + auto* alr_state = rtclog_event.mutable_alr_state(); + alr_state->set_in_alr(event.in_alr()); + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeAudioNetworkAdaptation( + const RtcEventAudioNetworkAdaptation& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT); + + auto* audio_network_adaptation = + rtclog_event.mutable_audio_network_adaptation(); + if (event.config().bitrate_bps) + audio_network_adaptation->set_bitrate_bps(*event.config().bitrate_bps); + if (event.config().frame_length_ms) + audio_network_adaptation->set_frame_length_ms( + *event.config().frame_length_ms); + if (event.config().uplink_packet_loss_fraction) { + audio_network_adaptation->set_uplink_packet_loss_fraction( + *event.config().uplink_packet_loss_fraction); + } + if (event.config().enable_fec) + audio_network_adaptation->set_enable_fec(*event.config().enable_fec); + if (event.config().enable_dtx) + audio_network_adaptation->set_enable_dtx(*event.config().enable_dtx); + if (event.config().num_channels) + audio_network_adaptation->set_num_channels(*event.config().num_channels); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeAudioPlayout( + const RtcEventAudioPlayout& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::AUDIO_PLAYOUT_EVENT); + + auto* playout_event = rtclog_event.mutable_audio_playout_event(); + playout_event->set_local_ssrc(event.ssrc()); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeAudioReceiveStreamConfig( + const RtcEventAudioReceiveStreamConfig& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT); + + rtclog::AudioReceiveConfig* receiver_config = + rtclog_event.mutable_audio_receiver_config(); + receiver_config->set_remote_ssrc(event.config().remote_ssrc); + receiver_config->set_local_ssrc(event.config().local_ssrc); + + for (const auto& e : event.config().rtp_extensions) { + rtclog::RtpHeaderExtension* extension = + receiver_config->add_header_extensions(); + extension->set_name(e.uri); + extension->set_id(e.id); + } + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeAudioSendStreamConfig( + const RtcEventAudioSendStreamConfig& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::AUDIO_SENDER_CONFIG_EVENT); + + rtclog::AudioSendConfig* sender_config = + rtclog_event.mutable_audio_sender_config(); + + sender_config->set_ssrc(event.config().local_ssrc); + + for (const auto& e : event.config().rtp_extensions) { + rtclog::RtpHeaderExtension* extension = + sender_config->add_header_extensions(); + extension->set_name(e.uri); + extension->set_id(e.id); + } + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeBweUpdateDelayBased( + const RtcEventBweUpdateDelayBased& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::DELAY_BASED_BWE_UPDATE); + + auto* bwe_event = rtclog_event.mutable_delay_based_bwe_update(); + bwe_event->set_bitrate_bps(event.bitrate_bps()); + bwe_event->set_detector_state(ConvertDetectorState(event.detector_state())); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeBweUpdateLossBased( + const RtcEventBweUpdateLossBased& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::LOSS_BASED_BWE_UPDATE); + + auto* bwe_event = rtclog_event.mutable_loss_based_bwe_update(); + bwe_event->set_bitrate_bps(event.bitrate_bps()); + bwe_event->set_fraction_loss(event.fraction_loss()); + bwe_event->set_total_packets(event.total_packets()); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeIceCandidatePairConfig( + const RtcEventIceCandidatePairConfig& event) { + rtclog::Event encoded_rtc_event; + encoded_rtc_event.set_timestamp_us(event.timestamp_us()); + encoded_rtc_event.set_type(rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG); + + auto* encoded_ice_event = + encoded_rtc_event.mutable_ice_candidate_pair_config(); + encoded_ice_event->set_config_type( + ConvertIceCandidatePairConfigType(event.type())); + encoded_ice_event->set_candidate_pair_id(event.candidate_pair_id()); + const auto& desc = event.candidate_pair_desc(); + encoded_ice_event->set_local_candidate_type( + ConvertIceCandidateType(desc.local_candidate_type)); + encoded_ice_event->set_local_relay_protocol( + ConvertIceCandidatePairProtocol(desc.local_relay_protocol)); + encoded_ice_event->set_local_network_type( + ConvertIceCandidateNetworkType(desc.local_network_type)); + encoded_ice_event->set_local_address_family( + ConvertIceCandidatePairAddressFamily(desc.local_address_family)); + encoded_ice_event->set_remote_candidate_type( + ConvertIceCandidateType(desc.remote_candidate_type)); + encoded_ice_event->set_remote_address_family( + ConvertIceCandidatePairAddressFamily(desc.remote_address_family)); + encoded_ice_event->set_candidate_pair_protocol( + ConvertIceCandidatePairProtocol(desc.candidate_pair_protocol)); + return Serialize(&encoded_rtc_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeIceCandidatePairEvent( + const RtcEventIceCandidatePair& event) { + rtclog::Event encoded_rtc_event; + encoded_rtc_event.set_timestamp_us(event.timestamp_us()); + encoded_rtc_event.set_type(rtclog::Event::ICE_CANDIDATE_PAIR_EVENT); + + auto* encoded_ice_event = + encoded_rtc_event.mutable_ice_candidate_pair_event(); + encoded_ice_event->set_event_type( + ConvertIceCandidatePairEventType(event.type())); + encoded_ice_event->set_candidate_pair_id(event.candidate_pair_id()); + return Serialize(&encoded_rtc_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeProbeClusterCreated( + const RtcEventProbeClusterCreated& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT); + + auto* probe_cluster = rtclog_event.mutable_probe_cluster(); + probe_cluster->set_id(event.id()); + probe_cluster->set_bitrate_bps(event.bitrate_bps()); + probe_cluster->set_min_packets(event.min_probes()); + probe_cluster->set_min_bytes(event.min_bytes()); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeProbeResultFailure( + const RtcEventProbeResultFailure& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::BWE_PROBE_RESULT_EVENT); + + auto* probe_result = rtclog_event.mutable_probe_result(); + probe_result->set_id(event.id()); + probe_result->set_result(ConvertProbeResultType(event.failure_reason())); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeProbeResultSuccess( + const RtcEventProbeResultSuccess& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::BWE_PROBE_RESULT_EVENT); + + auto* probe_result = rtclog_event.mutable_probe_result(); + probe_result->set_id(event.id()); + probe_result->set_result(rtclog::BweProbeResult::SUCCESS); + probe_result->set_bitrate_bps(event.bitrate_bps()); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeRemoteEstimate( + const RtcEventRemoteEstimate& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::REMOTE_ESTIMATE); + + auto* remote_estimate = rtclog_event.mutable_remote_estimate(); + if (event.link_capacity_lower_.IsFinite()) + remote_estimate->set_link_capacity_lower_kbps( + event.link_capacity_lower_.kbps<uint32_t>()); + if (event.link_capacity_upper_.IsFinite()) + remote_estimate->set_link_capacity_upper_kbps( + event.link_capacity_upper_.kbps<uint32_t>()); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtcpPacketIncoming( + const RtcEventRtcpPacketIncoming& event) { + return EncodeRtcpPacket(event.timestamp_us(), event.packet(), true); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtcpPacketOutgoing( + const RtcEventRtcpPacketOutgoing& event) { + return EncodeRtcpPacket(event.timestamp_us(), event.packet(), false); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtpPacketIncoming( + const RtcEventRtpPacketIncoming& event) { + return EncodeRtpPacket(event.timestamp_us(), event.RawHeader(), + event.packet_length(), PacedPacketInfo::kNotAProbe, + true); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtpPacketOutgoing( + const RtcEventRtpPacketOutgoing& event) { + return EncodeRtpPacket(event.timestamp_us(), event.RawHeader(), + event.packet_length(), event.probe_cluster_id(), + false); +} + +std::string RtcEventLogEncoderLegacy::EncodeVideoReceiveStreamConfig( + const RtcEventVideoReceiveStreamConfig& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT); + + rtclog::VideoReceiveConfig* receiver_config = + rtclog_event.mutable_video_receiver_config(); + receiver_config->set_remote_ssrc(event.config().remote_ssrc); + receiver_config->set_local_ssrc(event.config().local_ssrc); + + // TODO(perkj): Add field for rsid. + receiver_config->set_rtcp_mode(ConvertRtcpMode(event.config().rtcp_mode)); + receiver_config->set_remb(event.config().remb); + + for (const auto& e : event.config().rtp_extensions) { + rtclog::RtpHeaderExtension* extension = + receiver_config->add_header_extensions(); + extension->set_name(e.uri); + extension->set_id(e.id); + } + + for (const auto& d : event.config().codecs) { + rtclog::DecoderConfig* decoder = receiver_config->add_decoders(); + decoder->set_name(d.payload_name); + decoder->set_payload_type(d.payload_type); + if (d.rtx_payload_type != 0) { + rtclog::RtxMap* rtx = receiver_config->add_rtx_map(); + rtx->set_payload_type(d.payload_type); + rtx->mutable_config()->set_rtx_ssrc(event.config().rtx_ssrc); + rtx->mutable_config()->set_rtx_payload_type(d.rtx_payload_type); + } + } + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeVideoSendStreamConfig( + const RtcEventVideoSendStreamConfig& event) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(event.timestamp_us()); + rtclog_event.set_type(rtclog::Event::VIDEO_SENDER_CONFIG_EVENT); + + rtclog::VideoSendConfig* sender_config = + rtclog_event.mutable_video_sender_config(); + + // TODO(perkj): rtclog::VideoSendConfig should only contain one SSRC. + sender_config->add_ssrcs(event.config().local_ssrc); + if (event.config().rtx_ssrc != 0) { + sender_config->add_rtx_ssrcs(event.config().rtx_ssrc); + } + + for (const auto& e : event.config().rtp_extensions) { + rtclog::RtpHeaderExtension* extension = + sender_config->add_header_extensions(); + extension->set_name(e.uri); + extension->set_id(e.id); + } + + // TODO(perkj): rtclog::VideoSendConfig should contain many possible codec + // configurations. + for (const auto& codec : event.config().codecs) { + sender_config->set_rtx_payload_type(codec.rtx_payload_type); + rtclog::EncoderConfig* encoder = sender_config->mutable_encoder(); + encoder->set_name(codec.payload_name); + encoder->set_payload_type(codec.payload_type); + + if (event.config().codecs.size() > 1) { + RTC_LOG(LS_WARNING) + << "LogVideoSendStreamConfig currently only supports one " + "codec. Logging codec :" + << codec.payload_name; + break; + } + } + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtcpPacket( + int64_t timestamp_us, + const rtc::Buffer& packet, + bool is_incoming) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(timestamp_us); + rtclog_event.set_type(rtclog::Event::RTCP_EVENT); + rtclog_event.mutable_rtcp_packet()->set_incoming(is_incoming); + + rtcp::CommonHeader header; + const uint8_t* block_begin = packet.data(); + const uint8_t* packet_end = packet.data() + packet.size(); + std::vector<uint8_t> buffer(packet.size()); + uint32_t buffer_length = 0; + while (block_begin < packet_end) { + if (!header.Parse(block_begin, packet_end - block_begin)) { + break; // Incorrect message header. + } + const uint8_t* next_block = header.NextPacket(); + uint32_t block_size = next_block - block_begin; + switch (header.type()) { + case rtcp::Bye::kPacketType: + case rtcp::ExtendedReports::kPacketType: + case rtcp::Psfb::kPacketType: + case rtcp::ReceiverReport::kPacketType: + case rtcp::Rtpfb::kPacketType: + case rtcp::SenderReport::kPacketType: + // We log sender reports, receiver reports, bye messages, third-party + // loss reports, payload-specific feedback and extended reports. + memcpy(buffer.data() + buffer_length, block_begin, block_size); + buffer_length += block_size; + break; + case rtcp::App::kPacketType: + case rtcp::Sdes::kPacketType: + default: + // We don't log sender descriptions, application defined messages + // or message blocks of unknown type. + break; + } + + block_begin += block_size; + } + rtclog_event.mutable_rtcp_packet()->set_packet_data(buffer.data(), + buffer_length); + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::EncodeRtpPacket( + int64_t timestamp_us, + rtc::ArrayView<const uint8_t> header, + size_t packet_length, + int probe_cluster_id, + bool is_incoming) { + rtclog::Event rtclog_event; + rtclog_event.set_timestamp_us(timestamp_us); + rtclog_event.set_type(rtclog::Event::RTP_EVENT); + + rtclog_event.mutable_rtp_packet()->set_incoming(is_incoming); + rtclog_event.mutable_rtp_packet()->set_packet_length(packet_length); + rtclog_event.mutable_rtp_packet()->set_header(header.data(), header.size()); + if (probe_cluster_id != PacedPacketInfo::kNotAProbe) { + RTC_DCHECK(!is_incoming); + rtclog_event.mutable_rtp_packet()->set_probe_cluster_id(probe_cluster_id); + } + + return Serialize(&rtclog_event); +} + +std::string RtcEventLogEncoderLegacy::Serialize(rtclog::Event* event) { + // Even though we're only serializing a single event during this call, what + // we intend to get is a list of events, with a tag and length preceding + // each actual event. To produce that, we serialize a list of a single event. + // If we later concatenate several results from this function, the result will + // be a proper concatenation of all those events. + + rtclog::EventStream event_stream; + event_stream.add_stream(); + + // As a tweak, we swap the new event into the event-stream, write that to + // file, then swap back. This saves on some copying, while making sure that + // the caller wouldn't be surprised by Serialize() modifying the object. + rtclog::Event* output_event = event_stream.mutable_stream(0); + output_event->Swap(event); + + std::string output_string = event_stream.SerializeAsString(); + RTC_DCHECK(!output_string.empty()); + + // When the function returns, the original Event will be unchanged. + output_event->Swap(event); + + return output_string; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h new file mode 100644 index 0000000000..33c530789b --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2017 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_LEGACY_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_LEGACY_H_ + +#include <deque> +#include <memory> +#include <string> + +#include "api/array_view.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder.h" +#include "rtc_base/buffer.h" + +namespace webrtc { + +namespace rtclog { +class Event; // Auto-generated from protobuf. +} // namespace rtclog + +class RtcEventAlrState; +class RtcEventAudioNetworkAdaptation; +class RtcEventAudioPlayout; +class RtcEventAudioReceiveStreamConfig; +class RtcEventAudioSendStreamConfig; +class RtcEventBweUpdateDelayBased; +class RtcEventBweUpdateLossBased; +class RtcEventIceCandidatePairConfig; +class RtcEventIceCandidatePair; +class RtcEventLoggingStarted; +class RtcEventLoggingStopped; +class RtcEventProbeClusterCreated; +class RtcEventProbeResultFailure; +class RtcEventProbeResultSuccess; +class RtcEventRemoteEstimate; +class RtcEventRtcpPacketIncoming; +class RtcEventRtcpPacketOutgoing; +class RtcEventRtpPacketIncoming; +class RtcEventRtpPacketOutgoing; +class RtcEventVideoReceiveStreamConfig; +class RtcEventVideoSendStreamConfig; +class RtpPacket; + +class RtcEventLogEncoderLegacy final : public RtcEventLogEncoder { + public: + ~RtcEventLogEncoderLegacy() override = default; + + std::string EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) override; + std::string EncodeLogEnd(int64_t timestamp_us) override; + + std::string EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) override; + + private: + std::string Encode(const RtcEvent& event); + // Encoding entry-point for the various RtcEvent subclasses. + std::string EncodeAlrState(const RtcEventAlrState& event); + std::string EncodeAudioNetworkAdaptation( + const RtcEventAudioNetworkAdaptation& event); + std::string EncodeAudioPlayout(const RtcEventAudioPlayout& event); + std::string EncodeAudioReceiveStreamConfig( + const RtcEventAudioReceiveStreamConfig& event); + std::string EncodeAudioSendStreamConfig( + const RtcEventAudioSendStreamConfig& event); + std::string EncodeBweUpdateDelayBased( + const RtcEventBweUpdateDelayBased& event); + std::string EncodeBweUpdateLossBased(const RtcEventBweUpdateLossBased& event); + std::string EncodeIceCandidatePairConfig( + const RtcEventIceCandidatePairConfig& event); + std::string EncodeIceCandidatePairEvent( + const RtcEventIceCandidatePair& event); + std::string EncodeProbeClusterCreated( + const RtcEventProbeClusterCreated& event); + std::string EncodeProbeResultFailure(const RtcEventProbeResultFailure& event); + std::string EncodeProbeResultSuccess(const RtcEventProbeResultSuccess&); + std::string EncodeRemoteEstimate(const RtcEventRemoteEstimate& event); + std::string EncodeRtcpPacketIncoming(const RtcEventRtcpPacketIncoming& event); + std::string EncodeRtcpPacketOutgoing(const RtcEventRtcpPacketOutgoing& event); + std::string EncodeRtpPacketIncoming(const RtcEventRtpPacketIncoming& event); + std::string EncodeRtpPacketOutgoing(const RtcEventRtpPacketOutgoing& event); + std::string EncodeVideoReceiveStreamConfig( + const RtcEventVideoReceiveStreamConfig& event); + std::string EncodeVideoSendStreamConfig( + const RtcEventVideoSendStreamConfig& event); + + // RTCP/RTP are handled similarly for incoming/outgoing. + std::string EncodeRtcpPacket(int64_t timestamp_us, + const rtc::Buffer& packet, + bool is_incoming); + std::string EncodeRtpPacket(int64_t timestamp_us, + rtc::ArrayView<const uint8_t> header, + size_t packet_length, + int probe_cluster_id, + bool is_incoming); + + std::string Serialize(rtclog::Event* event); +}; + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_LEGACY_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc new file mode 100644 index 0000000000..2e02f68213 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc @@ -0,0 +1,1941 @@ +/* + * Copyright (c) 2017 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/rtc_event_log_encoder_new_format.h" + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/network_state_predictor.h" +#include "logging/rtc_event_log/encoder/blob_encoding.h" +#include "logging/rtc_event_log/encoder/delta_encoding.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h" +#include "logging/rtc_event_log/events/rtc_event_alr_state.h" +#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h" +#include "logging/rtc_event_log/events/rtc_event_audio_playout.h" +#include "logging/rtc_event_log/events/rtc_event_audio_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h" +#include "logging/rtc_event_log/events/rtc_event_dtls_transport_state.h" +#include "logging/rtc_event_log/events/rtc_event_dtls_writable_state.h" +#include "logging/rtc_event_log/events/rtc_event_frame_decoded.h" +#include "logging/rtc_event_log/events/rtc_event_generic_ack_received.h" +#include "logging/rtc_event_log/events/rtc_event_generic_packet_received.h" +#include "logging/rtc_event_log/events/rtc_event_generic_packet_sent.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" +#include "logging/rtc_event_log/events/rtc_event_neteq_set_minimum_delay.h" +#include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" +#include "logging/rtc_event_log/events/rtc_event_remote_estimate.h" +#include "logging/rtc_event_log/events/rtc_event_route_change.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h" +#include "logging/rtc_event_log/rtc_stream_config.h" +#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h" +#include "modules/rtp_rtcp/include/rtp_cvo.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sdes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "rtc_base/checks.h" +#include "rtc_base/ignore_wundef.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/field_trial.h" + +// *.pb.h files are generated at build-time by the protobuf compiler. +RTC_PUSH_IGNORING_WUNDEF() +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/logging/rtc_event_log/rtc_event_log2.pb.h" +#else +#include "logging/rtc_event_log/rtc_event_log2.pb.h" +#endif +RTC_POP_IGNORING_WUNDEF() + +using webrtc_event_logging::ToUnsigned; + +namespace webrtc { + +namespace { +rtclog2::DelayBasedBweUpdates::DetectorState ConvertToProtoFormat( + BandwidthUsage state) { + switch (state) { + case BandwidthUsage::kBwNormal: + return rtclog2::DelayBasedBweUpdates::BWE_NORMAL; + case BandwidthUsage::kBwUnderusing: + return rtclog2::DelayBasedBweUpdates::BWE_UNDERUSING; + case BandwidthUsage::kBwOverusing: + return rtclog2::DelayBasedBweUpdates::BWE_OVERUSING; + case BandwidthUsage::kLast: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::DelayBasedBweUpdates::BWE_UNKNOWN_STATE; +} + +rtclog2::FrameDecodedEvents::Codec ConvertToProtoFormat(VideoCodecType codec) { + switch (codec) { + case VideoCodecType::kVideoCodecGeneric: + return rtclog2::FrameDecodedEvents::CODEC_GENERIC; + case VideoCodecType::kVideoCodecVP8: + return rtclog2::FrameDecodedEvents::CODEC_VP8; + case VideoCodecType::kVideoCodecVP9: + return rtclog2::FrameDecodedEvents::CODEC_VP9; + case VideoCodecType::kVideoCodecAV1: + return rtclog2::FrameDecodedEvents::CODEC_AV1; + case VideoCodecType::kVideoCodecH264: + return rtclog2::FrameDecodedEvents::CODEC_H264; + case VideoCodecType::kVideoCodecMultiplex: + // This codec type is afaik not used. + return rtclog2::FrameDecodedEvents::CODEC_UNKNOWN; + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::FrameDecodedEvents::CODEC_UNKNOWN; +} + +rtclog2::BweProbeResultFailure::FailureReason ConvertToProtoFormat( + ProbeFailureReason failure_reason) { + switch (failure_reason) { + case ProbeFailureReason::kInvalidSendReceiveInterval: + return rtclog2::BweProbeResultFailure::INVALID_SEND_RECEIVE_INTERVAL; + case ProbeFailureReason::kInvalidSendReceiveRatio: + return rtclog2::BweProbeResultFailure::INVALID_SEND_RECEIVE_RATIO; + case ProbeFailureReason::kTimeout: + return rtclog2::BweProbeResultFailure::TIMEOUT; + case ProbeFailureReason::kLast: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::BweProbeResultFailure::UNKNOWN; +} + +// Returns true if there are recognized extensions that we should log +// and false if there are no extensions or all extensions are types we don't +// log. The protobuf representation of the header configs is written to +// `proto_config`. +bool ConvertToProtoFormat(const std::vector<RtpExtension>& extensions, + rtclog2::RtpHeaderExtensionConfig* proto_config) { + size_t unknown_extensions = 0; + for (auto& extension : extensions) { + if (extension.uri == RtpExtension::kAudioLevelUri) { + proto_config->set_audio_level_id(extension.id); + } else if (extension.uri == RtpExtension::kTimestampOffsetUri) { + proto_config->set_transmission_time_offset_id(extension.id); + } else if (extension.uri == RtpExtension::kAbsSendTimeUri) { + proto_config->set_absolute_send_time_id(extension.id); + } else if (extension.uri == RtpExtension::kTransportSequenceNumberUri) { + proto_config->set_transport_sequence_number_id(extension.id); + } else if (extension.uri == RtpExtension::kVideoRotationUri) { + proto_config->set_video_rotation_id(extension.id); + } else { + ++unknown_extensions; + } + } + return unknown_extensions < extensions.size(); +} + +rtclog2::DtlsTransportStateEvent::DtlsTransportState ConvertToProtoFormat( + webrtc::DtlsTransportState state) { + switch (state) { + case webrtc::DtlsTransportState::kNew: + return rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_NEW; + case webrtc::DtlsTransportState::kConnecting: + return rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CONNECTING; + case webrtc::DtlsTransportState::kConnected: + return rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CONNECTED; + case webrtc::DtlsTransportState::kClosed: + return rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_CLOSED; + case webrtc::DtlsTransportState::kFailed: + return rtclog2::DtlsTransportStateEvent::DTLS_TRANSPORT_FAILED; + case webrtc::DtlsTransportState::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::DtlsTransportStateEvent::UNKNOWN_DTLS_TRANSPORT_STATE; +} + +rtclog2::IceCandidatePairConfig::IceCandidatePairConfigType +ConvertToProtoFormat(IceCandidatePairConfigType type) { + switch (type) { + case IceCandidatePairConfigType::kAdded: + return rtclog2::IceCandidatePairConfig::ADDED; + case IceCandidatePairConfigType::kUpdated: + return rtclog2::IceCandidatePairConfig::UPDATED; + case IceCandidatePairConfigType::kDestroyed: + return rtclog2::IceCandidatePairConfig::DESTROYED; + case IceCandidatePairConfigType::kSelected: + return rtclog2::IceCandidatePairConfig::SELECTED; + case IceCandidatePairConfigType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairConfig::UNKNOWN_CONFIG_TYPE; +} + +rtclog2::IceCandidatePairConfig::IceCandidateType ConvertToProtoFormat( + IceCandidateType type) { + switch (type) { + case IceCandidateType::kUnknown: + return rtclog2::IceCandidatePairConfig::UNKNOWN_CANDIDATE_TYPE; + case IceCandidateType::kLocal: + return rtclog2::IceCandidatePairConfig::LOCAL; + case IceCandidateType::kStun: + return rtclog2::IceCandidatePairConfig::STUN; + case IceCandidateType::kPrflx: + return rtclog2::IceCandidatePairConfig::PRFLX; + case IceCandidateType::kRelay: + return rtclog2::IceCandidatePairConfig::RELAY; + case IceCandidateType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairConfig::UNKNOWN_CANDIDATE_TYPE; +} + +rtclog2::IceCandidatePairConfig::Protocol ConvertToProtoFormat( + IceCandidatePairProtocol protocol) { + switch (protocol) { + case IceCandidatePairProtocol::kUnknown: + return rtclog2::IceCandidatePairConfig::UNKNOWN_PROTOCOL; + case IceCandidatePairProtocol::kUdp: + return rtclog2::IceCandidatePairConfig::UDP; + case IceCandidatePairProtocol::kTcp: + return rtclog2::IceCandidatePairConfig::TCP; + case IceCandidatePairProtocol::kSsltcp: + return rtclog2::IceCandidatePairConfig::SSLTCP; + case IceCandidatePairProtocol::kTls: + return rtclog2::IceCandidatePairConfig::TLS; + case IceCandidatePairProtocol::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairConfig::UNKNOWN_PROTOCOL; +} + +rtclog2::IceCandidatePairConfig::AddressFamily ConvertToProtoFormat( + IceCandidatePairAddressFamily address_family) { + switch (address_family) { + case IceCandidatePairAddressFamily::kUnknown: + return rtclog2::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY; + case IceCandidatePairAddressFamily::kIpv4: + return rtclog2::IceCandidatePairConfig::IPV4; + case IceCandidatePairAddressFamily::kIpv6: + return rtclog2::IceCandidatePairConfig::IPV6; + case IceCandidatePairAddressFamily::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY; +} + +rtclog2::IceCandidatePairConfig::NetworkType ConvertToProtoFormat( + IceCandidateNetworkType network_type) { + switch (network_type) { + case IceCandidateNetworkType::kUnknown: + return rtclog2::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE; + case IceCandidateNetworkType::kEthernet: + return rtclog2::IceCandidatePairConfig::ETHERNET; + case IceCandidateNetworkType::kLoopback: + return rtclog2::IceCandidatePairConfig::LOOPBACK; + case IceCandidateNetworkType::kWifi: + return rtclog2::IceCandidatePairConfig::WIFI; + case IceCandidateNetworkType::kVpn: + return rtclog2::IceCandidatePairConfig::VPN; + case IceCandidateNetworkType::kCellular: + return rtclog2::IceCandidatePairConfig::CELLULAR; + case IceCandidateNetworkType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE; +} + +rtclog2::IceCandidatePairEvent::IceCandidatePairEventType ConvertToProtoFormat( + IceCandidatePairEventType type) { + switch (type) { + case IceCandidatePairEventType::kCheckSent: + return rtclog2::IceCandidatePairEvent::CHECK_SENT; + case IceCandidatePairEventType::kCheckReceived: + return rtclog2::IceCandidatePairEvent::CHECK_RECEIVED; + case IceCandidatePairEventType::kCheckResponseSent: + return rtclog2::IceCandidatePairEvent::CHECK_RESPONSE_SENT; + case IceCandidatePairEventType::kCheckResponseReceived: + return rtclog2::IceCandidatePairEvent::CHECK_RESPONSE_RECEIVED; + case IceCandidatePairEventType::kNumValues: + RTC_DCHECK_NOTREACHED(); + } + RTC_DCHECK_NOTREACHED(); + return rtclog2::IceCandidatePairEvent::UNKNOWN_CHECK_TYPE; +} + +// Copies all RTCP blocks except APP, SDES and unknown from `packet` to +// `buffer`. `buffer` must have space for at least `packet.size()` bytes. +size_t RemoveNonAllowlistedRtcpBlocks(const rtc::Buffer& packet, + uint8_t* buffer) { + RTC_DCHECK(buffer != nullptr); + rtcp::CommonHeader header; + const uint8_t* block_begin = packet.data(); + const uint8_t* packet_end = packet.data() + packet.size(); + size_t buffer_length = 0; + while (block_begin < packet_end) { + if (!header.Parse(block_begin, packet_end - block_begin)) { + break; // Incorrect message header. + } + const uint8_t* next_block = header.NextPacket(); + RTC_DCHECK_GT(next_block, block_begin); + RTC_DCHECK_LE(next_block, packet_end); + size_t block_size = next_block - block_begin; + switch (header.type()) { + case rtcp::Bye::kPacketType: + case rtcp::ExtendedReports::kPacketType: + case rtcp::Psfb::kPacketType: + case rtcp::ReceiverReport::kPacketType: + case rtcp::Rtpfb::kPacketType: + case rtcp::SenderReport::kPacketType: + // We log sender reports, receiver reports, bye messages, third-party + // loss reports, payload-specific feedback and extended reports. + // TODO(terelius): As an optimization, don't copy anything if all blocks + // in the packet are allowlisted types. + memcpy(buffer + buffer_length, block_begin, block_size); + buffer_length += block_size; + break; + case rtcp::App::kPacketType: + case rtcp::Sdes::kPacketType: + default: + // We don't log sender descriptions, application defined messages + // or message blocks of unknown type. + break; + } + + block_begin += block_size; + } + return buffer_length; +} + +template <typename EventType, typename ProtoType> +void EncodeRtcpPacket(rtc::ArrayView<const EventType*> batch, + ProtoType* proto_batch) { + if (batch.empty()) { + return; + } + + // Base event + const EventType* const base_event = batch[0]; + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + { + std::vector<uint8_t> buffer(base_event->packet().size()); + size_t buffer_length = + RemoveNonAllowlistedRtcpBlocks(base_event->packet(), buffer.data()); + proto_batch->set_raw_packet(buffer.data(), buffer_length); + } + + if (batch.size() == 1) { + return; + } + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // raw_packet + std::vector<std::string> scrubed_packets(batch.size() - 1); + for (size_t i = 0; i < scrubed_packets.size(); ++i) { + const EventType* event = batch[i + 1]; + scrubed_packets[i].resize(event->packet().size()); + static_assert(sizeof(std::string::value_type) == sizeof(uint8_t), ""); + const size_t buffer_length = RemoveNonAllowlistedRtcpBlocks( + event->packet(), reinterpret_cast<uint8_t*>(&scrubed_packets[i][0])); + if (buffer_length < event->packet().size()) { + scrubed_packets[i].resize(buffer_length); + } + } + proto_batch->set_raw_packet_blobs(EncodeBlobs(scrubed_packets)); +} + +template <typename EventType, typename ProtoType> +void EncodeRtpPacket(const std::vector<const EventType*>& batch, + ProtoType* proto_batch) { + if (batch.empty()) { + return; + } + + // Base event + const EventType* const base_event = batch[0]; + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_marker(base_event->Marker()); + // TODO(terelius): Is payload type needed? + proto_batch->set_payload_type(base_event->PayloadType()); + proto_batch->set_sequence_number(base_event->SequenceNumber()); + proto_batch->set_rtp_timestamp(base_event->Timestamp()); + proto_batch->set_ssrc(base_event->Ssrc()); + proto_batch->set_payload_size(base_event->payload_length()); + proto_batch->set_header_size(base_event->header_length()); + proto_batch->set_padding_size(base_event->padding_length()); + + // Add header extensions (base event). + absl::optional<uint64_t> base_transport_sequence_number; + { + uint16_t seqnum; + if (base_event->template GetExtension<TransportSequenceNumber>(&seqnum)) { + proto_batch->set_transport_sequence_number(seqnum); + base_transport_sequence_number = seqnum; + } + } + + absl::optional<uint64_t> unsigned_base_transmission_time_offset; + { + int32_t offset; + if (base_event->template GetExtension<TransmissionOffset>(&offset)) { + proto_batch->set_transmission_time_offset(offset); + unsigned_base_transmission_time_offset = ToUnsigned(offset); + } + } + + absl::optional<uint64_t> base_absolute_send_time; + { + uint32_t sendtime; + if (base_event->template GetExtension<AbsoluteSendTime>(&sendtime)) { + proto_batch->set_absolute_send_time(sendtime); + base_absolute_send_time = sendtime; + } + } + + absl::optional<uint64_t> base_video_rotation; + { + VideoRotation video_rotation; + if (base_event->template GetExtension<VideoOrientation>(&video_rotation)) { + proto_batch->set_video_rotation( + ConvertVideoRotationToCVOByte(video_rotation)); + base_video_rotation = ConvertVideoRotationToCVOByte(video_rotation); + } + } + + absl::optional<uint64_t> base_audio_level; + absl::optional<uint64_t> base_voice_activity; + { + bool voice_activity; + uint8_t audio_level; + if (base_event->template GetExtension<AudioLevel>(&voice_activity, + &audio_level)) { + RTC_DCHECK_LE(audio_level, 0x7Fu); + base_audio_level = audio_level; + proto_batch->set_audio_level(audio_level); + + base_voice_activity = voice_activity; + proto_batch->set_voice_activity(voice_activity); + } + } + + if (batch.size() == 1) { + return; + } + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms (event) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // marker (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->Marker(); + } + encoded_deltas = EncodeDeltas(base_event->Marker(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_marker_deltas(encoded_deltas); + } + + // payload_type (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->PayloadType(); + } + encoded_deltas = EncodeDeltas(base_event->PayloadType(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_payload_type_deltas(encoded_deltas); + } + + // sequence_number (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->SequenceNumber(); + } + encoded_deltas = EncodeDeltas(base_event->SequenceNumber(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_sequence_number_deltas(encoded_deltas); + } + + // rtp_timestamp (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->Timestamp(); + } + encoded_deltas = EncodeDeltas(base_event->Timestamp(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_rtp_timestamp_deltas(encoded_deltas); + } + + // ssrc (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->Ssrc(); + } + encoded_deltas = EncodeDeltas(base_event->Ssrc(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_ssrc_deltas(encoded_deltas); + } + + // payload_size (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->payload_length(); + } + encoded_deltas = EncodeDeltas(base_event->payload_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_payload_size_deltas(encoded_deltas); + } + + // header_size (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->header_length(); + } + encoded_deltas = EncodeDeltas(base_event->header_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_header_size_deltas(encoded_deltas); + } + + // padding_size (RTP base) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + values[i] = event->padding_length(); + } + encoded_deltas = EncodeDeltas(base_event->padding_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_padding_size_deltas(encoded_deltas); + } + + // transport_sequence_number (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + uint16_t seqnum; + if (event->template GetExtension<TransportSequenceNumber>(&seqnum)) { + values[i] = seqnum; + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_transport_sequence_number, values); + if (!encoded_deltas.empty()) { + proto_batch->set_transport_sequence_number_deltas(encoded_deltas); + } + + // transmission_time_offset (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + int32_t offset; + if (event->template GetExtension<TransmissionOffset>(&offset)) { + values[i] = ToUnsigned(offset); + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(unsigned_base_transmission_time_offset, values); + if (!encoded_deltas.empty()) { + proto_batch->set_transmission_time_offset_deltas(encoded_deltas); + } + + // absolute_send_time (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + uint32_t sendtime; + if (event->template GetExtension<AbsoluteSendTime>(&sendtime)) { + values[i] = sendtime; + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_absolute_send_time, values); + if (!encoded_deltas.empty()) { + proto_batch->set_absolute_send_time_deltas(encoded_deltas); + } + + // video_rotation (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + VideoRotation video_rotation; + if (event->template GetExtension<VideoOrientation>(&video_rotation)) { + values[i] = ConvertVideoRotationToCVOByte(video_rotation); + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_video_rotation, values); + if (!encoded_deltas.empty()) { + proto_batch->set_video_rotation_deltas(encoded_deltas); + } + + // audio_level (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + bool voice_activity; + uint8_t audio_level; + if (event->template GetExtension<AudioLevel>(&voice_activity, + &audio_level)) { + RTC_DCHECK_LE(audio_level, 0x7Fu); + values[i] = audio_level; + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_audio_level, values); + if (!encoded_deltas.empty()) { + proto_batch->set_audio_level_deltas(encoded_deltas); + } + + // voice_activity (RTP extension) + for (size_t i = 0; i < values.size(); ++i) { + const EventType* event = batch[i + 1]; + bool voice_activity; + uint8_t audio_level; + if (event->template GetExtension<AudioLevel>(&voice_activity, + &audio_level)) { + RTC_DCHECK_LE(audio_level, 0x7Fu); + values[i] = voice_activity; + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_voice_activity, values); + if (!encoded_deltas.empty()) { + proto_batch->set_voice_activity_deltas(encoded_deltas); + } +} +} // namespace + +RtcEventLogEncoderNewFormat::RtcEventLogEncoderNewFormat() { + encode_neteq_set_minimum_delay_kill_switch_ = false; + if (webrtc::field_trial::IsEnabled( + "WebRTC-RtcEventLogEncodeNetEqSetMinimumDelayKillSwitch")) { + encode_neteq_set_minimum_delay_kill_switch_ = true; + } +} + +std::string RtcEventLogEncoderNewFormat::EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) { + rtclog2::EventStream event_stream; + rtclog2::BeginLogEvent* proto_batch = event_stream.add_begin_log_events(); + proto_batch->set_timestamp_ms(timestamp_us / 1000); + proto_batch->set_version(2); + proto_batch->set_utc_time_ms(utc_time_us / 1000); + return event_stream.SerializeAsString(); +} + +std::string RtcEventLogEncoderNewFormat::EncodeLogEnd(int64_t timestamp_us) { + rtclog2::EventStream event_stream; + rtclog2::EndLogEvent* proto_batch = event_stream.add_end_log_events(); + proto_batch->set_timestamp_ms(timestamp_us / 1000); + return event_stream.SerializeAsString(); +} + +std::string RtcEventLogEncoderNewFormat::EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) { + rtclog2::EventStream event_stream; + std::string encoded_output; + + { + std::vector<const RtcEventAlrState*> alr_state_events; + std::vector<const RtcEventAudioNetworkAdaptation*> + audio_network_adaptation_events; + std::vector<const RtcEventAudioPlayout*> audio_playout_events; + std::vector<const RtcEventNetEqSetMinimumDelay*> + neteq_set_minimum_delay_events; + std::vector<const RtcEventAudioReceiveStreamConfig*> + audio_recv_stream_configs; + std::vector<const RtcEventAudioSendStreamConfig*> audio_send_stream_configs; + std::vector<const RtcEventBweUpdateDelayBased*> bwe_delay_based_updates; + std::vector<const RtcEventBweUpdateLossBased*> bwe_loss_based_updates; + std::vector<const RtcEventDtlsTransportState*> dtls_transport_states; + std::vector<const RtcEventDtlsWritableState*> dtls_writable_states; + std::map<uint32_t /* SSRC */, std::vector<const RtcEventFrameDecoded*>> + frames_decoded; + std::vector<const RtcEventGenericAckReceived*> generic_acks_received; + std::vector<const RtcEventGenericPacketReceived*> generic_packets_received; + std::vector<const RtcEventGenericPacketSent*> generic_packets_sent; + std::vector<const RtcEventIceCandidatePair*> ice_candidate_events; + std::vector<const RtcEventIceCandidatePairConfig*> ice_candidate_configs; + std::vector<const RtcEventProbeClusterCreated*> + probe_cluster_created_events; + std::vector<const RtcEventProbeResultFailure*> probe_result_failure_events; + std::vector<const RtcEventProbeResultSuccess*> probe_result_success_events; + std::vector<const RtcEventRouteChange*> route_change_events; + std::vector<const RtcEventRemoteEstimate*> remote_estimate_events; + std::vector<const RtcEventRtcpPacketIncoming*> incoming_rtcp_packets; + std::vector<const RtcEventRtcpPacketOutgoing*> outgoing_rtcp_packets; + std::map<uint32_t /* SSRC */, std::vector<const RtcEventRtpPacketIncoming*>> + incoming_rtp_packets; + std::map<uint32_t /* SSRC */, std::vector<const RtcEventRtpPacketOutgoing*>> + outgoing_rtp_packets; + std::vector<const RtcEventVideoReceiveStreamConfig*> + video_recv_stream_configs; + std::vector<const RtcEventVideoSendStreamConfig*> video_send_stream_configs; + + for (auto it = begin; it != end; ++it) { + switch ((*it)->GetType()) { + case RtcEvent::Type::AlrStateEvent: { + auto* rtc_event = + static_cast<const RtcEventAlrState* const>(it->get()); + alr_state_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::AudioNetworkAdaptation: { + auto* rtc_event = + static_cast<const RtcEventAudioNetworkAdaptation* const>( + it->get()); + audio_network_adaptation_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::AudioPlayout: { + auto* rtc_event = + static_cast<const RtcEventAudioPlayout* const>(it->get()); + audio_playout_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::AudioReceiveStreamConfig: { + auto* rtc_event = + static_cast<const RtcEventAudioReceiveStreamConfig* const>( + it->get()); + audio_recv_stream_configs.push_back(rtc_event); + break; + } + case RtcEvent::Type::AudioSendStreamConfig: { + auto* rtc_event = + static_cast<const RtcEventAudioSendStreamConfig* const>( + it->get()); + audio_send_stream_configs.push_back(rtc_event); + break; + } + case RtcEvent::Type::BweUpdateDelayBased: { + auto* rtc_event = + static_cast<const RtcEventBweUpdateDelayBased* const>(it->get()); + bwe_delay_based_updates.push_back(rtc_event); + break; + } + case RtcEvent::Type::BweUpdateLossBased: { + auto* rtc_event = + static_cast<const RtcEventBweUpdateLossBased* const>(it->get()); + bwe_loss_based_updates.push_back(rtc_event); + break; + } + case RtcEvent::Type::DtlsTransportState: { + auto* rtc_event = + static_cast<const RtcEventDtlsTransportState* const>(it->get()); + dtls_transport_states.push_back(rtc_event); + break; + } + case RtcEvent::Type::DtlsWritableState: { + auto* rtc_event = + static_cast<const RtcEventDtlsWritableState* const>(it->get()); + dtls_writable_states.push_back(rtc_event); + break; + } + case RtcEvent::Type::ProbeClusterCreated: { + auto* rtc_event = + static_cast<const RtcEventProbeClusterCreated* const>(it->get()); + probe_cluster_created_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::ProbeResultFailure: { + auto* rtc_event = + static_cast<const RtcEventProbeResultFailure* const>(it->get()); + probe_result_failure_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::ProbeResultSuccess: { + auto* rtc_event = + static_cast<const RtcEventProbeResultSuccess* const>(it->get()); + probe_result_success_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::RouteChangeEvent: { + auto* rtc_event = + static_cast<const RtcEventRouteChange* const>(it->get()); + route_change_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::RemoteEstimateEvent: { + auto* rtc_event = + static_cast<const RtcEventRemoteEstimate* const>(it->get()); + remote_estimate_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::RtcpPacketIncoming: { + auto* rtc_event = + static_cast<const RtcEventRtcpPacketIncoming* const>(it->get()); + incoming_rtcp_packets.push_back(rtc_event); + break; + } + case RtcEvent::Type::RtcpPacketOutgoing: { + auto* rtc_event = + static_cast<const RtcEventRtcpPacketOutgoing* const>(it->get()); + outgoing_rtcp_packets.push_back(rtc_event); + break; + } + case RtcEvent::Type::RtpPacketIncoming: { + auto* rtc_event = + static_cast<const RtcEventRtpPacketIncoming* const>(it->get()); + auto& v = incoming_rtp_packets[rtc_event->Ssrc()]; + v.emplace_back(rtc_event); + break; + } + case RtcEvent::Type::RtpPacketOutgoing: { + auto* rtc_event = + static_cast<const RtcEventRtpPacketOutgoing* const>(it->get()); + auto& v = outgoing_rtp_packets[rtc_event->Ssrc()]; + v.emplace_back(rtc_event); + break; + } + case RtcEvent::Type::VideoReceiveStreamConfig: { + auto* rtc_event = + static_cast<const RtcEventVideoReceiveStreamConfig* const>( + it->get()); + video_recv_stream_configs.push_back(rtc_event); + break; + } + case RtcEvent::Type::VideoSendStreamConfig: { + auto* rtc_event = + static_cast<const RtcEventVideoSendStreamConfig* const>( + it->get()); + video_send_stream_configs.push_back(rtc_event); + break; + } + case RtcEvent::Type::IceCandidatePairConfig: { + auto* rtc_event = + static_cast<const RtcEventIceCandidatePairConfig* const>( + it->get()); + ice_candidate_configs.push_back(rtc_event); + break; + } + case RtcEvent::Type::IceCandidatePairEvent: { + auto* rtc_event = + static_cast<const RtcEventIceCandidatePair* const>(it->get()); + ice_candidate_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::GenericPacketReceived: { + auto* rtc_event = + static_cast<const RtcEventGenericPacketReceived* const>( + it->get()); + generic_packets_received.push_back(rtc_event); + break; + } + case RtcEvent::Type::GenericPacketSent: { + auto* rtc_event = + static_cast<const RtcEventGenericPacketSent* const>(it->get()); + generic_packets_sent.push_back(rtc_event); + break; + } + case RtcEvent::Type::GenericAckReceived: { + auto* rtc_event = + static_cast<const RtcEventGenericAckReceived* const>(it->get()); + generic_acks_received.push_back(rtc_event); + break; + } + case RtcEvent::Type::FrameDecoded: { + auto* rtc_event = + static_cast<const RtcEventFrameDecoded* const>(it->get()); + frames_decoded[rtc_event->ssrc()].emplace_back(rtc_event); + break; + } + case RtcEvent::Type::NetEqSetMinimumDelay: { + auto* rtc_event = + static_cast<const RtcEventNetEqSetMinimumDelay* const>(it->get()); + neteq_set_minimum_delay_events.push_back(rtc_event); + break; + } + case RtcEvent::Type::BeginV3Log: + case RtcEvent::Type::EndV3Log: + // These special events are written as part of starting + // and stopping the log, and only as part of version 3 of the format. + RTC_DCHECK_NOTREACHED(); + break; + case RtcEvent::Type::FakeEvent: + // Fake event used for unit test. + RTC_DCHECK_NOTREACHED(); + break; + } + } + + EncodeAlrState(alr_state_events, &event_stream); + EncodeAudioNetworkAdaptation(audio_network_adaptation_events, + &event_stream); + EncodeAudioPlayout(audio_playout_events, &event_stream); + EncodeAudioRecvStreamConfig(audio_recv_stream_configs, &event_stream); + EncodeAudioSendStreamConfig(audio_send_stream_configs, &event_stream); + EncodeNetEqSetMinimumDelay(neteq_set_minimum_delay_events, &event_stream); + EncodeBweUpdateDelayBased(bwe_delay_based_updates, &event_stream); + EncodeBweUpdateLossBased(bwe_loss_based_updates, &event_stream); + EncodeDtlsTransportState(dtls_transport_states, &event_stream); + EncodeDtlsWritableState(dtls_writable_states, &event_stream); + for (const auto& kv : frames_decoded) { + EncodeFramesDecoded(kv.second, &event_stream); + } + EncodeGenericAcksReceived(generic_acks_received, &event_stream); + EncodeGenericPacketsReceived(generic_packets_received, &event_stream); + EncodeGenericPacketsSent(generic_packets_sent, &event_stream); + EncodeIceCandidatePairConfig(ice_candidate_configs, &event_stream); + EncodeIceCandidatePairEvent(ice_candidate_events, &event_stream); + EncodeProbeClusterCreated(probe_cluster_created_events, &event_stream); + EncodeProbeResultFailure(probe_result_failure_events, &event_stream); + EncodeProbeResultSuccess(probe_result_success_events, &event_stream); + EncodeRouteChange(route_change_events, &event_stream); + EncodeRemoteEstimate(remote_estimate_events, &event_stream); + EncodeRtcpPacketIncoming(incoming_rtcp_packets, &event_stream); + EncodeRtcpPacketOutgoing(outgoing_rtcp_packets, &event_stream); + EncodeRtpPacketIncoming(incoming_rtp_packets, &event_stream); + EncodeRtpPacketOutgoing(outgoing_rtp_packets, &event_stream); + EncodeVideoRecvStreamConfig(video_recv_stream_configs, &event_stream); + EncodeVideoSendStreamConfig(video_send_stream_configs, &event_stream); + } // Deallocate the temporary vectors. + + return event_stream.SerializeAsString(); +} + +void RtcEventLogEncoderNewFormat::EncodeAlrState( + rtc::ArrayView<const RtcEventAlrState*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventAlrState* base_event : batch) { + rtclog2::AlrState* proto_batch = event_stream->add_alr_states(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_in_alr(base_event->in_alr()); + } + // TODO(terelius): Should we delta-compress this event type? +} + +void RtcEventLogEncoderNewFormat::EncodeAudioNetworkAdaptation( + rtc::ArrayView<const RtcEventAudioNetworkAdaptation*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) + return; + + // Base event + const RtcEventAudioNetworkAdaptation* const base_event = batch[0]; + rtclog2::AudioNetworkAdaptations* proto_batch = + event_stream->add_audio_network_adaptations(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + if (base_event->config().bitrate_bps.has_value()) + proto_batch->set_bitrate_bps(base_event->config().bitrate_bps.value()); + if (base_event->config().frame_length_ms.has_value()) { + proto_batch->set_frame_length_ms( + base_event->config().frame_length_ms.value()); + } + absl::optional<uint64_t> base_uplink_packet_loss_fraction; + if (base_event->config().uplink_packet_loss_fraction.has_value()) { + base_uplink_packet_loss_fraction = ConvertPacketLossFractionToProtoFormat( + base_event->config().uplink_packet_loss_fraction.value()); + proto_batch->set_uplink_packet_loss_fraction( + base_uplink_packet_loss_fraction.value()); + } + if (base_event->config().enable_fec.has_value()) + proto_batch->set_enable_fec(base_event->config().enable_fec.value()); + if (base_event->config().enable_dtx.has_value()) + proto_batch->set_enable_dtx(base_event->config().enable_dtx.value()); + // Note that `num_channels_deltas` encodes N as N-1, to keep deltas smaller, + // but there's no reason to do the same for the base event's value, since + // no bits will be spared. + if (base_event->config().num_channels.has_value()) + proto_batch->set_num_channels(base_event->config().num_channels.value()); + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // bitrate_bps + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + if (event->config().bitrate_bps.has_value()) { + values[i] = ToUnsigned(event->config().bitrate_bps.value()); + } else { + values[i].reset(); + } + } + const absl::optional<uint64_t> unsigned_base_bitrate_bps = + base_event->config().bitrate_bps.has_value() + ? ToUnsigned(base_event->config().bitrate_bps.value()) + : absl::optional<uint64_t>(); + encoded_deltas = EncodeDeltas(unsigned_base_bitrate_bps, values); + if (!encoded_deltas.empty()) { + proto_batch->set_bitrate_bps_deltas(encoded_deltas); + } + + // frame_length_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + if (event->config().frame_length_ms.has_value()) { + values[i] = ToUnsigned(event->config().frame_length_ms.value()); + } else { + values[i].reset(); + } + } + const absl::optional<uint64_t> unsigned_base_frame_length_ms = + base_event->config().frame_length_ms.has_value() + ? ToUnsigned(base_event->config().frame_length_ms.value()) + : absl::optional<uint64_t>(); + encoded_deltas = EncodeDeltas(unsigned_base_frame_length_ms, values); + if (!encoded_deltas.empty()) { + proto_batch->set_frame_length_ms_deltas(encoded_deltas); + } + + // uplink_packet_loss_fraction + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + if (event->config().uplink_packet_loss_fraction.has_value()) { + values[i] = ConvertPacketLossFractionToProtoFormat( + event->config().uplink_packet_loss_fraction.value()); + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_uplink_packet_loss_fraction, values); + if (!encoded_deltas.empty()) { + proto_batch->set_uplink_packet_loss_fraction_deltas(encoded_deltas); + } + + // enable_fec + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + values[i] = event->config().enable_fec; + } + encoded_deltas = EncodeDeltas(base_event->config().enable_fec, values); + if (!encoded_deltas.empty()) { + proto_batch->set_enable_fec_deltas(encoded_deltas); + } + + // enable_dtx + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + values[i] = event->config().enable_dtx; + } + encoded_deltas = EncodeDeltas(base_event->config().enable_dtx, values); + if (!encoded_deltas.empty()) { + proto_batch->set_enable_dtx_deltas(encoded_deltas); + } + + // num_channels + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioNetworkAdaptation* event = batch[i + 1]; + const absl::optional<size_t> num_channels = event->config().num_channels; + if (num_channels.has_value()) { + // Since the number of channels is always greater than 0, we can encode + // N channels as N-1, thereby making sure that we get smaller deltas. + // That is, a toggle of 1->2->1 can be encoded as deltas vector (1, 1), + // rather than as (1, 3) or (1, -1), either of which would require two + // bits per delta. + RTC_DCHECK_GT(num_channels.value(), 0u); + values[i] = num_channels.value() - 1; + } else { + values[i].reset(); + } + } + // In the base event, N channels encoded as N channels, but for delta + // compression purposes, also shifted down by 1. + absl::optional<size_t> shifted_base_num_channels; + if (base_event->config().num_channels.has_value()) { + RTC_DCHECK_GT(base_event->config().num_channels.value(), 0u); + shifted_base_num_channels = base_event->config().num_channels.value() - 1; + } + encoded_deltas = EncodeDeltas(shifted_base_num_channels, values); + if (!encoded_deltas.empty()) { + proto_batch->set_num_channels_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeAudioPlayout( + rtc::ArrayView<const RtcEventAudioPlayout*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) + return; + + // Base event + const RtcEventAudioPlayout* const base_event = batch[0]; + rtclog2::AudioPlayoutEvents* proto_batch = + event_stream->add_audio_playout_events(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_local_ssrc(base_event->ssrc()); + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioPlayout* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // local_ssrc + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventAudioPlayout* event = batch[i + 1]; + values[i] = event->ssrc(); + } + encoded_deltas = EncodeDeltas(base_event->ssrc(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_local_ssrc_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeNetEqSetMinimumDelay( + rtc::ArrayView<const RtcEventNetEqSetMinimumDelay*> batch, + rtclog2::EventStream* event_stream) { + if (encode_neteq_set_minimum_delay_kill_switch_) { + return; + } + if (batch.empty()) { + return; + } + + const RtcEventNetEqSetMinimumDelay* base_event = batch[0]; + + rtclog2::NetEqSetMinimumDelay* proto_batch = + event_stream->add_neteq_set_minimum_delay(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_remote_ssrc(base_event->remote_ssrc()); + proto_batch->set_minimum_delay_ms(base_event->minimum_delay_ms()); + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventNetEqSetMinimumDelay* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // remote_ssrc + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventNetEqSetMinimumDelay* event = batch[i + 1]; + values[i] = event->remote_ssrc(); + } + encoded_deltas = EncodeDeltas(base_event->remote_ssrc(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_remote_ssrc_deltas(encoded_deltas); + } + + // minimum_delay_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventNetEqSetMinimumDelay* event = batch[i + 1]; + values[i] = ToUnsigned(event->minimum_delay_ms()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->minimum_delay_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_minimum_delay_ms_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeAudioRecvStreamConfig( + rtc::ArrayView<const RtcEventAudioReceiveStreamConfig*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventAudioReceiveStreamConfig* base_event : batch) { + rtclog2::AudioRecvStreamConfig* proto_batch = + event_stream->add_audio_recv_stream_configs(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_remote_ssrc(base_event->config().remote_ssrc); + proto_batch->set_local_ssrc(base_event->config().local_ssrc); + + rtclog2::RtpHeaderExtensionConfig* proto_config = + proto_batch->mutable_header_extensions(); + bool has_recognized_extensions = + ConvertToProtoFormat(base_event->config().rtp_extensions, proto_config); + if (!has_recognized_extensions) + proto_batch->clear_header_extensions(); + } +} + +void RtcEventLogEncoderNewFormat::EncodeAudioSendStreamConfig( + rtc::ArrayView<const RtcEventAudioSendStreamConfig*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventAudioSendStreamConfig* base_event : batch) { + rtclog2::AudioSendStreamConfig* proto_batch = + event_stream->add_audio_send_stream_configs(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_ssrc(base_event->config().local_ssrc); + + rtclog2::RtpHeaderExtensionConfig* proto_config = + proto_batch->mutable_header_extensions(); + bool has_recognized_extensions = + ConvertToProtoFormat(base_event->config().rtp_extensions, proto_config); + if (!has_recognized_extensions) + proto_batch->clear_header_extensions(); + } +} + +void RtcEventLogEncoderNewFormat::EncodeBweUpdateDelayBased( + rtc::ArrayView<const RtcEventBweUpdateDelayBased*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) + return; + + // Base event + const RtcEventBweUpdateDelayBased* const base_event = batch[0]; + rtclog2::DelayBasedBweUpdates* proto_batch = + event_stream->add_delay_based_bwe_updates(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_bitrate_bps(base_event->bitrate_bps()); + proto_batch->set_detector_state( + ConvertToProtoFormat(base_event->detector_state())); + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateDelayBased* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // bitrate_bps + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateDelayBased* event = batch[i + 1]; + values[i] = event->bitrate_bps(); + } + encoded_deltas = EncodeDeltas(base_event->bitrate_bps(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_bitrate_bps_deltas(encoded_deltas); + } + + // detector_state + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateDelayBased* event = batch[i + 1]; + values[i] = + static_cast<uint64_t>(ConvertToProtoFormat(event->detector_state())); + } + encoded_deltas = EncodeDeltas( + static_cast<uint64_t>(ConvertToProtoFormat(base_event->detector_state())), + values); + if (!encoded_deltas.empty()) { + proto_batch->set_detector_state_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeBweUpdateLossBased( + rtc::ArrayView<const RtcEventBweUpdateLossBased*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) + return; + + // Base event + const RtcEventBweUpdateLossBased* const base_event = batch[0]; + rtclog2::LossBasedBweUpdates* proto_batch = + event_stream->add_loss_based_bwe_updates(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_bitrate_bps(base_event->bitrate_bps()); + proto_batch->set_fraction_loss(base_event->fraction_loss()); + proto_batch->set_total_packets(base_event->total_packets()); + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateLossBased* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // bitrate_bps + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateLossBased* event = batch[i + 1]; + values[i] = event->bitrate_bps(); + } + encoded_deltas = EncodeDeltas(base_event->bitrate_bps(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_bitrate_bps_deltas(encoded_deltas); + } + + // fraction_loss + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateLossBased* event = batch[i + 1]; + values[i] = event->fraction_loss(); + } + encoded_deltas = EncodeDeltas(base_event->fraction_loss(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_fraction_loss_deltas(encoded_deltas); + } + + // total_packets + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventBweUpdateLossBased* event = batch[i + 1]; + values[i] = event->total_packets(); + } + encoded_deltas = EncodeDeltas(base_event->total_packets(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_total_packets_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeDtlsTransportState( + rtc::ArrayView<const RtcEventDtlsTransportState*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventDtlsTransportState* base_event : batch) { + rtclog2::DtlsTransportStateEvent* proto_batch = + event_stream->add_dtls_transport_state_events(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_dtls_transport_state( + ConvertToProtoFormat(base_event->dtls_transport_state())); + } +} + +void RtcEventLogEncoderNewFormat::EncodeDtlsWritableState( + rtc::ArrayView<const RtcEventDtlsWritableState*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventDtlsWritableState* base_event : batch) { + rtclog2::DtlsWritableState* proto_batch = + event_stream->add_dtls_writable_states(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_writable(base_event->writable()); + } +} + +void RtcEventLogEncoderNewFormat::EncodeProbeClusterCreated( + rtc::ArrayView<const RtcEventProbeClusterCreated*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventProbeClusterCreated* base_event : batch) { + rtclog2::BweProbeCluster* proto_batch = event_stream->add_probe_clusters(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_id(base_event->id()); + proto_batch->set_bitrate_bps(base_event->bitrate_bps()); + proto_batch->set_min_packets(base_event->min_probes()); + proto_batch->set_min_bytes(base_event->min_bytes()); + } +} + +void RtcEventLogEncoderNewFormat::EncodeProbeResultFailure( + rtc::ArrayView<const RtcEventProbeResultFailure*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventProbeResultFailure* base_event : batch) { + rtclog2::BweProbeResultFailure* proto_batch = + event_stream->add_probe_failure(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_id(base_event->id()); + proto_batch->set_failure( + ConvertToProtoFormat(base_event->failure_reason())); + } + // TODO(terelius): Should we delta-compress this event type? +} + +void RtcEventLogEncoderNewFormat::EncodeProbeResultSuccess( + rtc::ArrayView<const RtcEventProbeResultSuccess*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventProbeResultSuccess* base_event : batch) { + rtclog2::BweProbeResultSuccess* proto_batch = + event_stream->add_probe_success(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_id(base_event->id()); + proto_batch->set_bitrate_bps(base_event->bitrate_bps()); + } + // TODO(terelius): Should we delta-compress this event type? +} + +void RtcEventLogEncoderNewFormat::EncodeRouteChange( + rtc::ArrayView<const RtcEventRouteChange*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventRouteChange* base_event : batch) { + rtclog2::RouteChange* proto_batch = event_stream->add_route_changes(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_connected(base_event->connected()); + proto_batch->set_overhead(base_event->overhead()); + } + // TODO(terelius): Should we delta-compress this event type? +} + +void RtcEventLogEncoderNewFormat::EncodeRemoteEstimate( + rtc::ArrayView<const RtcEventRemoteEstimate*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) + return; + + // Base event + const auto* const base_event = batch[0]; + rtclog2::RemoteEstimates* proto_batch = event_stream->add_remote_estimates(); + + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + + absl::optional<uint64_t> base_link_capacity_lower; + if (base_event->link_capacity_lower_.IsFinite()) { + base_link_capacity_lower = + base_event->link_capacity_lower_.kbps<uint32_t>(); + proto_batch->set_link_capacity_lower_kbps(*base_link_capacity_lower); + } + absl::optional<uint64_t> base_link_capacity_upper; + if (base_event->link_capacity_upper_.IsFinite()) { + base_link_capacity_upper = + base_event->link_capacity_upper_.kbps<uint32_t>(); + proto_batch->set_link_capacity_upper_kbps(*base_link_capacity_upper); + } + + if (batch.size() == 1) + return; + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const auto* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // link_capacity_lower_kbps + for (size_t i = 0; i < values.size(); ++i) { + const auto* event = batch[i + 1]; + if (event->link_capacity_lower_.IsFinite()) { + values[i] = event->link_capacity_lower_.kbps<uint32_t>(); + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_link_capacity_lower, values); + if (!encoded_deltas.empty()) { + proto_batch->set_link_capacity_lower_kbps_deltas(encoded_deltas); + } + + // link_capacity_upper_kbps + for (size_t i = 0; i < values.size(); ++i) { + const auto* event = batch[i + 1]; + if (event->link_capacity_upper_.IsFinite()) { + values[i] = event->link_capacity_upper_.kbps<uint32_t>(); + } else { + values[i].reset(); + } + } + encoded_deltas = EncodeDeltas(base_link_capacity_upper, values); + if (!encoded_deltas.empty()) { + proto_batch->set_link_capacity_upper_kbps_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeRtcpPacketIncoming( + rtc::ArrayView<const RtcEventRtcpPacketIncoming*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + EncodeRtcpPacket(batch, event_stream->add_incoming_rtcp_packets()); +} + +void RtcEventLogEncoderNewFormat::EncodeRtcpPacketOutgoing( + rtc::ArrayView<const RtcEventRtcpPacketOutgoing*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + EncodeRtcpPacket(batch, event_stream->add_outgoing_rtcp_packets()); +} + +void RtcEventLogEncoderNewFormat::EncodeRtpPacketIncoming( + const std::map<uint32_t, std::vector<const RtcEventRtpPacketIncoming*>>& + batch, + rtclog2::EventStream* event_stream) { + for (const auto& it : batch) { + RTC_DCHECK(!it.second.empty()); + EncodeRtpPacket(it.second, event_stream->add_incoming_rtp_packets()); + } +} + +void RtcEventLogEncoderNewFormat::EncodeFramesDecoded( + rtc::ArrayView<const RtcEventFrameDecoded* const> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + const RtcEventFrameDecoded* const base_event = batch[0]; + rtclog2::FrameDecodedEvents* proto_batch = + event_stream->add_frame_decoded_events(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_ssrc(base_event->ssrc()); + proto_batch->set_render_time_ms(base_event->render_time_ms()); + proto_batch->set_width(base_event->width()); + proto_batch->set_height(base_event->height()); + proto_batch->set_codec(ConvertToProtoFormat(base_event->codec())); + proto_batch->set_qp(base_event->qp()); + + if (batch.size() == 1) { + return; + } + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // SSRC + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = event->ssrc(); + } + encoded_deltas = EncodeDeltas(base_event->ssrc(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_ssrc_deltas(encoded_deltas); + } + + // render_time_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = ToUnsigned(event->render_time_ms()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->render_time_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_render_time_ms_deltas(encoded_deltas); + } + + // width + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = ToUnsigned(event->width()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->width()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_width_deltas(encoded_deltas); + } + + // height + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = ToUnsigned(event->height()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->height()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_height_deltas(encoded_deltas); + } + + // codec + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = static_cast<uint64_t>(ConvertToProtoFormat(event->codec())); + } + encoded_deltas = EncodeDeltas( + static_cast<uint64_t>(ConvertToProtoFormat(base_event->codec())), values); + if (!encoded_deltas.empty()) { + proto_batch->set_codec_deltas(encoded_deltas); + } + + // qp + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventFrameDecoded* event = batch[i + 1]; + values[i] = event->qp(); + } + encoded_deltas = EncodeDeltas(base_event->qp(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_qp_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeGenericPacketsSent( + rtc::ArrayView<const RtcEventGenericPacketSent*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + const RtcEventGenericPacketSent* const base_event = batch[0]; + rtclog2::GenericPacketSent* proto_batch = + event_stream->add_generic_packets_sent(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_packet_number(base_event->packet_number()); + proto_batch->set_overhead_length(base_event->overhead_length()); + proto_batch->set_payload_length(base_event->payload_length()); + proto_batch->set_padding_length(base_event->padding_length()); + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + if (batch.size() == 1) { + return; + } + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketSent* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // packet_number + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketSent* event = batch[i + 1]; + values[i] = ToUnsigned(event->packet_number()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->packet_number()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_packet_number_deltas(encoded_deltas); + } + + // overhead_length + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketSent* event = batch[i + 1]; + values[i] = event->overhead_length(); + } + encoded_deltas = EncodeDeltas(base_event->overhead_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_overhead_length_deltas(encoded_deltas); + } + + // payload_length + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketSent* event = batch[i + 1]; + values[i] = event->payload_length(); + } + encoded_deltas = EncodeDeltas(base_event->payload_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_payload_length_deltas(encoded_deltas); + } + + // padding_length + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketSent* event = batch[i + 1]; + values[i] = event->padding_length(); + } + encoded_deltas = EncodeDeltas(base_event->padding_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_padding_length_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeGenericPacketsReceived( + rtc::ArrayView<const RtcEventGenericPacketReceived*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + const RtcEventGenericPacketReceived* const base_event = batch[0]; + rtclog2::GenericPacketReceived* proto_batch = + event_stream->add_generic_packets_received(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_packet_number(base_event->packet_number()); + proto_batch->set_packet_length(base_event->packet_length()); + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + if (batch.size() == 1) { + return; + } + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketReceived* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // packet_number + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketReceived* event = batch[i + 1]; + values[i] = ToUnsigned(event->packet_number()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->packet_number()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_packet_number_deltas(encoded_deltas); + } + + // packet_length + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericPacketReceived* event = batch[i + 1]; + values[i] = event->packet_length(); + } + encoded_deltas = EncodeDeltas(base_event->packet_length(), values); + if (!encoded_deltas.empty()) { + proto_batch->set_packet_length_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeGenericAcksReceived( + rtc::ArrayView<const RtcEventGenericAckReceived*> batch, + rtclog2::EventStream* event_stream) { + if (batch.empty()) { + return; + } + const RtcEventGenericAckReceived* const base_event = batch[0]; + rtclog2::GenericAckReceived* proto_batch = + event_stream->add_generic_acks_received(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_packet_number(base_event->packet_number()); + proto_batch->set_acked_packet_number(base_event->acked_packet_number()); + absl::optional<uint64_t> base_receive_timestamp; + if (base_event->receive_acked_packet_time_ms()) { + int64_t receive_acked_packet_time_ms = + base_event->receive_acked_packet_time_ms().value(); + base_receive_timestamp = ToUnsigned(receive_acked_packet_time_ms); + proto_batch->set_receive_acked_packet_time_ms(receive_acked_packet_time_ms); + } + + // Delta encoding + proto_batch->set_number_of_deltas(batch.size() - 1); + std::vector<absl::optional<uint64_t>> values(batch.size() - 1); + std::string encoded_deltas; + + if (batch.size() == 1) { + return; + } + + // timestamp_ms + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericAckReceived* event = batch[i + 1]; + values[i] = ToUnsigned(event->timestamp_ms()); + } + encoded_deltas = EncodeDeltas(ToUnsigned(base_event->timestamp_ms()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_timestamp_ms_deltas(encoded_deltas); + } + + // packet_number + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericAckReceived* event = batch[i + 1]; + values[i] = ToUnsigned(event->packet_number()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->packet_number()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_packet_number_deltas(encoded_deltas); + } + + // acked packet number + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericAckReceived* event = batch[i + 1]; + values[i] = ToUnsigned(event->acked_packet_number()); + } + encoded_deltas = + EncodeDeltas(ToUnsigned(base_event->acked_packet_number()), values); + if (!encoded_deltas.empty()) { + proto_batch->set_acked_packet_number_deltas(encoded_deltas); + } + + // receive timestamp + for (size_t i = 0; i < values.size(); ++i) { + const RtcEventGenericAckReceived* event = batch[i + 1]; + if (event->receive_acked_packet_time_ms()) { + values[i] = ToUnsigned(event->receive_acked_packet_time_ms().value()); + } else { + values[i] = absl::nullopt; + } + } + encoded_deltas = EncodeDeltas(base_receive_timestamp, values); + if (!encoded_deltas.empty()) { + proto_batch->set_receive_acked_packet_time_ms_deltas(encoded_deltas); + } +} + +void RtcEventLogEncoderNewFormat::EncodeRtpPacketOutgoing( + const std::map<uint32_t, std::vector<const RtcEventRtpPacketOutgoing*>>& + batch, + rtclog2::EventStream* event_stream) { + for (const auto& it : batch) { + RTC_DCHECK(!it.second.empty()); + EncodeRtpPacket(it.second, event_stream->add_outgoing_rtp_packets()); + } +} + +void RtcEventLogEncoderNewFormat::EncodeVideoRecvStreamConfig( + rtc::ArrayView<const RtcEventVideoReceiveStreamConfig*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventVideoReceiveStreamConfig* base_event : batch) { + rtclog2::VideoRecvStreamConfig* proto_batch = + event_stream->add_video_recv_stream_configs(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_remote_ssrc(base_event->config().remote_ssrc); + proto_batch->set_local_ssrc(base_event->config().local_ssrc); + proto_batch->set_rtx_ssrc(base_event->config().rtx_ssrc); + + rtclog2::RtpHeaderExtensionConfig* proto_config = + proto_batch->mutable_header_extensions(); + bool has_recognized_extensions = + ConvertToProtoFormat(base_event->config().rtp_extensions, proto_config); + if (!has_recognized_extensions) + proto_batch->clear_header_extensions(); + } +} + +void RtcEventLogEncoderNewFormat::EncodeVideoSendStreamConfig( + rtc::ArrayView<const RtcEventVideoSendStreamConfig*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventVideoSendStreamConfig* base_event : batch) { + rtclog2::VideoSendStreamConfig* proto_batch = + event_stream->add_video_send_stream_configs(); + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_ssrc(base_event->config().local_ssrc); + proto_batch->set_rtx_ssrc(base_event->config().rtx_ssrc); + + rtclog2::RtpHeaderExtensionConfig* proto_config = + proto_batch->mutable_header_extensions(); + bool has_recognized_extensions = + ConvertToProtoFormat(base_event->config().rtp_extensions, proto_config); + if (!has_recognized_extensions) + proto_batch->clear_header_extensions(); + } +} + +void RtcEventLogEncoderNewFormat::EncodeIceCandidatePairConfig( + rtc::ArrayView<const RtcEventIceCandidatePairConfig*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventIceCandidatePairConfig* base_event : batch) { + rtclog2::IceCandidatePairConfig* proto_batch = + event_stream->add_ice_candidate_configs(); + + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + proto_batch->set_config_type(ConvertToProtoFormat(base_event->type())); + proto_batch->set_candidate_pair_id(base_event->candidate_pair_id()); + const auto& desc = base_event->candidate_pair_desc(); + proto_batch->set_local_candidate_type( + ConvertToProtoFormat(desc.local_candidate_type)); + proto_batch->set_local_relay_protocol( + ConvertToProtoFormat(desc.local_relay_protocol)); + proto_batch->set_local_network_type( + ConvertToProtoFormat(desc.local_network_type)); + proto_batch->set_local_address_family( + ConvertToProtoFormat(desc.local_address_family)); + proto_batch->set_remote_candidate_type( + ConvertToProtoFormat(desc.remote_candidate_type)); + proto_batch->set_remote_address_family( + ConvertToProtoFormat(desc.remote_address_family)); + proto_batch->set_candidate_pair_protocol( + ConvertToProtoFormat(desc.candidate_pair_protocol)); + } + // TODO(terelius): Should we delta-compress this event type? +} + +void RtcEventLogEncoderNewFormat::EncodeIceCandidatePairEvent( + rtc::ArrayView<const RtcEventIceCandidatePair*> batch, + rtclog2::EventStream* event_stream) { + for (const RtcEventIceCandidatePair* base_event : batch) { + rtclog2::IceCandidatePairEvent* proto_batch = + event_stream->add_ice_candidate_events(); + + proto_batch->set_timestamp_ms(base_event->timestamp_ms()); + + proto_batch->set_event_type(ConvertToProtoFormat(base_event->type())); + proto_batch->set_candidate_pair_id(base_event->candidate_pair_id()); + proto_batch->set_transaction_id(base_event->transaction_id()); + } + // TODO(terelius): Should we delta-compress this event type? +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h new file mode 100644 index 0000000000..6747f41f07 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_NEW_FORMAT_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_NEW_FORMAT_H_ + +#include <deque> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "api/array_view.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder.h" + +namespace webrtc { + +namespace rtclog2 { +class EventStream; // Auto-generated from protobuf. +} // namespace rtclog2 + +class RtcEventAlrState; +class RtcEventRouteChange; +class RtcEventRemoteEstimate; +class RtcEventAudioNetworkAdaptation; +class RtcEventAudioPlayout; +class RtcEventAudioReceiveStreamConfig; +class RtcEventAudioSendStreamConfig; +class RtcEventBweUpdateDelayBased; +class RtcEventBweUpdateLossBased; +class RtcEventDtlsTransportState; +class RtcEventDtlsWritableState; +class RtcEventLoggingStarted; +class RtcEventLoggingStopped; +class RtcEventNetEqSetMinimumDelay; +class RtcEventProbeClusterCreated; +class RtcEventProbeResultFailure; +class RtcEventProbeResultSuccess; +class RtcEventRtcpPacketIncoming; +class RtcEventRtcpPacketOutgoing; +class RtcEventRtpPacketIncoming; +class RtcEventRtpPacketOutgoing; +class RtcEventVideoReceiveStreamConfig; +class RtcEventVideoSendStreamConfig; +class RtcEventIceCandidatePairConfig; +class RtcEventIceCandidatePair; +class RtpPacket; +class RtcEventFrameDecoded; +class RtcEventGenericAckReceived; +class RtcEventGenericPacketReceived; +class RtcEventGenericPacketSent; + +class RtcEventLogEncoderNewFormat final : public RtcEventLogEncoder { + public: + RtcEventLogEncoderNewFormat(); + ~RtcEventLogEncoderNewFormat() override = default; + + std::string EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) override; + + std::string EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) override; + std::string EncodeLogEnd(int64_t timestamp_us) override; + + private: + bool encode_neteq_set_minimum_delay_kill_switch_ = false; + + // Encoding entry-point for the various RtcEvent subclasses. + void EncodeAlrState(rtc::ArrayView<const RtcEventAlrState*> batch, + rtclog2::EventStream* event_stream); + void EncodeAudioNetworkAdaptation( + rtc::ArrayView<const RtcEventAudioNetworkAdaptation*> batch, + rtclog2::EventStream* event_stream); + void EncodeAudioPlayout(rtc::ArrayView<const RtcEventAudioPlayout*> batch, + rtclog2::EventStream* event_stream); + void EncodeAudioRecvStreamConfig( + rtc::ArrayView<const RtcEventAudioReceiveStreamConfig*> batch, + rtclog2::EventStream* event_stream); + void EncodeAudioSendStreamConfig( + rtc::ArrayView<const RtcEventAudioSendStreamConfig*> batch, + rtclog2::EventStream* event_stream); + void EncodeBweUpdateDelayBased( + rtc::ArrayView<const RtcEventBweUpdateDelayBased*> batch, + rtclog2::EventStream* event_stream); + void EncodeBweUpdateLossBased( + rtc::ArrayView<const RtcEventBweUpdateLossBased*> batch, + rtclog2::EventStream* event_stream); + void EncodeDtlsTransportState( + rtc::ArrayView<const RtcEventDtlsTransportState*> batch, + rtclog2::EventStream* event_stream); + void EncodeDtlsWritableState( + rtc::ArrayView<const RtcEventDtlsWritableState*> batch, + rtclog2::EventStream* event_stream); + void EncodeFramesDecoded( + rtc::ArrayView<const RtcEventFrameDecoded* const> batch, + rtclog2::EventStream* event_stream); + void EncodeGenericAcksReceived( + rtc::ArrayView<const RtcEventGenericAckReceived*> batch, + rtclog2::EventStream* event_stream); + void EncodeGenericPacketsReceived( + rtc::ArrayView<const RtcEventGenericPacketReceived*> batch, + rtclog2::EventStream* event_stream); + void EncodeGenericPacketsSent( + rtc::ArrayView<const RtcEventGenericPacketSent*> batch, + rtclog2::EventStream* event_stream); + void EncodeIceCandidatePairConfig( + rtc::ArrayView<const RtcEventIceCandidatePairConfig*> batch, + rtclog2::EventStream* event_stream); + void EncodeIceCandidatePairEvent( + rtc::ArrayView<const RtcEventIceCandidatePair*> batch, + rtclog2::EventStream* event_stream); + void EncodeLoggingStarted(rtc::ArrayView<const RtcEventLoggingStarted*> batch, + rtclog2::EventStream* event_stream); + void EncodeLoggingStopped(rtc::ArrayView<const RtcEventLoggingStopped*> batch, + rtclog2::EventStream* event_stream); + void EncodeNetEqSetMinimumDelay( + rtc::ArrayView<const RtcEventNetEqSetMinimumDelay*> batch, + rtclog2::EventStream* event_stream); + void EncodeProbeClusterCreated( + rtc::ArrayView<const RtcEventProbeClusterCreated*> batch, + rtclog2::EventStream* event_stream); + void EncodeProbeResultFailure( + rtc::ArrayView<const RtcEventProbeResultFailure*> batch, + rtclog2::EventStream* event_stream); + void EncodeProbeResultSuccess( + rtc::ArrayView<const RtcEventProbeResultSuccess*> batch, + rtclog2::EventStream* event_stream); + void EncodeRouteChange(rtc::ArrayView<const RtcEventRouteChange*> batch, + rtclog2::EventStream* event_stream); + void EncodeRemoteEstimate(rtc::ArrayView<const RtcEventRemoteEstimate*> batch, + rtclog2::EventStream* event_stream); + void EncodeRtcpPacketIncoming( + rtc::ArrayView<const RtcEventRtcpPacketIncoming*> batch, + rtclog2::EventStream* event_stream); + void EncodeRtcpPacketOutgoing( + rtc::ArrayView<const RtcEventRtcpPacketOutgoing*> batch, + rtclog2::EventStream* event_stream); + void EncodeRtpPacketIncoming( + const std::map<uint32_t, std::vector<const RtcEventRtpPacketIncoming*>>& + batch, + rtclog2::EventStream* event_stream); + void EncodeRtpPacketOutgoing( + const std::map<uint32_t, std::vector<const RtcEventRtpPacketOutgoing*>>& + batch, + rtclog2::EventStream* event_stream); + void EncodeVideoRecvStreamConfig( + rtc::ArrayView<const RtcEventVideoReceiveStreamConfig*> batch, + rtclog2::EventStream* event_stream); + void EncodeVideoSendStreamConfig( + rtc::ArrayView<const RtcEventVideoSendStreamConfig*> batch, + rtclog2::EventStream* event_stream); +}; + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_NEW_FORMAT_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc new file mode 100644 index 0000000000..7039fe7eb6 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc @@ -0,0 +1,1401 @@ +/* + * Copyright (c) 2017 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 <deque> +#include <limits> +#include <memory> +#include <string> +#include <tuple> + +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.h" +#include "logging/rtc_event_log/events/rtc_event_alr_state.h" +#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h" +#include "logging/rtc_event_log/events/rtc_event_audio_playout.h" +#include "logging/rtc_event_log/events/rtc_event_audio_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h" +#include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h" +#include "logging/rtc_event_log/rtc_event_log_parser.h" +#include "logging/rtc_event_log/rtc_event_log_unittest_helper.h" +#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { +class RtcEventLogEncoderTest + : public ::testing::TestWithParam< + std::tuple<int, RtcEventLog::EncodingType, size_t, bool>> { + protected: + RtcEventLogEncoderTest() + : seed_(std::get<0>(GetParam())), + prng_(seed_), + encoding_type_(std::get<1>(GetParam())), + event_count_(std::get<2>(GetParam())), + force_repeated_fields_(std::get<3>(GetParam())), + gen_(seed_ * 880001UL), + verifier_(encoding_type_) { + switch (encoding_type_) { + case RtcEventLog::EncodingType::Legacy: + encoder_ = std::make_unique<RtcEventLogEncoderLegacy>(); + break; + case RtcEventLog::EncodingType::NewFormat: + encoder_ = std::make_unique<RtcEventLogEncoderNewFormat>(); + break; + case RtcEventLog::EncodingType::ProtoFree: + encoder_ = std::make_unique<RtcEventLogEncoderV3>(); + break; + } + encoded_ = + encoder_->EncodeLogStart(rtc::TimeMillis(), rtc::TimeUTCMillis()); + } + ~RtcEventLogEncoderTest() override = default; + + // ANA events have some optional fields, so we want to make sure that we get + // correct behavior both when all of the values are there, as well as when + // only some. + void TestRtcEventAudioNetworkAdaptation( + const std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>>&); + + template <typename EventType> + std::unique_ptr<EventType> NewRtpPacket( + uint32_t ssrc, + const RtpHeaderExtensionMap& extension_map); + + template <typename ParsedType> + const std::vector<ParsedType>* GetRtpPacketsBySsrc( + const ParsedRtcEventLog* parsed_log, + uint32_t ssrc); + + template <typename EventType, typename ParsedType> + void TestRtpPackets(); + + std::deque<std::unique_ptr<RtcEvent>> history_; + std::unique_ptr<RtcEventLogEncoder> encoder_; + ParsedRtcEventLog parsed_log_; + const uint64_t seed_; + Random prng_; + const RtcEventLog::EncodingType encoding_type_; + const size_t event_count_; + const bool force_repeated_fields_; + test::EventGenerator gen_; + test::EventVerifier verifier_; + std::string encoded_; +}; + +void RtcEventLogEncoderTest::TestRtcEventAudioNetworkAdaptation( + const std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>>& + events) { + ASSERT_TRUE(history_.empty()) << "Function should be called once per test."; + + for (auto& event : events) { + history_.push_back(event->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& ana_configs = parsed_log_.audio_network_adaptation_events(); + + ASSERT_EQ(ana_configs.size(), events.size()); + for (size_t i = 0; i < events.size(); ++i) { + verifier_.VerifyLoggedAudioNetworkAdaptationEvent(*events[i], + ana_configs[i]); + } +} + +template <> +std::unique_ptr<RtcEventRtpPacketIncoming> RtcEventLogEncoderTest::NewRtpPacket( + uint32_t ssrc, + const RtpHeaderExtensionMap& extension_map) { + return gen_.NewRtpPacketIncoming(ssrc, extension_map, false); +} + +template <> +std::unique_ptr<RtcEventRtpPacketOutgoing> RtcEventLogEncoderTest::NewRtpPacket( + uint32_t ssrc, + const RtpHeaderExtensionMap& extension_map) { + return gen_.NewRtpPacketOutgoing(ssrc, extension_map, false); +} + +template <> +const std::vector<LoggedRtpPacketIncoming>* +RtcEventLogEncoderTest::GetRtpPacketsBySsrc(const ParsedRtcEventLog* parsed_log, + uint32_t ssrc) { + const auto& incoming_streams = parsed_log->incoming_rtp_packets_by_ssrc(); + for (const auto& stream : incoming_streams) { + if (stream.ssrc == ssrc) { + return &stream.incoming_packets; + } + } + return nullptr; +} + +template <> +const std::vector<LoggedRtpPacketOutgoing>* +RtcEventLogEncoderTest::GetRtpPacketsBySsrc(const ParsedRtcEventLog* parsed_log, + uint32_t ssrc) { + const auto& outgoing_streams = parsed_log->outgoing_rtp_packets_by_ssrc(); + for (const auto& stream : outgoing_streams) { + if (stream.ssrc == ssrc) { + return &stream.outgoing_packets; + } + } + return nullptr; +} + +template <typename EventType, typename ParsedType> +void RtcEventLogEncoderTest::TestRtpPackets() { + // SSRCs will be randomly assigned out of this small pool, significant only + // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff. + // The pool is intentionally small, so as to produce collisions. + const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01, + 0xffffffff, 0x20171024, 0x19840730, + 0x19831230}; + + // TODO(terelius): Test extensions for legacy encoding, too. + RtpHeaderExtensionMap extension_map; + if (encoding_type_ != RtcEventLog::EncodingType::Legacy) { + extension_map = gen_.NewRtpHeaderExtensionMap(true); + } + + // Simulate `event_count_` RTP packets, with SSRCs assigned randomly + // out of the small pool above. + std::map<uint32_t, std::vector<std::unique_ptr<EventType>>> events_by_ssrc; + for (size_t i = 0; i < event_count_; ++i) { + const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)]; + std::unique_ptr<EventType> event = + (events_by_ssrc[ssrc].empty() || !force_repeated_fields_) + ? NewRtpPacket<EventType>(ssrc, extension_map) + : events_by_ssrc[ssrc][0]->Copy(); + history_.push_back(event->Copy()); + events_by_ssrc[ssrc].emplace_back(std::move(event)); + } + + // Encode and parse. + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + // For each SSRC, make sure the RTP packets associated with it to have been + // correctly encoded and parsed. + for (auto it = events_by_ssrc.begin(); it != events_by_ssrc.end(); ++it) { + const uint32_t ssrc = it->first; + const auto& original_packets = it->second; + const std::vector<ParsedType>* parsed_rtp_packets = + GetRtpPacketsBySsrc<ParsedType>(&parsed_log_, ssrc); + ASSERT_NE(parsed_rtp_packets, nullptr); + ASSERT_EQ(original_packets.size(), parsed_rtp_packets->size()); + for (size_t i = 0; i < original_packets.size(); ++i) { + verifier_.VerifyLoggedRtpPacket<EventType, ParsedType>( + *original_packets[i], (*parsed_rtp_packets)[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAlrState) { + std::vector<std::unique_ptr<RtcEventAlrState>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) ? gen_.NewAlrState() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& alr_state_events = parsed_log_.alr_state_events(); + + ASSERT_EQ(alr_state_events.size(), event_count_); + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedAlrStateEvent(*events[i], alr_state_events[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRouteChange) { + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + return; + } + std::vector<std::unique_ptr<RtcEventRouteChange>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) ? gen_.NewRouteChange() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& route_change_events = parsed_log_.route_change_events(); + + ASSERT_EQ(route_change_events.size(), event_count_); + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedRouteChangeEvent(*events[i], route_change_events[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRemoteEstimate) { + std::vector<std::unique_ptr<RtcEventRemoteEstimate>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewRemoteEstimate() + : std::make_unique<RtcEventRemoteEstimate>(*events[0]); + history_.push_back(std::make_unique<RtcEventRemoteEstimate>(*events[i])); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& parsed_events = parsed_log_.remote_estimate_events(); + + ASSERT_EQ(parsed_events.size(), event_count_); + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedRemoteEstimateEvent(*events[i], parsed_events[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationBitrate) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + const int bitrate_bps = rtc::checked_cast<int>( + prng_.Rand(0, std::numeric_limits<int32_t>::max())); + runtime_config->bitrate_bps = bitrate_bps; + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationFrameLength) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + const int frame_length_ms = prng_.Rand(1, 1000); + runtime_config->frame_length_ms = frame_length_ms; + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationPacketLoss) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + // To simplify the test, we just check powers of two. + const float plr = std::pow(0.5f, prng_.Rand(1, 8)); + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + runtime_config->uplink_packet_loss_fraction = plr; + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationFec) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + runtime_config->enable_fec = prng_.Rand<bool>(); + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationDtx) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + runtime_config->enable_dtx = prng_.Rand<bool>(); + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationChannels) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + runtime_config->num_channels = prng_.Rand(1, 2); + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationAll) { + std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + if (i == 0 || !force_repeated_fields_) { + auto runtime_config = std::make_unique<AudioEncoderRuntimeConfig>(); + runtime_config->bitrate_bps = rtc::checked_cast<int>( + prng_.Rand(0, std::numeric_limits<int32_t>::max())); + runtime_config->frame_length_ms = prng_.Rand(1, 1000); + runtime_config->uplink_packet_loss_fraction = + std::pow(0.5f, prng_.Rand(1, 8)); + runtime_config->enable_fec = prng_.Rand<bool>(); + runtime_config->enable_dtx = prng_.Rand<bool>(); + runtime_config->num_channels = prng_.Rand(1, 2); + events[i] = std::make_unique<RtcEventAudioNetworkAdaptation>( + std::move(runtime_config)); + } else { + events[i] = events[0]->Copy(); + } + } + TestRtcEventAudioNetworkAdaptation(events); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventAudioPlayout) { + // SSRCs will be randomly assigned out of this small pool, significant only + // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff. + // The pool is intentionally small, so as to produce collisions. + const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01, + 0xffffffff, 0x20171024, 0x19840730, + 0x19831230}; + + std::map<uint32_t, std::vector<std::unique_ptr<RtcEventAudioPlayout>>> + original_events_by_ssrc; + for (size_t i = 0; i < event_count_; ++i) { + const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)]; + std::unique_ptr<RtcEventAudioPlayout> event = + (original_events_by_ssrc[ssrc].empty() || !force_repeated_fields_) + ? gen_.NewAudioPlayout(ssrc) + : original_events_by_ssrc[ssrc][0]->Copy(); + history_.push_back(event->Copy()); + original_events_by_ssrc[ssrc].push_back(std::move(event)); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& parsed_playout_events_by_ssrc = + parsed_log_.audio_playout_events(); + + // Same number of distinct SSRCs. + ASSERT_EQ(parsed_playout_events_by_ssrc.size(), + original_events_by_ssrc.size()); + + for (auto& original_event_it : original_events_by_ssrc) { + const uint32_t ssrc = original_event_it.first; + const auto& original_playout_events = original_event_it.second; + + const auto& parsed_event_it = parsed_playout_events_by_ssrc.find(ssrc); + ASSERT_TRUE(parsed_event_it != parsed_playout_events_by_ssrc.end()); + const auto& parsed_playout_events = parsed_event_it->second; + + // Same number playout events for the SSRC under examination. + ASSERT_EQ(original_playout_events.size(), parsed_playout_events.size()); + + for (size_t i = 0; i < original_playout_events.size(); ++i) { + verifier_.VerifyLoggedAudioPlayoutEvent(*original_playout_events[i], + parsed_playout_events[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventNetEqSetMinimumDelayDecoded) { + // SSRCs will be randomly assigned out of this small pool, significant only + // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff. + // The pool is intentionally small, so as to produce collisions. + const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01, + 0xffffffff, 0x20171024, 0x19840730, + 0x19831230}; + std::map<uint32_t, std::vector<std::unique_ptr<RtcEventNetEqSetMinimumDelay>>> + original_events_by_ssrc; + for (size_t i = 0; i < event_count_; ++i) { + const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)]; + std::unique_ptr<RtcEventNetEqSetMinimumDelay> event = + (original_events_by_ssrc[ssrc].empty() || !force_repeated_fields_) + ? gen_.NewNetEqSetMinimumDelay(ssrc) + : original_events_by_ssrc[ssrc][0]->Copy(); + history_.push_back(event->Copy()); + original_events_by_ssrc[ssrc].push_back(std::move(event)); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& parsed_neteq_set_minimum_delay_events_by_ssrc = + parsed_log_.neteq_set_minimum_delay_events(); + + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + ASSERT_EQ(parsed_neteq_set_minimum_delay_events_by_ssrc.size(), 0u); + return; + } + + // Same number of distinct SSRCs. + ASSERT_EQ(parsed_neteq_set_minimum_delay_events_by_ssrc.size(), + original_events_by_ssrc.size()); + + for (auto& original_event_it : original_events_by_ssrc) { + const uint32_t ssrc = original_event_it.first; + const auto& original_neteq_set_minimum_delay_events = + original_event_it.second; + + const auto& parsed_event_it = + parsed_neteq_set_minimum_delay_events_by_ssrc.find(ssrc); + ASSERT_TRUE(parsed_event_it != + parsed_neteq_set_minimum_delay_events_by_ssrc.end()); + const auto& parsed_neteq_set_minimum_delay_events = parsed_event_it->second; + + // Same number playout events for the SSRC under examination. + ASSERT_EQ(original_neteq_set_minimum_delay_events.size(), + parsed_neteq_set_minimum_delay_events.size()); + + for (size_t i = 0; i < original_neteq_set_minimum_delay_events.size(); + ++i) { + verifier_.VerifyLoggedNetEqSetMinimumDelay( + *original_neteq_set_minimum_delay_events[i], + parsed_neteq_set_minimum_delay_events[i]); + } + } +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventAudioReceiveStreamConfig) { + uint32_t ssrc = prng_.Rand<uint32_t>(); + RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap(); + std::unique_ptr<RtcEventAudioReceiveStreamConfig> event = + gen_.NewAudioReceiveStreamConfig(ssrc, extensions); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& audio_recv_configs = parsed_log_.audio_recv_configs(); + + ASSERT_EQ(audio_recv_configs.size(), 1u); + verifier_.VerifyLoggedAudioRecvConfig(*event, audio_recv_configs[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventAudioSendStreamConfig) { + uint32_t ssrc = prng_.Rand<uint32_t>(); + RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap(); + std::unique_ptr<RtcEventAudioSendStreamConfig> event = + gen_.NewAudioSendStreamConfig(ssrc, extensions); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& audio_send_configs = parsed_log_.audio_send_configs(); + + ASSERT_EQ(audio_send_configs.size(), 1u); + verifier_.VerifyLoggedAudioSendConfig(*event, audio_send_configs[0]); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventBweUpdateDelayBased) { + std::vector<std::unique_ptr<RtcEventBweUpdateDelayBased>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewBweUpdateDelayBased() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& bwe_delay_updates = parsed_log_.bwe_delay_updates(); + ASSERT_EQ(bwe_delay_updates.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedBweDelayBasedUpdate(*events[i], bwe_delay_updates[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventBweUpdateLossBased) { + std::vector<std::unique_ptr<RtcEventBweUpdateLossBased>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewBweUpdateLossBased() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& bwe_loss_updates = parsed_log_.bwe_loss_updates(); + ASSERT_EQ(bwe_loss_updates.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedBweLossBasedUpdate(*events[i], bwe_loss_updates[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventGenericPacketReceived) { + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + return; + } + std::vector<std::unique_ptr<RtcEventGenericPacketReceived>> events( + event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewGenericPacketReceived() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& packets_received = parsed_log_.generic_packets_received(); + ASSERT_EQ(packets_received.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedGenericPacketReceived(*events[i], + packets_received[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventGenericPacketSent) { + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + return; + } + std::vector<std::unique_ptr<RtcEventGenericPacketSent>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewGenericPacketSent() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& packets_sent = parsed_log_.generic_packets_sent(); + ASSERT_EQ(packets_sent.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedGenericPacketSent(*events[i], packets_sent[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventGenericAcksReceived) { + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + return; + } + std::vector<std::unique_ptr<RtcEventGenericAckReceived>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewGenericAckReceived() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& decoded_events = parsed_log_.generic_acks_received(); + ASSERT_EQ(decoded_events.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedGenericAckReceived(*events[i], decoded_events[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventDtlsTransportState) { + std::vector<std::unique_ptr<RtcEventDtlsTransportState>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewDtlsTransportState() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& dtls_transport_states = parsed_log_.dtls_transport_states(); + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + ASSERT_EQ(dtls_transport_states.size(), 0u); + return; + } + + ASSERT_EQ(dtls_transport_states.size(), event_count_); + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedDtlsTransportState(*events[i], + dtls_transport_states[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventDtlsWritableState) { + std::vector<std::unique_ptr<RtcEventDtlsWritableState>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewDtlsWritableState() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& dtls_writable_states = parsed_log_.dtls_writable_states(); + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + ASSERT_EQ(dtls_writable_states.size(), 0u); + return; + } + + ASSERT_EQ(dtls_writable_states.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedDtlsWritableState(*events[i], + dtls_writable_states[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventFrameDecoded) { + // SSRCs will be randomly assigned out of this small pool, significant only + // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff. + // The pool is intentionally small, so as to produce collisions. + const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01, + 0xffffffff, 0x20171024, 0x19840730, + 0x19831230}; + + std::map<uint32_t, std::vector<std::unique_ptr<RtcEventFrameDecoded>>> + original_events_by_ssrc; + for (size_t i = 0; i < event_count_; ++i) { + const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)]; + std::unique_ptr<RtcEventFrameDecoded> event = + (original_events_by_ssrc[ssrc].empty() || !force_repeated_fields_) + ? gen_.NewFrameDecodedEvent(ssrc) + : original_events_by_ssrc[ssrc][0]->Copy(); + history_.push_back(event->Copy()); + original_events_by_ssrc[ssrc].push_back(std::move(event)); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + auto status = parsed_log_.ParseString(encoded_); + if (!status.ok()) + RTC_LOG(LS_ERROR) << status.message(); + ASSERT_TRUE(status.ok()); + + const auto& decoded_frames_by_ssrc = parsed_log_.decoded_frames(); + if (encoding_type_ == RtcEventLog::EncodingType::Legacy) { + ASSERT_EQ(decoded_frames_by_ssrc.size(), 0u); + return; + } + + // Same number of distinct SSRCs. + ASSERT_EQ(decoded_frames_by_ssrc.size(), original_events_by_ssrc.size()); + + for (const auto& original_event_it : original_events_by_ssrc) { + const uint32_t ssrc = original_event_it.first; + const std::vector<std::unique_ptr<RtcEventFrameDecoded>>& original_frames = + original_event_it.second; + + const auto& parsed_event_it = decoded_frames_by_ssrc.find(ssrc); + ASSERT_TRUE(parsed_event_it != decoded_frames_by_ssrc.end()); + const std::vector<LoggedFrameDecoded>& parsed_frames = + parsed_event_it->second; + + // Same number events for the SSRC under examination. + ASSERT_EQ(original_frames.size(), parsed_frames.size()); + + for (size_t i = 0; i < original_frames.size(); ++i) { + verifier_.VerifyLoggedFrameDecoded(*original_frames[i], parsed_frames[i]); + } + } +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventIceCandidatePairConfig) { + std::unique_ptr<RtcEventIceCandidatePairConfig> event = + gen_.NewIceCandidatePairConfig(); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& ice_candidate_pair_configs = + parsed_log_.ice_candidate_pair_configs(); + + ASSERT_EQ(ice_candidate_pair_configs.size(), 1u); + verifier_.VerifyLoggedIceCandidatePairConfig(*event, + ice_candidate_pair_configs[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventIceCandidatePair) { + std::unique_ptr<RtcEventIceCandidatePair> event = gen_.NewIceCandidatePair(); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& ice_candidate_pair_events = + parsed_log_.ice_candidate_pair_events(); + + ASSERT_EQ(ice_candidate_pair_events.size(), 1u); + verifier_.VerifyLoggedIceCandidatePairEvent(*event, + ice_candidate_pair_events[0]); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventLoggingStarted) { + const int64_t timestamp_ms = prng_.Rand(1'000'000'000); + const int64_t utc_time_ms = prng_.Rand(1'000'000'000); + + // Overwrite the previously encoded LogStart event. + encoded_ = encoder_->EncodeLogStart(timestamp_ms * 1000, utc_time_ms * 1000); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& start_log_events = parsed_log_.start_log_events(); + + ASSERT_EQ(start_log_events.size(), 1u); + verifier_.VerifyLoggedStartEvent(timestamp_ms * 1000, utc_time_ms * 1000, + start_log_events[0]); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventLoggingStopped) { + const int64_t start_timestamp_ms = prng_.Rand(1'000'000'000); + const int64_t start_utc_time_ms = prng_.Rand(1'000'000'000); + + // Overwrite the previously encoded LogStart event. + encoded_ = encoder_->EncodeLogStart(start_timestamp_ms * 1000, + start_utc_time_ms * 1000); + + const int64_t stop_timestamp_ms = + prng_.Rand(start_timestamp_ms, 2'000'000'000); + encoded_ += encoder_->EncodeLogEnd(stop_timestamp_ms * 1000); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& stop_log_events = parsed_log_.stop_log_events(); + + ASSERT_EQ(stop_log_events.size(), 1u); + verifier_.VerifyLoggedStopEvent(stop_timestamp_ms * 1000, stop_log_events[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventProbeClusterCreated) { + std::unique_ptr<RtcEventProbeClusterCreated> event = + gen_.NewProbeClusterCreated(); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& bwe_probe_cluster_created_events = + parsed_log_.bwe_probe_cluster_created_events(); + + ASSERT_EQ(bwe_probe_cluster_created_events.size(), 1u); + verifier_.VerifyLoggedBweProbeClusterCreatedEvent( + *event, bwe_probe_cluster_created_events[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventProbeResultFailure) { + std::unique_ptr<RtcEventProbeResultFailure> event = + gen_.NewProbeResultFailure(); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& bwe_probe_failure_events = parsed_log_.bwe_probe_failure_events(); + + ASSERT_EQ(bwe_probe_failure_events.size(), 1u); + verifier_.VerifyLoggedBweProbeFailureEvent(*event, + bwe_probe_failure_events[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventProbeResultSuccess) { + std::unique_ptr<RtcEventProbeResultSuccess> event = + gen_.NewProbeResultSuccess(); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& bwe_probe_success_events = parsed_log_.bwe_probe_success_events(); + + ASSERT_EQ(bwe_probe_success_events.size(), 1u); + verifier_.VerifyLoggedBweProbeSuccessEvent(*event, + bwe_probe_success_events[0]); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpPacketIncoming) { + if (force_repeated_fields_) { + // RTCP packets maybe delivered twice (once for audio and once for video). + // As a work around, we're removing duplicates in the parser. + return; + } + + std::vector<std::unique_ptr<RtcEventRtcpPacketIncoming>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewRtcpPacketIncoming() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& incoming_rtcp_packets = parsed_log_.incoming_rtcp_packets(); + ASSERT_EQ(incoming_rtcp_packets.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedRtcpPacketIncoming(*events[i], + incoming_rtcp_packets[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpPacketOutgoing) { + std::vector<std::unique_ptr<RtcEventRtcpPacketOutgoing>> events(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + events[i] = (i == 0 || !force_repeated_fields_) + ? gen_.NewRtcpPacketOutgoing() + : events[0]->Copy(); + history_.push_back(events[i]->Copy()); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& outgoing_rtcp_packets = parsed_log_.outgoing_rtcp_packets(); + ASSERT_EQ(outgoing_rtcp_packets.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedRtcpPacketOutgoing(*events[i], + outgoing_rtcp_packets[i]); + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpReceiverReport) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::ReceiverReport> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewReceiverReport(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& receiver_reports = parsed_log_.receiver_reports(direction); + ASSERT_EQ(receiver_reports.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedReceiverReport(timestamps_ms[i], events[i], + receiver_reports[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpSenderReport) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::SenderReport> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewSenderReport(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& sender_reports = parsed_log_.sender_reports(direction); + ASSERT_EQ(sender_reports.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedSenderReport(timestamps_ms[i], events[i], + sender_reports[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpExtendedReports) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::ExtendedReports> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewExtendedReports(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& extended_reports = parsed_log_.extended_reports(direction); + ASSERT_EQ(extended_reports.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedExtendedReports(timestamps_ms[i], events[i], + extended_reports[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpFir) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::Fir> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewFir(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& firs = parsed_log_.firs(direction); + ASSERT_EQ(firs.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedFir(timestamps_ms[i], events[i], firs[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpPli) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::Pli> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewPli(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& plis = parsed_log_.plis(direction); + ASSERT_EQ(plis.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedPli(timestamps_ms[i], events[i], plis[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpBye) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::Bye> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewBye(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& byes = parsed_log_.byes(direction); + ASSERT_EQ(byes.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedBye(timestamps_ms[i], events[i], byes[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpNack) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::Nack> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewNack(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& nacks = parsed_log_.nacks(direction); + ASSERT_EQ(nacks.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedNack(timestamps_ms[i], events[i], nacks[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpRemb) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::Remb> events(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events[i] = gen_.NewRemb(); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& rembs = parsed_log_.rembs(direction); + ASSERT_EQ(rembs.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedRemb(timestamps_ms[i], events[i], rembs[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpTransportFeedback) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::TransportFeedback> events; + events.reserve(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events.emplace_back(gen_.NewTransportFeedback()); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& transport_feedbacks = + parsed_log_.transport_feedbacks(direction); + ASSERT_EQ(transport_feedbacks.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedTransportFeedback(timestamps_ms[i], events[i], + transport_feedbacks[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtcpLossNotification) { + if (force_repeated_fields_) { + return; + } + + rtc::ScopedFakeClock fake_clock; + fake_clock.SetTime(Timestamp::Millis(prng_.Rand<uint32_t>())); + + for (auto direction : {kIncomingPacket, kOutgoingPacket}) { + std::vector<rtcp::LossNotification> events; + events.reserve(event_count_); + std::vector<int64_t> timestamps_ms(event_count_); + for (size_t i = 0; i < event_count_; ++i) { + timestamps_ms[i] = rtc::TimeMillis(); + events.emplace_back(gen_.NewLossNotification()); + rtc::Buffer buffer = events[i].Build(); + if (direction == kIncomingPacket) { + history_.push_back( + std::make_unique<RtcEventRtcpPacketIncoming>(buffer)); + } else { + history_.push_back( + std::make_unique<RtcEventRtcpPacketOutgoing>(buffer)); + } + fake_clock.AdvanceTime(TimeDelta::Millis(prng_.Rand(0, 1000))); + } + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + + const auto& loss_notifications = parsed_log_.loss_notifications(direction); + ASSERT_EQ(loss_notifications.size(), event_count_); + + for (size_t i = 0; i < event_count_; ++i) { + verifier_.VerifyLoggedLossNotification(timestamps_ms[i], events[i], + loss_notifications[i]); + } + } +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtpPacketIncoming) { + TestRtpPackets<RtcEventRtpPacketIncoming, LoggedRtpPacketIncoming>(); +} + +TEST_P(RtcEventLogEncoderTest, RtcEventRtpPacketOutgoing) { + TestRtpPackets<RtcEventRtpPacketOutgoing, LoggedRtpPacketOutgoing>(); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventVideoReceiveStreamConfig) { + uint32_t ssrc = prng_.Rand<uint32_t>(); + RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap(); + std::unique_ptr<RtcEventVideoReceiveStreamConfig> event = + gen_.NewVideoReceiveStreamConfig(ssrc, extensions); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& video_recv_configs = parsed_log_.video_recv_configs(); + + ASSERT_EQ(video_recv_configs.size(), 1u); + verifier_.VerifyLoggedVideoRecvConfig(*event, video_recv_configs[0]); +} + +// TODO(eladalon/terelius): Test with multiple events in the batch. +TEST_P(RtcEventLogEncoderTest, RtcEventVideoSendStreamConfig) { + uint32_t ssrc = prng_.Rand<uint32_t>(); + RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap(); + std::unique_ptr<RtcEventVideoSendStreamConfig> event = + gen_.NewVideoSendStreamConfig(ssrc, extensions); + history_.push_back(event->Copy()); + + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + ASSERT_TRUE(parsed_log_.ParseString(encoded_).ok()); + const auto& video_send_configs = parsed_log_.video_send_configs(); + + ASSERT_EQ(video_send_configs.size(), 1u); + verifier_.VerifyLoggedVideoSendConfig(*event, video_send_configs[0]); +} + +INSTANTIATE_TEST_SUITE_P( + RandomSeeds, + RtcEventLogEncoderTest, + ::testing::Combine(/* Random seed*: */ ::testing::Values(1, 2, 3, 4, 5), + /* Encoding: */ + ::testing::Values(RtcEventLog::EncodingType::Legacy, + RtcEventLog::EncodingType::NewFormat), + /* Event count: */ ::testing::Values(1, 2, 10, 100), + /* Repeated fields: */ ::testing::Bool())); + +class RtcEventLogEncoderSimpleTest + : public ::testing::TestWithParam<RtcEventLog::EncodingType> { + protected: + RtcEventLogEncoderSimpleTest() : encoding_type_(GetParam()) { + switch (encoding_type_) { + case RtcEventLog::EncodingType::Legacy: + encoder_ = std::make_unique<RtcEventLogEncoderLegacy>(); + break; + case RtcEventLog::EncodingType::NewFormat: + encoder_ = std::make_unique<RtcEventLogEncoderNewFormat>(); + break; + case RtcEventLog::EncodingType::ProtoFree: + encoder_ = std::make_unique<RtcEventLogEncoderV3>(); + break; + } + encoded_ = + encoder_->EncodeLogStart(rtc::TimeMillis(), rtc::TimeUTCMillis()); + } + ~RtcEventLogEncoderSimpleTest() override = default; + + std::deque<std::unique_ptr<RtcEvent>> history_; + std::unique_ptr<RtcEventLogEncoder> encoder_; + ParsedRtcEventLog parsed_log_; + const RtcEventLog::EncodingType encoding_type_; + std::string encoded_; +}; + +TEST_P(RtcEventLogEncoderSimpleTest, RtcEventLargeCompoundRtcpPacketIncoming) { + // Create a compound packet containing multiple Bye messages. + rtc::Buffer packet; + size_t index = 0; + for (int i = 0; i < 8; i++) { + rtcp::Bye bye; + std::string reason(255, 'a'); // Add some arbitrary data. + bye.SetReason(reason); + bye.SetSenderSsrc(0x12345678); + packet.SetSize(packet.size() + bye.BlockLength()); + bool created = + bye.Create(packet.data(), &index, packet.capacity(), nullptr); + ASSERT_TRUE(created); + ASSERT_EQ(index, packet.size()); + } + + EXPECT_GT(packet.size(), static_cast<size_t>(IP_PACKET_SIZE)); + auto event = std::make_unique<RtcEventRtcpPacketIncoming>(packet); + history_.push_back(event->Copy()); + encoded_ += encoder_->EncodeBatch(history_.begin(), history_.end()); + + ParsedRtcEventLog::ParseStatus status = parsed_log_.ParseString(encoded_); + ASSERT_TRUE(status.ok()) << status.message(); + + const auto& incoming_rtcp_packets = parsed_log_.incoming_rtcp_packets(); + ASSERT_EQ(incoming_rtcp_packets.size(), 1u); + ASSERT_EQ(incoming_rtcp_packets[0].rtcp.raw_data.size(), packet.size()); + EXPECT_EQ(memcmp(incoming_rtcp_packets[0].rtcp.raw_data.data(), packet.data(), + packet.size()), + 0); +} + +INSTANTIATE_TEST_SUITE_P( + LargeCompoundRtcp, + RtcEventLogEncoderSimpleTest, + ::testing::Values(RtcEventLog::EncodingType::Legacy, + RtcEventLog::EncodingType::NewFormat)); + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.cc new file mode 100644 index 0000000000..131aae1de8 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.cc @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2021 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/rtc_event_log_encoder_v3.h" + +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h" +#include "logging/rtc_event_log/encoder/var_int.h" +#include "logging/rtc_event_log/events/rtc_event_alr_state.h" +#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h" +#include "logging/rtc_event_log/events/rtc_event_audio_playout.h" +#include "logging/rtc_event_log/events/rtc_event_audio_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_begin_log.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" +#include "logging/rtc_event_log/events/rtc_event_bwe_update_loss_based.h" +#include "logging/rtc_event_log/events/rtc_event_dtls_transport_state.h" +#include "logging/rtc_event_log/events/rtc_event_dtls_writable_state.h" +#include "logging/rtc_event_log/events/rtc_event_end_log.h" +#include "logging/rtc_event_log/events/rtc_event_frame_decoded.h" +#include "logging/rtc_event_log/events/rtc_event_generic_ack_received.h" +#include "logging/rtc_event_log/events/rtc_event_generic_packet_received.h" +#include "logging/rtc_event_log/events/rtc_event_generic_packet_sent.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h" +#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" +#include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" +#include "logging/rtc_event_log/events/rtc_event_remote_estimate.h" +#include "logging/rtc_event_log/events/rtc_event_route_change.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h" +#include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +std::string RtcEventLogEncoderV3::EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) { + std::unique_ptr<RtcEventBeginLog> begin_log = + std::make_unique<RtcEventBeginLog>(Timestamp::Micros(timestamp_us), + Timestamp::Micros(utc_time_us)); + std::vector<const RtcEvent*> batch; + batch.push_back(begin_log.get()); + + std::string encoded_event = RtcEventBeginLog::Encode(batch); + + return encoded_event; +} + +std::string RtcEventLogEncoderV3::EncodeLogEnd(int64_t timestamp_us) { + std::unique_ptr<RtcEventEndLog> end_log = + std::make_unique<RtcEventEndLog>(Timestamp::Micros(timestamp_us)); + std::vector<const RtcEvent*> batch; + batch.push_back(end_log.get()); + + std::string encoded_event = RtcEventEndLog::Encode(batch); + + return encoded_event; +} + +RtcEventLogEncoderV3::RtcEventLogEncoderV3() { + encoders_[RtcEvent::Type::AlrStateEvent] = RtcEventAlrState::Encode; + encoders_[RtcEvent::Type::AudioNetworkAdaptation] = + RtcEventAudioNetworkAdaptation::Encode; + encoders_[RtcEvent::Type::AudioPlayout] = RtcEventAudioPlayout::Encode; + encoders_[RtcEvent::Type::AudioReceiveStreamConfig] = + RtcEventAudioReceiveStreamConfig::Encode; + encoders_[RtcEvent::Type::AudioSendStreamConfig] = + RtcEventAudioSendStreamConfig::Encode; + encoders_[RtcEvent::Type::BweUpdateDelayBased] = + RtcEventBweUpdateDelayBased::Encode; + encoders_[RtcEvent::Type::BweUpdateLossBased] = + RtcEventBweUpdateLossBased::Encode; + encoders_[RtcEvent::Type::DtlsTransportState] = + RtcEventDtlsTransportState::Encode; + encoders_[RtcEvent::Type::DtlsWritableState] = + RtcEventDtlsWritableState::Encode; + encoders_[RtcEvent::Type::FrameDecoded] = RtcEventFrameDecoded::Encode; + encoders_[RtcEvent::Type::GenericAckReceived] = + RtcEventGenericAckReceived::Encode; + encoders_[RtcEvent::Type::GenericPacketReceived] = + RtcEventGenericPacketReceived::Encode; + encoders_[RtcEvent::Type::GenericPacketSent] = + RtcEventGenericPacketSent::Encode; + encoders_[RtcEvent::Type::IceCandidatePairConfig] = + RtcEventIceCandidatePairConfig::Encode; + encoders_[RtcEvent::Type::IceCandidatePairEvent] = + RtcEventIceCandidatePair::Encode; + encoders_[RtcEvent::Type::ProbeClusterCreated] = + RtcEventProbeClusterCreated::Encode; + encoders_[RtcEvent::Type::ProbeResultFailure] = + RtcEventProbeResultFailure::Encode; + encoders_[RtcEvent::Type::ProbeResultSuccess] = + RtcEventProbeResultSuccess::Encode; + encoders_[RtcEvent::Type::RemoteEstimateEvent] = + RtcEventRemoteEstimate::Encode; + encoders_[RtcEvent::Type::RouteChangeEvent] = RtcEventRouteChange::Encode; + encoders_[RtcEvent::Type::RtcpPacketIncoming] = + RtcEventRtcpPacketIncoming::Encode; + encoders_[RtcEvent::Type::RtcpPacketOutgoing] = + RtcEventRtcpPacketOutgoing::Encode; + encoders_[RtcEvent::Type::RtpPacketIncoming] = + RtcEventRtpPacketIncoming::Encode; + encoders_[RtcEvent::Type::RtpPacketOutgoing] = + RtcEventRtpPacketOutgoing::Encode; + encoders_[RtcEvent::Type::VideoReceiveStreamConfig] = + RtcEventVideoReceiveStreamConfig::Encode; + encoders_[RtcEvent::Type::VideoSendStreamConfig] = + RtcEventVideoSendStreamConfig::Encode; +} + +std::string RtcEventLogEncoderV3::EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) { + struct EventGroupKey { + // Events are grouped by event type. For compression efficiency, + // events can optionally have a secondary key, in most cases the + // SSRC. + RtcEvent::Type type; + uint32_t secondary_group_key; + + bool operator<(EventGroupKey other) const { + return type < other.type || + (type == other.type && + secondary_group_key < other.secondary_group_key); + } + }; + + std::map<EventGroupKey, std::vector<const RtcEvent*>> event_groups; + + for (auto it = begin; it != end; ++it) { + event_groups[{(*it)->GetType(), (*it)->GetGroupKey()}].push_back(it->get()); + } + + std::string encoded_output; + for (auto& kv : event_groups) { + auto it = encoders_.find(kv.first.type); + RTC_DCHECK(it != encoders_.end()); + if (it != encoders_.end()) { + auto& encoder = it->second; + // TODO(terelius): Use some "string builder" or preallocate? + encoded_output += encoder(kv.second); + } + } + + return encoded_output; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.h new file mode 100644 index 0000000000..cb796ec562 --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/rtc_event_log_encoder_v3.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_V3_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_V3_H_ + +#include <deque> +#include <map> +#include <memory> +#include <string> + +#include "api/array_view.h" +#include "logging/rtc_event_log/encoder/rtc_event_log_encoder.h" +#include "logging/rtc_event_log/events/rtc_event_definition.h" + +namespace webrtc { + +class RtcEventLogEncoderV3 final : public RtcEventLogEncoder { + public: + RtcEventLogEncoderV3(); + ~RtcEventLogEncoderV3() override = default; + + std::string EncodeBatch( + std::deque<std::unique_ptr<RtcEvent>>::const_iterator begin, + std::deque<std::unique_ptr<RtcEvent>>::const_iterator end) override; + + std::string EncodeLogStart(int64_t timestamp_us, + int64_t utc_time_us) override; + std::string EncodeLogEnd(int64_t timestamp_us) override; + + private: + std::map<RtcEvent::Type, + std::function<std::string(rtc::ArrayView<const RtcEvent*>)>> + encoders_; +}; + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_V3_H_ diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.cc new file mode 100644 index 0000000000..a84a233d6b --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.cc @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 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/var_int.h" + +#include "rtc_base/bitstream_reader.h" +#include "rtc_base/checks.h" + +// TODO(eladalon): Add unit tests. + +namespace webrtc { + +const size_t kMaxVarIntLengthBytes = 10; // ceil(64 / 7.0) is 10. + +std::string EncodeVarInt(uint64_t input) { + std::string output; + output.reserve(kMaxVarIntLengthBytes); + + do { + uint8_t byte = static_cast<uint8_t>(input & 0x7f); + input >>= 7; + if (input > 0) { + byte |= 0x80; + } + output += byte; + } while (input > 0); + + RTC_DCHECK_GE(output.size(), 1u); + RTC_DCHECK_LE(output.size(), kMaxVarIntLengthBytes); + + return output; +} + +// There is some code duplication between the flavors of this function. +// For performance's sake, it's best to just keep it. +std::pair<bool, absl::string_view> DecodeVarInt(absl::string_view input, + uint64_t* output) { + RTC_DCHECK(output); + + uint64_t decoded = 0; + for (size_t i = 0; i < input.length() && i < kMaxVarIntLengthBytes; ++i) { + decoded += (static_cast<uint64_t>(input[i] & 0x7f) + << static_cast<uint64_t>(7 * i)); + if (!(input[i] & 0x80)) { + *output = decoded; + return {true, input.substr(i + 1)}; + } + } + + return {false, input}; +} + +// There is some code duplication between the flavors of this function. +// For performance's sake, it's best to just keep it. +uint64_t DecodeVarInt(BitstreamReader& input) { + uint64_t decoded = 0; + for (size_t i = 0; i < kMaxVarIntLengthBytes; ++i) { + uint8_t byte = input.Read<uint8_t>(); + decoded += + (static_cast<uint64_t>(byte & 0x7f) << static_cast<uint64_t>(7 * i)); + if (!(byte & 0x80)) { + return decoded; + } + } + + input.Invalidate(); + return 0; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.h b/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.h new file mode 100644 index 0000000000..4624e046ba --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/var_int.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_VAR_INT_H_ +#define LOGGING_RTC_EVENT_LOG_ENCODER_VAR_INT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "rtc_base/bitstream_reader.h" + +namespace webrtc { + +extern const size_t kMaxVarIntLengthBytes; + +// Encode a given uint64_t as a varint. From least to most significant, +// each batch of seven bits are put into the lower bits of a byte, and the last +// remaining bit in that byte (the highest one) marks whether additional bytes +// follow (which happens if and only if there are other bits in `input` which +// are non-zero). +// Notes: If input == 0, one byte is used. If input is uint64_t::max, exactly +// kMaxVarIntLengthBytes are used. +std::string EncodeVarInt(uint64_t input); + +// Inverse of EncodeVarInt(). +// Returns true and the remaining (unread) slice of the input if decoding +// succeeds. Returns false otherwise and `output` is not modified. +std::pair<bool, absl::string_view> DecodeVarInt(absl::string_view input, + uint64_t* output); + +// Same as other version, but uses a BitstreamReader for input. +// If decoding is successful returns the decoded varint. +// If not successful, `input` reader is set into the failure state, return value +// is unspecified. +uint64_t DecodeVarInt(BitstreamReader& input); + +} // namespace webrtc + +#endif // LOGGING_RTC_EVENT_LOG_ENCODER_VAR_INT_H_ |