| /* |
| * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "logging/rtc_event_log/events/rtc_event_field_encoding.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/strings/string_view.h" |
| #include "api/array_view.h" |
| #include "api/rtc_event_log/rtc_event.h" |
| #include "logging/rtc_event_log/encoder/bit_writer.h" |
| #include "logging/rtc_event_log/encoder/var_int.h" |
| #include "logging/rtc_event_log/events/fixed_length_encoding_parameters_v3.h" |
| #include "logging/rtc_event_log/events/rtc_event_field_extraction.h" |
| #include "rtc_base/checks.h" |
| |
| using webrtc_event_logging::UnsignedDelta; |
| |
| namespace { |
| |
| std::string SerializeLittleEndian(uint64_t value, uint8_t bytes) { |
| RTC_DCHECK_LE(bytes, sizeof(uint64_t)); |
| RTC_DCHECK_GE(bytes, 1); |
| if (bytes < sizeof(uint64_t)) { |
| // Note that shifting a 64-bit value by 64 (or more) bits is undefined. |
| RTC_DCHECK_EQ(value >> (8 * bytes), 0); |
| } |
| std::string output(bytes, 0); |
| // Getting a non-const pointer to the representation. See e.g. |
| // https://en.cppreference.com/w/cpp/string/basic_string: |
| // "The elements of a basic_string are stored contiguously, |
| // that is, [...] a pointer to s[0] can be passed to functions |
| // that expect a pointer to the first element of a null-terminated |
| // CharT[] array." |
| uint8_t* p = reinterpret_cast<uint8_t*>(&output[0]); |
| #ifdef WEBRTC_ARCH_LITTLE_ENDIAN |
| memcpy(p, &value, bytes); |
| #else |
| while (bytes > 0) { |
| *p = static_cast<uint8_t>(value & 0xFF); |
| value >>= 8; |
| ++p; |
| --bytes; |
| } |
| #endif // WEBRTC_ARCH_LITTLE_ENDIAN |
| return output; |
| } |
| |
| } // namespace |
| |
| namespace webrtc { |
| |
| std::string EncodeOptionalValuePositions(std::vector<bool> positions) { |
| BitWriter writer((positions.size() + 7) / 8); |
| for (bool position : positions) { |
| writer.WriteBits(position ? 1u : 0u, 1); |
| } |
| return writer.GetString(); |
| } |
| |
| std::string EncodeSingleValue(uint64_t value, FieldType field_type) { |
| switch (field_type) { |
| case FieldType::kFixed8: |
| return SerializeLittleEndian(value, /*bytes=*/1); |
| case FieldType::kFixed32: |
| return SerializeLittleEndian(value, /*bytes=*/4); |
| case FieldType::kFixed64: |
| return SerializeLittleEndian(value, /*bytes=*/8); |
| case FieldType::kVarInt: |
| return EncodeVarInt(value); |
| case FieldType::kString: |
| RTC_DCHECK_NOTREACHED(); |
| return std::string(); |
| } |
| RTC_DCHECK_NOTREACHED(); |
| return std::string(); |
| } |
| |
| std::optional<FieldType> ConvertFieldType(uint64_t value) { |
| switch (value) { |
| case static_cast<uint64_t>(FieldType::kFixed8): |
| return FieldType::kFixed8; |
| case static_cast<uint64_t>(FieldType::kFixed32): |
| return FieldType::kFixed32; |
| case static_cast<uint64_t>(FieldType::kFixed64): |
| return FieldType::kFixed64; |
| case static_cast<uint64_t>(FieldType::kVarInt): |
| return FieldType::kVarInt; |
| case static_cast<uint64_t>(FieldType::kString): |
| return FieldType::kString; |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| std::string EncodeDeltasV3(FixedLengthEncodingParametersV3 params, |
| uint64_t base, |
| rtc::ArrayView<const uint64_t> values) { |
| size_t outputbound = (values.size() * params.delta_bit_width() + 7) / 8; |
| BitWriter writer(outputbound); |
| |
| uint64_t previous = base; |
| for (uint64_t value : values) { |
| if (params.signed_deltas()) { |
| uint64_t positive_delta = |
| UnsignedDelta(previous, value, params.value_mask()); |
| uint64_t negative_delta = |
| UnsignedDelta(value, previous, params.value_mask()); |
| uint64_t delta; |
| if (positive_delta <= negative_delta) { |
| delta = positive_delta; |
| } else { |
| // Compute the two's complement representation of a negative |
| // delta, in a field width params_.delta_mask(). |
| RTC_DCHECK_GE(params.delta_mask(), negative_delta); |
| RTC_DCHECK_LT(params.delta_mask() - negative_delta, |
| params.delta_mask()); |
| delta = params.delta_mask() - negative_delta + 1; |
| RTC_DCHECK_LE(delta, params.delta_mask()); |
| } |
| writer.WriteBits(delta, params.delta_bit_width()); |
| } else { |
| uint64_t delta = UnsignedDelta(previous, value, params.value_mask()); |
| writer.WriteBits(delta, params.delta_bit_width()); |
| } |
| previous = value; |
| } |
| |
| return writer.GetString(); |
| } |
| |
| EventEncoder::EventEncoder(EventParameters params, |
| rtc::ArrayView<const RtcEvent*> batch) { |
| batch_size_ = batch.size(); |
| if (!batch.empty()) { |
| // Encode event type. |
| uint32_t batched = batch.size() > 1 ? 1 : 0; |
| event_tag_ = (static_cast<uint32_t>(params.id) << 1) + batched; |
| |
| // Event tag and number of encoded bytes will be filled in when the |
| // encoding is finalized in AsString(). |
| |
| // Encode number of events in batch |
| if (batched) { |
| encoded_fields_.push_back(EncodeVarInt(batch.size())); |
| } |
| |
| // Encode timestamp |
| std::vector<uint64_t> timestamps; |
| timestamps.reserve(batch.size()); |
| for (const RtcEvent* event : batch) { |
| timestamps.push_back(EncodeAsUnsigned(event->timestamp_ms())); |
| } |
| constexpr FieldParameters timestamp_params{"timestamp_ms", |
| FieldParameters::kTimestampField, |
| FieldType::kVarInt, 64}; |
| EncodeField(timestamp_params, timestamps); |
| } |
| } |
| |
| void EventEncoder::EncodeField(const FieldParameters& params, |
| const ValuesWithPositions& values) { |
| return EncodeField(params, values.values, &values.position_mask); |
| } |
| |
| void EventEncoder::EncodeField(const FieldParameters& params, |
| const std::vector<uint64_t>& values, |
| const std::vector<bool>* positions) { |
| if (positions) { |
| RTC_DCHECK_EQ(positions->size(), batch_size_); |
| RTC_DCHECK_LE(values.size(), batch_size_); |
| } else { |
| RTC_DCHECK_EQ(values.size(), batch_size_); |
| } |
| |
| if (values.empty()) { |
| // If all values for a particular field is empty/nullopt, |
| // then we completely skip the field even if the the batch is non-empty. |
| return; |
| } |
| |
| // We know that each event starts with the varint encoded timestamp, |
| // so we omit that field tag (field id + field type). In all other |
| // cases, we write the field tag. |
| if (params.field_id != FieldParameters::kTimestampField) { |
| RTC_DCHECK_LE(params.field_id, std::numeric_limits<uint64_t>::max() >> 3); |
| uint64_t field_tag = params.field_id << 3; |
| field_tag += static_cast<uint64_t>(params.field_type); |
| encoded_fields_.push_back(EncodeVarInt(field_tag)); |
| } |
| |
| RTC_CHECK_GE(values.size(), 1); |
| if (batch_size_ == 1) { |
| encoded_fields_.push_back(EncodeSingleValue(values[0], params.field_type)); |
| return; |
| } |
| |
| const bool values_optional = values.size() != batch_size_; |
| |
| // Compute delta parameters |
| rtc::ArrayView<const uint64_t> all_values(values); |
| uint64_t base = values[0]; |
| rtc::ArrayView<const uint64_t> remaining_values(all_values.subview(1)); |
| |
| FixedLengthEncodingParametersV3 delta_params = |
| FixedLengthEncodingParametersV3::CalculateParameters( |
| base, remaining_values, params.value_width, values_optional); |
| |
| encoded_fields_.push_back(EncodeVarInt(delta_params.DeltaHeaderAsInt())); |
| |
| if (values_optional) { |
| RTC_CHECK(positions); |
| encoded_fields_.push_back(EncodeOptionalValuePositions(*positions)); |
| } |
| // Base element, encoded as uint8, uint32, uint64 or varint |
| encoded_fields_.push_back(EncodeSingleValue(base, params.field_type)); |
| |
| // If all (existing) values are equal to the base, then we can skip |
| // writing the all-zero deltas, and instead infer those from the delta |
| // header. |
| if (!delta_params.values_equal()) { |
| encoded_fields_.push_back( |
| EncodeDeltasV3(delta_params, base, remaining_values)); |
| } |
| } |
| |
| void EventEncoder::EncodeField(const FieldParameters& params, |
| const std::vector<absl::string_view>& values) { |
| RTC_DCHECK_EQ(values.size(), batch_size_); |
| |
| if (values.empty()) { |
| // If all values for a particular field is empty/nullopt, |
| // then we completely skip the field even if the the batch is non-empty. |
| return; |
| } |
| |
| // Write the field tag. |
| RTC_CHECK_NE(params.field_id, FieldParameters::kTimestampField); |
| RTC_DCHECK_LE(params.field_id, std::numeric_limits<uint64_t>::max() >> 3); |
| RTC_DCHECK_EQ(params.field_type, FieldType::kString); |
| uint64_t field_tag = params.field_id << 3; |
| field_tag += static_cast<uint64_t>(params.field_type); |
| encoded_fields_.push_back(EncodeVarInt(field_tag)); |
| |
| if (values.size() > 1) { |
| // If multiple values in the batch, write the encoding |
| // parameters. (Values >0 reserved for future use.) |
| uint64_t encoding_params = 0; |
| encoded_fields_.push_back(EncodeVarInt(encoding_params)); |
| } |
| |
| // Write the strings as (length, data) pairs. |
| for (absl::string_view s : values) { |
| encoded_fields_.push_back(EncodeVarInt(s.size())); |
| encoded_fields_.push_back(std::string(s)); |
| } |
| } |
| |
| std::string EventEncoder::AsString() { |
| std::string encoded_event; |
| |
| if (batch_size_ == 0) { |
| RTC_DCHECK_EQ(encoded_fields_.size(), 0); |
| return encoded_event; |
| } |
| |
| // Compute size of encoded fields. |
| size_t total_fields_size = 0; |
| for (const std::string& s : encoded_fields_) { |
| total_fields_size += s.size(); |
| } |
| |
| constexpr size_t kExpectedMaxEventTagBytes = 4; |
| constexpr size_t kExpectedMaxSizeEncodingBytes = 4; |
| encoded_event.reserve(kExpectedMaxEventTagBytes + |
| kExpectedMaxSizeEncodingBytes + total_fields_size); |
| |
| // Encode event tag (event id and whether batch or single event). |
| encoded_event.append(EncodeVarInt(event_tag_)); |
| |
| // Encode size of the remaining fields. |
| encoded_event.append(EncodeVarInt(total_fields_size)); |
| |
| // Append encoded fields. |
| for (const std::string& s : encoded_fields_) { |
| encoded_event.append(s); |
| } |
| |
| return encoded_event; |
| } |
| |
| } // namespace webrtc |