You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
6.4 KiB

// Copyright 2020 The Pigweed Authors
//
// 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
//
// https://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 "pw_protobuf/decoder.h"
#include <cstring>
#include "pw_varint/varint.h"
namespace pw::protobuf {
Status Decoder::Next() {
if (!previous_field_consumed_) {
if (Status status = SkipField(); !status.ok()) {
return status;
}
}
if (proto_.empty()) {
return Status::OutOfRange();
}
previous_field_consumed_ = false;
return FieldSize() == 0 ? Status::DataLoss() : OkStatus();
}
Status Decoder::SkipField() {
if (proto_.empty()) {
return Status::OutOfRange();
}
size_t bytes_to_skip = FieldSize();
if (bytes_to_skip == 0) {
return Status::DataLoss();
}
proto_ = proto_.subspan(bytes_to_skip);
return proto_.empty() ? Status::OutOfRange() : OkStatus();
}
uint32_t Decoder::FieldNumber() const {
uint64_t key;
varint::Decode(proto_, &key);
return key >> kFieldNumberShift;
}
Status Decoder::ReadUint32(uint32_t* out) {
uint64_t value = 0;
Status status = ReadUint64(&value);
if (!status.ok()) {
return status;
}
if (value > std::numeric_limits<uint32_t>::max()) {
return Status::OutOfRange();
}
*out = value;
return OkStatus();
}
Status Decoder::ReadSint32(int32_t* out) {
int64_t value = 0;
Status status = ReadSint64(&value);
if (!status.ok()) {
return status;
}
if (value > std::numeric_limits<int32_t>::max()) {
return Status::OutOfRange();
}
*out = value;
return OkStatus();
}
Status Decoder::ReadSint64(int64_t* out) {
uint64_t value = 0;
Status status = ReadUint64(&value);
if (!status.ok()) {
return status;
}
*out = varint::ZigZagDecode(value);
return OkStatus();
}
Status Decoder::ReadBool(bool* out) {
uint64_t value = 0;
Status status = ReadUint64(&value);
if (!status.ok()) {
return status;
}
*out = value;
return OkStatus();
}
Status Decoder::ReadString(std::string_view* out) {
std::span<const std::byte> bytes;
Status status = ReadDelimited(&bytes);
if (!status.ok()) {
return status;
}
*out = std::string_view(reinterpret_cast<const char*>(bytes.data()),
bytes.size());
return OkStatus();
}
size_t Decoder::FieldSize() const {
uint64_t key;
size_t key_size = varint::Decode(proto_, &key);
if (key_size == 0) {
return 0;
}
std::span<const std::byte> remainder = proto_.subspan(key_size);
WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
uint64_t value = 0;
size_t expected_size = 0;
switch (wire_type) {
case WireType::kVarint:
expected_size = varint::Decode(remainder, &value);
if (expected_size == 0) {
return 0;
}
break;
case WireType::kDelimited:
// Varint at cursor indicates size of the field.
expected_size = varint::Decode(remainder, &value);
if (expected_size == 0) {
return 0;
}
expected_size += value;
break;
case WireType::kFixed32:
expected_size = sizeof(uint32_t);
break;
case WireType::kFixed64:
expected_size = sizeof(uint64_t);
break;
}
if (remainder.size() < expected_size) {
return 0;
}
return key_size + expected_size;
}
Status Decoder::ConsumeKey(WireType expected_type) {
uint64_t key;
size_t bytes_read = varint::Decode(proto_, &key);
if (bytes_read == 0) {
return Status::FailedPrecondition();
}
WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
if (wire_type != expected_type) {
return Status::FailedPrecondition();
}
// Advance past the key.
proto_ = proto_.subspan(bytes_read);
return OkStatus();
}
Status Decoder::ReadVarint(uint64_t* out) {
if (Status status = ConsumeKey(WireType::kVarint); !status.ok()) {
return status;
}
size_t bytes_read = varint::Decode(proto_, out);
if (bytes_read == 0) {
return Status::DataLoss();
}
// Advance to the next field.
proto_ = proto_.subspan(bytes_read);
previous_field_consumed_ = true;
return OkStatus();
}
Status Decoder::ReadFixed(std::byte* out, size_t size) {
WireType expected_wire_type =
size == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
Status status = ConsumeKey(expected_wire_type);
if (!status.ok()) {
return status;
}
if (proto_.size() < size) {
return Status::DataLoss();
}
std::memcpy(out, proto_.data(), size);
proto_ = proto_.subspan(size);
previous_field_consumed_ = true;
return OkStatus();
}
Status Decoder::ReadDelimited(std::span<const std::byte>* out) {
Status status = ConsumeKey(WireType::kDelimited);
if (!status.ok()) {
return status;
}
uint64_t length;
size_t bytes_read = varint::Decode(proto_, &length);
if (bytes_read == 0) {
return Status::DataLoss();
}
proto_ = proto_.subspan(bytes_read);
if (proto_.size() < length) {
return Status::DataLoss();
}
*out = proto_.first(length);
proto_ = proto_.subspan(length);
previous_field_consumed_ = true;
return OkStatus();
}
Status CallbackDecoder::Decode(std::span<const std::byte> proto) {
if (handler_ == nullptr || state_ != kReady) {
return Status::FailedPrecondition();
}
state_ = kDecodeInProgress;
decoder_.Reset(proto);
// Iterate the proto, calling the handler with each field number.
while (state_ == kDecodeInProgress) {
if (Status status = decoder_.Next(); !status.ok()) {
if (status.IsOutOfRange()) {
// Reached the end of the proto.
break;
}
// Proto data is malformed.
return status;
}
Status status = handler_->ProcessField(*this, decoder_.FieldNumber());
if (!status.ok()) {
state_ = status.IsCancelled() ? kDecodeCancelled : kDecodeFailed;
return status;
}
// The callback function can modify the decoder's state; check that
// everything is still okay.
if (state_ == kDecodeFailed) {
break;
}
}
if (state_ != kDecodeInProgress) {
return Status::DataLoss();
}
state_ = kReady;
return OkStatus();
}
} // namespace pw::protobuf