diff --git a/logging/rtc_event_log/encoder/delta_encoding.cc b/logging/rtc_event_log/encoder/delta_encoding.cc index 755eb41ec8..3d242ec3ab 100644 --- a/logging/rtc_event_log/encoder/delta_encoding.cc +++ b/logging/rtc_event_log/encoder/delta_encoding.cc @@ -188,7 +188,6 @@ class FixedLengthEncodingParameters final { // 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). - // TODO(eladalon): Add support for optional elements. bool values_optional() const { return values_optional_; } // Number of bits necessary to hold the largest value in the sequence. @@ -226,18 +225,20 @@ class FixedLengthDeltaEncoder final { // 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(uint64_t base, - const std::vector& values); + static std::string EncodeDeltas( + absl::optional base, + const std::vector>& values); private: // Calculate min/max values of unsigned/signed deltas, given the bit width // of all the values in the series. - static void CalculateMinAndMaxDeltas(uint64_t base, - const std::vector& values, - uint64_t bit_width, - uint64_t* max_unsigned_delta, - uint64_t* max_pos_signed_delta, - uint64_t* min_neg_signed_delta); + static void CalculateMinAndMaxDeltas( + uint64_t base, + const std::vector>& 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. @@ -251,17 +252,18 @@ class FixedLengthDeltaEncoder final { // 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, - uint64_t base, - const std::vector& values); + absl::optional base, + const std::vector>& 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() const; + size_t OutputLengthBytes(size_t existent_values_count) const; size_t HeaderLengthBits() const; - size_t EncodedDeltasLengthBits() const; + size_t EncodedDeltasLengthBits(size_t existent_values_count) const; // Encode the compression parameters into the stream. void EncodeHeader(); @@ -278,11 +280,11 @@ class FixedLengthDeltaEncoder final { // 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 uint64_t base_; + const absl::optional base_; // The values to be encoded. // Note: This is a non-owning reference. See comment above ctor for details. - const std::vector& values_; + const std::vector>& values_; // Buffer into which encoded values will be written. // This is created dynmically as a way to enforce that the rest of the @@ -295,15 +297,34 @@ class FixedLengthDeltaEncoder final { // TODO(eladalon): Reduce the number of passes. std::string FixedLengthDeltaEncoder::EncodeDeltas( - uint64_t base, - const std::vector& values) { + absl::optional base, + const std::vector>& values) { RTC_DCHECK(!values.empty()); - bool non_decreasing = base <= values[0]; - uint64_t max_value_including_base = std::max(base, values[0]); - for (size_t i = 1; i < values.size(); ++i) { - non_decreasing &= (values[i - 1] <= values[i]); - max_value_including_base = std::max(max_value_including_base, values[i]); + // 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 val) { return val == base; })) { + return std::string(); + } + + const uint64_t base_val = base.value_or(0u); + + bool non_decreasing = true; + uint64_t max_value_including_base = base_val; + uint64_t previous = base_val; + size_t existent_values_count = 0; + 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; @@ -314,16 +335,9 @@ std::string FixedLengthDeltaEncoder::EncodeDeltas( 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); - - // We indicate the special case of all values being equal to the base with - // the empty string. - if (max_unsigned_delta == 0) { - RTC_DCHECK(std::all_of(values.cbegin(), values.cend(), - [base](uint64_t val) { return val == base; })); - return std::string(); - } + CalculateMinAndMaxDeltas(base_val, 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); @@ -336,7 +350,7 @@ std::string FixedLengthDeltaEncoder::EncodeDeltas( const uint64_t delta_width_bits = signed_deltas ? delta_width_bits_signed : delta_width_bits_unsigned; - const bool values_optional = false; + const bool values_optional = (existent_values_count < values.size()); FixedLengthEncodingParameters params(delta_width_bits, signed_deltas, values_optional, value_width_bits); @@ -345,13 +359,13 @@ std::string FixedLengthDeltaEncoder::EncodeDeltas( ConsiderTestOverrides(¶ms, delta_width_bits_signed, delta_width_bits_unsigned); - FixedLengthDeltaEncoder encoder(params, base, values); + FixedLengthDeltaEncoder encoder(params, base, values, existent_values_count); return encoder.Encode(); } void FixedLengthDeltaEncoder::CalculateMinAndMaxDeltas( uint64_t base, - const std::vector& values, + const std::vector>& values, uint64_t bit_width, uint64_t* max_unsigned_delta_out, uint64_t* max_pos_signed_delta_out, @@ -369,8 +383,14 @@ void FixedLengthDeltaEncoder::CalculateMinAndMaxDeltas( uint64_t prev = base; for (size_t i = 0; i < values.size(); ++i) { - const uint64_t forward_delta = UnsignedDelta(prev, values[i], bit_mask); - const uint64_t backward_delta = UnsignedDelta(values[i], prev, bit_mask); + if (!values[i].has_value()) { + 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); @@ -380,7 +400,7 @@ void FixedLengthDeltaEncoder::CalculateMinAndMaxDeltas( min_neg_signed_delta = std::max(min_neg_signed_delta, backward_delta); } - prev = values[i]; + prev = current; } *max_unsigned_delta_out = max_unsigned_delta; @@ -405,28 +425,42 @@ void FixedLengthDeltaEncoder::ConsiderTestOverrides( FixedLengthDeltaEncoder::FixedLengthDeltaEncoder( const FixedLengthEncodingParameters& params, - uint64_t base, - const std::vector& values) + absl::optional base, + const std::vector>& values, + size_t existent_values_count) : params_(params), base_(base), values_(values) { RTC_DCHECK(!values_.empty()); - writer_ = absl::make_unique(OutputLengthBytes()); + writer_ = + absl::make_unique(OutputLengthBytes(existent_values_count)); } std::string FixedLengthDeltaEncoder::Encode() { EncodeHeader(); - uint64_t previous = base_; - for (uint64_t value : values_) { - EncodeDelta(previous, value); - previous = value; + if (params_.values_optional()) { + // Encode which values exist and which don't. + for (absl::optional value : values_) { + writer_->WriteBits(value.has_value() ? 1u : 0u, 1); + } + } + + uint64_t previous = base_.has_value() ? base_.value() : 0u; + for (absl::optional value : values_) { + if (!value.has_value()) { + RTC_DCHECK(params_.values_optional()); + continue; + } + EncodeDelta(previous, value.value()); + previous = value.value(); } return writer_->GetString(); } -size_t FixedLengthDeltaEncoder::OutputLengthBytes() const { - const size_t length_bits = HeaderLengthBits() + EncodedDeltasLengthBits(); - return BitsToBytes(length_bits); +size_t FixedLengthDeltaEncoder::OutputLengthBytes( + size_t existent_values_count) const { + return BitsToBytes(HeaderLengthBits() + + EncodedDeltasLengthBits(existent_values_count)); } size_t FixedLengthDeltaEncoder::HeaderLengthBits() const { @@ -441,11 +475,21 @@ size_t FixedLengthDeltaEncoder::HeaderLengthBits() const { } } -size_t FixedLengthDeltaEncoder::EncodedDeltasLengthBits() const { - // TODO(eladalon): When optional values are supported, iterate over the - // deltas to check the exact cost of each. - RTC_DCHECK(!params_.values_optional()); - return values_.size() * params_.delta_width_bits(); +size_t FixedLengthDeltaEncoder::EncodedDeltasLengthBits( + size_t existent_values_count) const { + if (params_.values_optional()) { + RTC_DCHECK_EQ(std::count_if(values_.begin(), values_.end(), + [](absl::optional 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. + return (1 * values_.size()) + + (params_.delta_width_bits() * existent_values_count); + } else { + return values_.size() * params_.delta_width_bits(); + } } void FixedLengthDeltaEncoder::EncodeHeader() { @@ -534,9 +578,10 @@ class FixedLengthDeltaDecoder final { // 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 DecodeDeltas(const std::string& input, - uint64_t base, - size_t num_of_deltas); + static std::vector> DecodeDeltas( + const std::string& input, + absl::optional base, + size_t num_of_deltas); private: // Reads the encoding header in |input| and returns a FixedLengthDeltaDecoder @@ -547,8 +592,10 @@ class FixedLengthDeltaDecoder final { // 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 - Create(const std::string& input, uint64_t base, size_t num_of_deltas); + static std::unique_ptr Create( + const std::string& input, + absl::optional 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 @@ -558,11 +605,11 @@ class FixedLengthDeltaDecoder final { // of |reader|'s underlying buffer. FixedLengthDeltaDecoder(std::unique_ptr reader, const FixedLengthEncodingParameters& params, - uint64_t base, + absl::optional base, size_t num_of_deltas); // Perform the decoding using the parameters given to the ctor. - std::vector Decode(); + std::vector> Decode(); // Attempt to parse a delta from the input reader. // Returns true/false for success/failure. @@ -590,7 +637,7 @@ class FixedLengthDeltaDecoder final { // 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 uint64_t base_; + const absl::optional base_; // The number of values to be known to be decoded. const size_t num_of_deltas_; @@ -618,13 +665,13 @@ bool FixedLengthDeltaDecoder::IsSuitableDecoderFor(const std::string& input) { EncodingType::kFixedSizeSignedDeltasEarlyWrapAndOptSupported; } -std::vector FixedLengthDeltaDecoder::DecodeDeltas( +std::vector> FixedLengthDeltaDecoder::DecodeDeltas( const std::string& input, - uint64_t base, + absl::optional base, size_t num_of_deltas) { auto decoder = FixedLengthDeltaDecoder::Create(input, base, num_of_deltas); if (!decoder) { - return std::vector(); + return std::vector>(); } return decoder->Decode(); @@ -632,7 +679,7 @@ std::vector FixedLengthDeltaDecoder::DecodeDeltas( std::unique_ptr FixedLengthDeltaDecoder::Create( const std::string& input, - uint64_t base, + absl::optional base, size_t num_of_deltas) { if (input.length() < kBitsInHeaderForEncodingType) { return nullptr; @@ -685,10 +732,6 @@ std::unique_ptr FixedLengthDeltaDecoder::Create( } RTC_DCHECK_LE(read_buffer, 1); values_optional = rtc::dchecked_cast(read_buffer); - if (values_optional) { - RTC_LOG(LS_WARNING) << "Not implemented."; - return nullptr; - } // value_width_bits if (!reader->ReadBits(&read_buffer, kBitsInHeaderForValueWidthBits)) { @@ -716,28 +759,48 @@ std::unique_ptr FixedLengthDeltaDecoder::Create( FixedLengthDeltaDecoder::FixedLengthDeltaDecoder( std::unique_ptr reader, const FixedLengthEncodingParameters& params, - uint64_t base, + absl::optional base, size_t num_of_deltas) : reader_(std::move(reader)), params_(params), base_(base), num_of_deltas_(num_of_deltas) { RTC_DCHECK(reader_); - // TODO(eladalon): Support optional values. - RTC_DCHECK(!params.values_optional()) << "Not implemented."; } -std::vector FixedLengthDeltaDecoder::Decode() { - std::vector values(num_of_deltas_); +std::vector> FixedLengthDeltaDecoder::Decode() { + RTC_DCHECK(reader_); - uint64_t previous = base_; + std::vector existing_values(num_of_deltas_); + if (params_.values_optional()) { + for (size_t i = 0; i < num_of_deltas_; ++i) { + uint32_t exists; + if (!reader_->ReadBits(&exists, 1u)) { + RTC_LOG(LS_WARNING) << "Failed to read existence-indicating bit."; + return std::vector>(); + } + RTC_DCHECK_LE(exists, 1u); + existing_values[i] = (exists == 1); + } + } else { + std::fill(existing_values.begin(), existing_values.end(), true); + } + + std::vector> values(num_of_deltas_); + + uint64_t previous = base_.has_value() ? base_.value() : 0u; for (size_t i = 0; i < num_of_deltas_; ++i) { + if (!existing_values[i]) { + RTC_DCHECK(params_.values_optional()); + continue; + } + uint64_t delta; if (!ParseDelta(&delta)) { - return std::vector(); + return std::vector>(); } values[i] = ApplyDelta(previous, delta); - previous = values[i]; + previous = values[i].value(); } return values; @@ -745,7 +808,6 @@ std::vector FixedLengthDeltaDecoder::Decode() { bool FixedLengthDeltaDecoder::ParseDelta(uint64_t* delta) { RTC_DCHECK(reader_); - RTC_DCHECK(!params_.values_optional()) << "Not implemented."; // Reminder. // BitBuffer and BitBufferWriter read/write higher bits before lower bits. @@ -781,7 +843,6 @@ bool FixedLengthDeltaDecoder::ParseDelta(uint64_t* delta) { uint64_t FixedLengthDeltaDecoder::ApplyDelta(uint64_t base, uint64_t delta) const { - RTC_DCHECK(!params_.values_optional()) << "Not implemented."; // Reminder. 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) @@ -816,20 +877,22 @@ uint64_t FixedLengthDeltaDecoder::ApplySignedDelta(uint64_t base, } // namespace -std::string EncodeDeltas(uint64_t base, const std::vector& values) { +std::string EncodeDeltas(absl::optional base, + const std::vector>& values) { // TODO(eladalon): Support additional encodings. return FixedLengthDeltaEncoder::EncodeDeltas(base, values); } -std::vector DecodeDeltas(const std::string& input, - uint64_t base, - size_t num_of_deltas) { +std::vector> DecodeDeltas( + const std::string& input, + absl::optional 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 result(num_of_deltas); + std::vector> result(num_of_deltas); std::fill(result.begin(), result.end(), base); return result; } @@ -839,7 +902,7 @@ std::vector DecodeDeltas(const std::string& input, } RTC_LOG(LS_WARNING) << "Could not decode delta-encoded stream."; - return std::vector(); + return std::vector>(); } void SetFixedLengthEncoderDeltaSignednessForTesting(bool signedness) { diff --git a/logging/rtc_event_log/encoder/delta_encoding.h b/logging/rtc_event_log/encoder/delta_encoding.h index 7ce5e4f23f..9e5e8b7f43 100644 --- a/logging/rtc_event_log/encoder/delta_encoding.h +++ b/logging/rtc_event_log/encoder/delta_encoding.h @@ -14,6 +14,8 @@ #include #include +#include "absl/types/optional.h" + namespace webrtc { // Encode |values| as a sequence of deltas following on |base| and return it. @@ -22,7 +24,9 @@ namespace webrtc { // |base| is not guaranteed to be written into |output|, and must therefore // be provided separately to the decoder. // This function never fails. -std::string EncodeDeltas(uint64_t base, const std::vector& values); +// TODO(eladalon): Split into optional and non-optional variants (efficiency). +std::string EncodeDeltas(absl::optional base, + const std::vector>& values); // EncodeDeltas() and DecodeDeltas() are inverse operations; // invoking DecodeDeltas() over the output of EncodeDeltas(), will return @@ -30,9 +34,11 @@ std::string EncodeDeltas(uint64_t base, const std::vector& values); // |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. -std::vector DecodeDeltas(const std::string& input, - uint64_t base, - size_t num_of_deltas); +// TODO(eladalon): Split into optional and non-optional variants (efficiency). +std::vector> DecodeDeltas( + const std::string& input, + absl::optional base, + size_t num_of_deltas); } // namespace webrtc diff --git a/logging/rtc_event_log/encoder/delta_encoding_unittest.cc b/logging/rtc_event_log/encoder/delta_encoding_unittest.cc index d5535177ce..1cf3b648b1 100644 --- a/logging/rtc_event_log/encoder/delta_encoding_unittest.cc +++ b/logging/rtc_event_log/encoder/delta_encoding_unittest.cc @@ -67,50 +67,56 @@ uint64_t RandomWithMaxBitWidth(Random* prng, uint64_t max_width) { // 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(uint64_t base, - const std::vector& values, - std::string* encoded_string = nullptr) { +void TestEncodingAndDecoding( + absl::optional base, + const std::vector>& values, + std::string* encoded_string = nullptr) { const std::string encoded = EncodeDeltas(base, values); if (encoded_string) { *encoded_string = encoded; } - const std::vector decoded = + const std::vector> decoded = DecodeDeltas(encoded, base, values.size()); EXPECT_EQ(decoded, values); } -std::vector CreateSequenceByFirstValue(uint64_t first, - size_t sequence_length) { - std::vector sequence(sequence_length); +std::vector> CreateSequenceByFirstValue( + uint64_t first, + size_t sequence_length) { + std::vector> sequence(sequence_length); std::iota(sequence.begin(), sequence.end(), first); return sequence; } -std::vector CreateSequenceByLastValue(uint64_t last, - size_t num_values) { +std::vector> CreateSequenceByLastValue( + uint64_t last, + size_t num_values) { const uint64_t first = last - num_values + 1; - std::vector result(num_values); + std::vector> 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 CreateSequenceByDeltas( +std::vector> CreateSequenceByOptionalDeltas( uint64_t first, - const std::vector& deltas, + const std::vector>& deltas, size_t sequence_length) { RTC_DCHECK_GE(sequence_length, 1); - std::vector sequence(sequence_length); + std::vector> sequence(sequence_length); uint64_t previous = first; for (size_t i = 0, next_delta_index = 0; i < sequence.size(); ++i) { - sequence[i] = previous + deltas[next_delta_index]; + if (deltas[next_delta_index].has_value()) { + sequence[i] = + absl::optional(previous + deltas[next_delta_index].value()); + previous = sequence[i].value(); + } next_delta_index = (next_delta_index + 1) % deltas.size(); - previous = sequence[i]; } return sequence; @@ -134,25 +140,59 @@ size_t EncodingLengthUpperBound(size_t delta_max_bit_width, 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> CreateSequenceByDeltas( + uint64_t first, + const std::vector& deltas, + size_t sequence_length) { + RTC_DCHECK(!deltas.empty()); + std::vector> optional_deltas(deltas.size()); + for (size_t i = 0; i < deltas.size(); ++i) { + optional_deltas[i] = absl::optional(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> { +class DeltaEncodingTest : public ::testing::TestWithParam< + std::tuple> { public: DeltaEncodingTest() : signedness_(std::get<0>(GetParam())), - num_of_values_(std::get<1>(GetParam())) { + num_of_values_(std::get<1>(GetParam())), + optional_values_(std::get<2>(GetParam())) { MaybeSetSignedness(signedness_); } + ~DeltaEncodingTest() override = default; const DeltaSignedness signedness_; const uint64_t num_of_values_; + const bool optional_values_; }; -TEST_P(DeltaEncodingTest, AllValuesEqualToBaseValue) { - const uint64_t base = 3432; - std::vector values(num_of_values_); +TEST_P(DeltaEncodingTest, AllValuesEqualToExistentBaseValue) { + const absl::optional base(3432); + std::vector> 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 base; + std::vector> values(num_of_values_); std::fill(values.begin(), values.end(), base); std::string encoded; TestEncodingAndDecoding(base, values, &encoded); @@ -163,54 +203,89 @@ TEST_P(DeltaEncodingTest, AllValuesEqualToBaseValue) { } TEST_P(DeltaEncodingTest, MinDeltaNoWrapAround) { - const uint64_t base = 3432; + const absl::optional base(3432); - const auto values = CreateSequenceByFirstValue(base + 1, num_of_values_); + 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(); + } + TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, BigDeltaNoWrapAround) { const uint64_t kBigDelta = 132828; - const uint64_t base = 3432; + const absl::optional base(3432); - const auto values = - CreateSequenceByFirstValue(base + kBigDelta, num_of_values_); + 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(); + } + TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, MaxDeltaNoWrapAround) { - const uint64_t base = 3432; + const absl::optional base(3432); - const auto values = CreateSequenceByLastValue( - std::numeric_limits::max(), num_of_values_); + auto values = CreateSequenceByLastValue(std::numeric_limits::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(); + } + TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundComparedToBase) { - const uint64_t base = std::numeric_limits::max(); + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } - const auto values = CreateSequenceByDeltas(base, {1, 10, 3}, num_of_values_); - ASSERT_LT(values[values.size() - 1], base) << "Sanity; must wrap around"; + const absl::optional base(std::numeric_limits::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(); + } TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundInValueSequence) { - if (num_of_values_ == 1) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { return; // Inapplicable. } - const uint64_t base = std::numeric_limits::max() - 2; + const absl::optional base(std::numeric_limits::max() - 2); - const auto values = CreateSequenceByDeltas(base, {1, 10, 3}, num_of_values_); + 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(); + } + TestEncodingAndDecoding(base, values); } @@ -220,27 +295,46 @@ TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundInValueSequence) { #pragma warning(disable : 4307) #endif TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundComparedToBase) { - const uint64_t kBigDelta = 132828; - const uint64_t base = std::numeric_limits::max() - kBigDelta + 3; + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } - const auto values = - CreateSequenceByFirstValue(base + kBigDelta, num_of_values_); - ASSERT_LT(values[values.size() - 1], base) << "Sanity; must wrap around"; + const uint64_t kBigDelta = 132828; + const absl::optional base(std::numeric_limits::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(); + } TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundInValueSequence) { - if (num_of_values_ == 1) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { return; // Inapplicable. } const uint64_t kBigDelta = 132828; - const uint64_t base = std::numeric_limits::max() - kBigDelta + 3; + const absl::optional base(std::numeric_limits::max() - + kBigDelta + 3); - const auto values = CreateSequenceByFirstValue( - std::numeric_limits::max(), num_of_values_); - ASSERT_LT(values[values.size() - 1], base) << "Sanity; must wrap around"; + auto values = CreateSequenceByFirstValue(std::numeric_limits::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(); + } TestEncodingAndDecoding(base, values); } @@ -249,36 +343,60 @@ TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundInValueSequence) { #endif TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundComparedToBase) { - const uint64_t base = 3432; - const auto values = CreateSequenceByFirstValue(base - 1, num_of_values_); + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const absl::optional 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(); + } + TestEncodingAndDecoding(base, values); } TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundInValueSequence) { - if (num_of_values_ == 1) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { return; // Inapplicable. } - const uint64_t base = 3432; + const absl::optional base(3432); - const auto values = CreateSequenceByDeltas( - base, {0, std::numeric_limits::max(), 3}, num_of_values_); - ASSERT_LT(values[1], base) << "Sanity; must wrap around"; + auto values = CreateSequenceByDeltas( + *base, {0, std::numeric_limits::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(); + } TestEncodingAndDecoding(base, values); } // If num_of_values_ == 1, a zero delta will yield an empty string; that's -// already covered by AllValuesEqualToBaseValue, but it doesn't hurt to test -// again. For all other cases, we have a new test. +// 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 uint64_t base = 3432; + const absl::optional base(3432); // Arbitrary sequence of deltas with intentional zero deltas, as well as // consecutive zeros. const std::vector deltas = {0, 312, 11, 1, 1, 0, 0, 12, 400321, 3, 3, 12, 5, 0, 6}; - const auto values = CreateSequenceByDeltas(base, deltas, num_of_values_); + 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(); + } TestEncodingAndDecoding(base, values); } @@ -289,7 +407,8 @@ INSTANTIATE_TEST_CASE_P( ::testing::Combine(::testing::Values(DeltaSignedness::kNoOverride, DeltaSignedness::kForceUnsigned, DeltaSignedness::kForceSigned), - ::testing::Values(1, 2, 100, 10000))); + ::testing::Values(1, 2, 100, 10000), + ::testing::Bool())); // Tests over the quality of the compression (as opposed to its correctness). // Not to be confused with tests of runtime efficiency. @@ -332,7 +451,6 @@ TEST_P(DeltaEncodingCompressionQualityTest, // need to be conveyed explicitly in the encoding header. const uint64_t bases[] = {0, 0x55, 0xffffffff, std::numeric_limits::max()}; - const size_t kIntendedWrapAroundBaseIndex = arraysize(bases); std::vector deltas(num_of_values_); @@ -408,12 +526,13 @@ INSTANTIATE_TEST_CASE_P( // specific cases, produce large amount of semi-realistic inputs. class DeltaEncodingFuzzerLikeTest : public ::testing::TestWithParam< - std::tuple> { + std::tuple> { public: DeltaEncodingFuzzerLikeTest() : signedness_(std::get<0>(GetParam())), delta_max_bit_width_(std::get<1>(GetParam())), - num_of_values_(std::get<2>(GetParam())) { + num_of_values_(std::get<2>(GetParam())), + optional_values_(std::get<3>(GetParam())) { MaybeSetSignedness(signedness_); } @@ -427,24 +546,27 @@ class DeltaEncodingFuzzerLikeTest // to produce unique results. return non_zero_base_seed + 2 * static_cast(signedness_) + 3 * delta_max_bit_width_ + 5 * delta_max_bit_width_ + - 7 * num_of_values_; + 7 * num_of_values_ + 11 * static_cast(optional_values_); } const DeltaSignedness signedness_; const uint64_t delta_max_bit_width_; const uint64_t num_of_values_; + const bool optional_values_; }; TEST_P(DeltaEncodingFuzzerLikeTest, Test) { - const uint64_t base = 3432; + const absl::optional base(3432); Random prng(Seed()); - std::vector deltas(num_of_values_); + std::vector> deltas(num_of_values_); for (size_t i = 0; i < deltas.size(); ++i) { - deltas[i] = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + if (!optional_values_ || prng.Rand()) { + deltas[i] = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + } } - - const auto values = CreateSequenceByDeltas(base, deltas, num_of_values_); + const auto values = + CreateSequenceByOptionalDeltas(base.value(), deltas, num_of_values_); TestEncodingAndDecoding(base, values); } @@ -457,7 +579,8 @@ INSTANTIATE_TEST_CASE_P( 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(1, 2, 100, 10000), + ::testing::Bool())); class DeltaEncodingSpecificEdgeCasesTest : public ::testing::TestWithParam< @@ -474,10 +597,10 @@ class DeltaEncodingSpecificEdgeCasesTest TEST_F(DeltaEncodingSpecificEdgeCasesTest, SignedDeltaWithOnlyTopBitOn) { MaybeSetSignedness(DeltaSignedness::kForceSigned); - const uint64_t base = 3432; + const absl::optional base(3432); const uint64_t delta = static_cast(1) << 63; - const std::vector values = {base + delta}; + const std::vector> values = {base.value() + delta}; TestEncodingAndDecoding(base, values); } @@ -485,9 +608,9 @@ TEST_F(DeltaEncodingSpecificEdgeCasesTest, SignedDeltaWithOnlyTopBitOn) { TEST_F(DeltaEncodingSpecificEdgeCasesTest, MaximumUnsignedDelta) { MaybeSetSignedness(DeltaSignedness::kForceUnsigned); - const uint64_t base = (static_cast(1) << 63) + 0x123; + const absl::optional base((static_cast(1) << 63) + 0x123); - const std::vector values = {base - 1}; + const std::vector> values = {base.value() - 1}; TestEncodingAndDecoding(base, values); } @@ -503,9 +626,9 @@ TEST_P(DeltaEncodingSpecificEdgeCasesTest, ReverseSequence) { : ((static_cast(1) << width) - 1); const uint64_t base = wrap_around ? 1u : (0xf82d3 & value_mask); - const std::vector values = {(base - 1u) & value_mask, - (base - 2u) & value_mask, - (base - 3u) & value_mask}; + const std::vector> values = { + (base - 1u) & value_mask, (base - 2u) & value_mask, + (base - 3u) & value_mask}; TestEncodingAndDecoding(base, values); }