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