| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "perfetto/protozero/proto_decoder.h" |
| |
| #include <string.h> |
| #include <limits> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/utils.h" |
| #include "perfetto/protozero/proto_utils.h" |
| |
| namespace protozero { |
| |
| using namespace proto_utils; |
| |
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ |
| #error Unimplemented for big endian archs. |
| #endif |
| |
| namespace { |
| |
| struct ParseFieldResult { |
| const uint8_t* next; |
| Field field; |
| }; |
| |
| // Parses one field and returns the field itself and a pointer to the next |
| // field to parse. If parsing fails, the returned |next| == |buffer|. |
| PERFETTO_ALWAYS_INLINE ParseFieldResult |
| ParseOneField(const uint8_t* const buffer, const uint8_t* const end) { |
| ParseFieldResult res{buffer, Field{}}; |
| |
| // The first byte of a proto field is structured as follows: |
| // The least 3 significant bits determine the field type. |
| // The most 5 significant bits determine the field id. If MSB == 1, the |
| // field id continues on the next bytes following the VarInt encoding. |
| const uint8_t kFieldTypeNumBits = 3; |
| const uint64_t kFieldTypeMask = (1 << kFieldTypeNumBits) - 1; // 0000 0111; |
| const uint8_t* pos = buffer; |
| |
| // If we've already hit the end, just return an invalid field. |
| if (PERFETTO_UNLIKELY(pos >= end)) |
| return res; |
| |
| uint64_t preamble = 0; |
| if (PERFETTO_LIKELY(*pos < 0x80)) { // Fastpath for fields with ID < 16. |
| preamble = *(pos++); |
| } else { |
| const uint8_t* next = ParseVarInt(pos, end, &preamble); |
| if (PERFETTO_UNLIKELY(pos == next)) |
| return res; |
| pos = next; |
| } |
| |
| uint32_t field_id = static_cast<uint32_t>(preamble >> kFieldTypeNumBits); |
| if (field_id == 0 || pos >= end) |
| return res; |
| |
| auto field_type = static_cast<uint8_t>(preamble & kFieldTypeMask); |
| const uint8_t* new_pos = pos; |
| uint64_t int_value = 0; |
| uint32_t size = 0; |
| |
| switch (field_type) { |
| case static_cast<uint8_t>(ProtoWireType::kVarInt): { |
| new_pos = ParseVarInt(pos, end, &int_value); |
| |
| // new_pos not being greater than pos means ParseVarInt could not fully |
| // parse the number. This is because we are out of space in the buffer. |
| // Set the id to zero and return but don't update the offset so a future |
| // read can read this field. |
| if (PERFETTO_UNLIKELY(new_pos == pos)) |
| return res; |
| |
| break; |
| } |
| |
| case static_cast<uint8_t>(ProtoWireType::kLengthDelimited): { |
| uint64_t payload_length; |
| new_pos = ParseVarInt(pos, end, &payload_length); |
| if (PERFETTO_UNLIKELY(new_pos == pos)) |
| return res; |
| |
| // ParseVarInt guarantees that |new_pos| <= |end| when it succeeds; |
| if (payload_length > static_cast<uint64_t>(end - new_pos)) |
| return res; |
| |
| // If the message is larger than 256 MiB silently skip it. |
| if (PERFETTO_LIKELY(payload_length <= proto_utils::kMaxMessageLength)) { |
| const uintptr_t payload_start = reinterpret_cast<uintptr_t>(new_pos); |
| int_value = payload_start; |
| size = static_cast<uint32_t>(payload_length); |
| } |
| |
| new_pos += payload_length; |
| break; |
| } |
| |
| case static_cast<uint8_t>(ProtoWireType::kFixed64): { |
| new_pos = pos + sizeof(uint64_t); |
| if (PERFETTO_UNLIKELY(new_pos > end)) |
| return res; |
| memcpy(&int_value, pos, sizeof(uint64_t)); |
| break; |
| } |
| |
| case static_cast<uint8_t>(ProtoWireType::kFixed32): { |
| new_pos = pos + sizeof(uint32_t); |
| if (PERFETTO_UNLIKELY(new_pos > end)) |
| return res; |
| memcpy(&int_value, pos, sizeof(uint32_t)); |
| break; |
| } |
| |
| default: |
| PERFETTO_DLOG("Invalid proto field type: %u", field_type); |
| return res; |
| } |
| |
| if (PERFETTO_UNLIKELY(field_id > std::numeric_limits<uint16_t>::max())) { |
| // PERFETTO_DFATAL("Cannot parse proto field ids > 0xFFFF"); |
| return res; |
| } |
| |
| res.field.initialize(static_cast<uint16_t>(field_id), field_type, int_value, |
| size); |
| res.next = new_pos; |
| return res; |
| } |
| |
| } // namespace |
| |
| Field ProtoDecoder::FindField(uint32_t field_id) { |
| Field res{}; |
| auto old_position = read_ptr_; |
| read_ptr_ = begin_; |
| for (auto f = ReadField(); f.valid(); f = ReadField()) { |
| if (f.id() == field_id) { |
| res = f; |
| break; |
| } |
| } |
| read_ptr_ = old_position; |
| return res; |
| } |
| |
| PERFETTO_ALWAYS_INLINE |
| Field ProtoDecoder::ReadField() { |
| ParseFieldResult res = ParseOneField(read_ptr_, end_); |
| read_ptr_ = res.next; |
| return res.field; |
| } |
| |
| void TypedProtoDecoderBase::ParseAllFields() { |
| const uint8_t* cur = begin_; |
| ParseFieldResult res; |
| for (;;) { |
| res = ParseOneField(cur, end_); |
| if (res.next == cur) |
| break; |
| PERFETTO_DCHECK(res.field.valid()); |
| cur = res.next; |
| |
| auto field_id = res.field.id(); |
| if (PERFETTO_UNLIKELY(field_id >= num_fields_)) |
| continue; |
| |
| Field* fld = &fields_[field_id]; |
| if (PERFETTO_LIKELY(!fld->valid())) { |
| // This is the first time we see this field. |
| *fld = std::move(res.field); |
| } else { |
| // Repeated field case. |
| // In this case we need to: |
| // 1. Append the last value of the field to end of the repeated field |
| // storage. |
| // 2. Replace the default instance at offset |field_id| with the current |
| // value. This is because in case of repeated field a call to Get(X) is |
| // supposed to return the last value of X, not the first one. |
| // This is so that the RepeatedFieldIterator will iterate in the right |
| // order, see comments on RepeatedFieldIterator. |
| if (PERFETTO_UNLIKELY(size_ >= capacity_)) { |
| ExpandHeapStorage(); |
| // ExpandHeapStorage moves fields_ so we need to update the ptr to fld: |
| fld = &fields_[field_id]; |
| PERFETTO_DCHECK(size_ < capacity_); |
| } |
| fields_[size_++] = *fld; |
| *fld = std::move(res.field); |
| } |
| } |
| read_ptr_ = res.next; |
| } |
| |
| void TypedProtoDecoderBase::ExpandHeapStorage() { |
| uint32_t new_capacity = capacity_ * 2; |
| PERFETTO_CHECK(new_capacity > size_); |
| std::unique_ptr<Field[]> new_storage(new Field[new_capacity]); |
| |
| static_assert(std::is_trivially_copyable<Field>::value, |
| "Field must be trivially copyable"); |
| memcpy(&new_storage[0], fields_, sizeof(Field) * size_); |
| |
| heap_storage_ = std::move(new_storage); |
| fields_ = &heap_storage_[0]; |
| capacity_ = new_capacity; |
| } |
| |
| } // namespace protozero |