| /* | |
| * 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 <cstddef> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #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/bitstream_reader.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.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 */, | |
| 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, 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( | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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( | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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, | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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 std::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<std::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( | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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](std::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( | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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; | |
| std::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, | |
| std::optional<uint64_t> base, | |
| const std::vector<std::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 (std::optional<uint64_t> value : values_) { | |
| writer_->WriteBits(value.has_value() ? 1u : 0u, 1); | |
| } | |
| } | |
| std::optional<uint64_t> previous = base_; | |
| for (std::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(), | |
| [](std::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<std::optional<uint64_t>> DecodeDeltas( | |
| absl::string_view input, | |
| std::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, | |
| std::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, | |
| std::optional<uint64_t> base, | |
| size_t num_of_deltas); | |
| // Perform the decoding using the parameters given to the ctor. | |
| std::vector<std::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 std::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<std::optional<uint64_t>> FixedLengthDeltaDecoder::DecodeDeltas( | |
| absl::string_view input, | |
| std::optional<uint64_t> base, | |
| size_t num_of_deltas) { | |
| auto decoder = FixedLengthDeltaDecoder::Create(input, base, num_of_deltas); | |
| if (!decoder) { | |
| return std::vector<std::optional<uint64_t>>(); | |
| } | |
| return decoder->Decode(); | |
| } | |
| std::unique_ptr<FixedLengthDeltaDecoder> FixedLengthDeltaDecoder::Create( | |
| absl::string_view input, | |
| std::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, 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, | |
| std::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<std::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); | |
| } | |
| std::optional<uint64_t> previous = base_; | |
| std::vector<std::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(std::optional<uint64_t> base, | |
| const std::vector<std::optional<uint64_t>>& values) { | |
| // TODO(eladalon): Support additional encodings. | |
| return FixedLengthDeltaEncoder::EncodeDeltas(base, values); | |
| } | |
| std::vector<std::optional<uint64_t>> DecodeDeltas(absl::string_view input, | |
| std::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<std::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<std::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 |