| /* |
| * 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 "perfetto/base/logging.h" |
| |
| namespace protozero { |
| |
| using namespace proto_utils; |
| |
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
| #define BYTE_SWAP_TO_LE32(x) (x) |
| #define BYTE_SWAP_TO_LE64(x) (x) |
| #else |
| #error Unimplemented for big endian archs. |
| #endif |
| |
| ProtoDecoder::ProtoDecoder(const uint8_t* buffer, uint64_t length) |
| : buffer_(buffer), length_(length), current_position_(buffer) {} |
| |
| ProtoDecoder::Field ProtoDecoder::ReadField() { |
| Field 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* end = buffer_ + length_; |
| const uint8_t* pos = current_position_; |
| PERFETTO_DCHECK(pos >= buffer_); |
| PERFETTO_DCHECK(pos <= end); |
| |
| uint64_t raw_field_id = 0; |
| pos = ParseVarInt(pos, end, &raw_field_id); |
| |
| uint32_t field_id = static_cast<uint32_t>(raw_field_id >> kFieldTypeNumBits); |
| if (field_id == 0 || pos >= end) { |
| return field; |
| } |
| field.type = static_cast<FieldType>(raw_field_id & kFieldTypeMask); |
| |
| uint64_t field_intvalue = 0; |
| switch (field.type) { |
| case kFieldTypeFixed64: { |
| if (pos + sizeof(uint64_t) > end) { |
| return field; |
| } |
| memcpy(&field_intvalue, pos, sizeof(uint64_t)); |
| field.int_value = BYTE_SWAP_TO_LE64(field_intvalue); |
| pos += sizeof(uint64_t); |
| break; |
| } |
| case kFieldTypeFixed32: { |
| if (pos + sizeof(uint32_t) > end) { |
| return field; |
| } |
| uint32_t tmp; |
| memcpy(&tmp, pos, sizeof(uint32_t)); |
| field.int_value = BYTE_SWAP_TO_LE32(tmp); |
| pos += sizeof(uint32_t); |
| break; |
| } |
| case kFieldTypeVarInt: { |
| // We need to explicity check for zero to ensure that ParseVarInt doesn't |
| // return zero because of running out of space in the buffer. |
| if (*pos == 0) { |
| pos++; |
| field.int_value = 0; |
| } else { |
| pos = ParseVarInt(pos, end, &field.int_value); |
| |
| // The parsed value equalling zero 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 (field.int_value == 0) { |
| return field; |
| } |
| } |
| break; |
| } |
| case kFieldTypeLengthDelimited: { |
| // We need to explicity check for zero to ensure that ParseVarInt doesn't |
| // return zero because of running out of space in the buffer. |
| if (*pos == 0) { |
| field.length_value.data = ++pos; |
| field.length_value.length = 0; |
| } else { |
| pos = ParseVarInt(pos, end, &field_intvalue); |
| |
| // The parsed value equalling zero means ParseVarInt could not fully |
| // parse the number. This is because we are out of space in the buffer. |
| // Alternatively, we may not have space to fully read the length |
| // delimited field. Set the id to zero and return but don't update the |
| // offset so a future read can read this field. |
| if (field_intvalue == 0 || pos + field_intvalue > end) { |
| return field; |
| } |
| field.length_value.data = pos; |
| field.length_value.length = field_intvalue; |
| pos += field_intvalue; |
| } |
| break; |
| } |
| } |
| // Set the field id to make the returned value valid and update the current |
| // position in the buffer. |
| field.id = field_id; |
| current_position_ = pos; |
| return field; |
| } |
| |
| bool ProtoDecoder::IsEndOfBuffer() { |
| PERFETTO_DCHECK(current_position_ >= buffer_); |
| return length_ == static_cast<uint64_t>(current_position_ - buffer_); |
| } |
| |
| } // namespace protozero |