// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "wpi/protobuf/Protobuf.h" #include #include #include #include #include #include "wpi/SmallVector.h" using namespace wpi; using google::protobuf::Arena; using google::protobuf::FileDescriptor; using google::protobuf::FileDescriptorProto; namespace { class VectorOutputStream final : public google::protobuf::io::ZeroCopyOutputStream { public: // Create a StringOutputStream which appends bytes to the given string. // The string remains property of the caller, but it is mutated in arbitrary // ways and MUST NOT be accessed in any way until you're done with the // stream. Either be sure there's no further usage, or (safest) destroy the // stream before using the contents. // // Hint: If you call target->reserve(n) before creating the stream, // the first call to Next() will return at least n bytes of buffer // space. explicit VectorOutputStream(std::vector& target) : target_{target} {} VectorOutputStream(const VectorOutputStream&) = delete; ~VectorOutputStream() override = default; VectorOutputStream& operator=(const VectorOutputStream&) = delete; // implements ZeroCopyOutputStream --------------------------------- bool Next(void** data, int* size) override; void BackUp(int count) override { target_.resize(target_.size() - count); } int64_t ByteCount() const override { return target_.size(); } private: static constexpr size_t kMinimumSize = 16; std::vector& target_; }; class SmallVectorOutputStream final : public google::protobuf::io::ZeroCopyOutputStream { public: // Create a StringOutputStream which appends bytes to the given string. // The string remains property of the caller, but it is mutated in arbitrary // ways and MUST NOT be accessed in any way until you're done with the // stream. Either be sure there's no further usage, or (safest) destroy the // stream before using the contents. // // Hint: If you call target->reserve(n) before creating the stream, // the first call to Next() will return at least n bytes of buffer // space. explicit SmallVectorOutputStream(wpi::SmallVectorImpl& target) : target_{target} { target.resize(0); } SmallVectorOutputStream(const SmallVectorOutputStream&) = delete; ~SmallVectorOutputStream() override = default; SmallVectorOutputStream& operator=(const SmallVectorOutputStream&) = delete; // implements ZeroCopyOutputStream --------------------------------- bool Next(void** data, int* size) override; void BackUp(int count) override { target_.resize(target_.size() - count); } int64_t ByteCount() const override { return target_.size(); } private: static constexpr size_t kMinimumSize = 16; wpi::SmallVectorImpl& target_; }; } // namespace bool VectorOutputStream::Next(void** data, int* size) { size_t old_size = target_.size(); // Grow the string. size_t new_size; if (old_size < target_.capacity()) { // Resize to match its capacity, since we can get away // without a memory allocation this way. new_size = target_.capacity(); } else { // Size has reached capacity, try to double it. new_size = old_size * 2; } // Avoid integer overflow in returned '*size'. new_size = (std::min)(new_size, old_size + (std::numeric_limits::max)()); // Increase the size, also make sure that it is at least kMinimumSize. target_.resize((std::max)(new_size, kMinimumSize)); *data = target_.data() + old_size; *size = target_.size() - old_size; return true; } bool SmallVectorOutputStream::Next(void** data, int* size) { size_t old_size = target_.size(); // Grow the string. size_t new_size; if (old_size < target_.capacity()) { // Resize to match its capacity, since we can get away // without a memory allocation this way. new_size = target_.capacity(); } else { // Size has reached capacity, try to double it. new_size = old_size * 2; } // Avoid integer overflow in returned '*size'. new_size = (std::min)(new_size, old_size + (std::numeric_limits::max)()); // Increase the size, also make sure that it is at least kMinimumSize. target_.resize_for_overwrite((std::max)(new_size, kMinimumSize)); *data = target_.data() + old_size; *size = target_.size() - old_size; return true; } void detail::DeleteProtobuf(google::protobuf::Message* msg) { if (msg && !msg->GetArena()) { delete msg; } } bool detail::ParseProtobuf(google::protobuf::Message* msg, std::span data) { return msg->ParseFromArray(data.data(), data.size()); } bool detail::SerializeProtobuf(wpi::SmallVectorImpl& out, const google::protobuf::Message& msg) { SmallVectorOutputStream stream{out}; return msg.SerializeToZeroCopyStream(&stream); } bool detail::SerializeProtobuf(std::vector& out, const google::protobuf::Message& msg) { VectorOutputStream stream{out}; return msg.SerializeToZeroCopyStream(&stream); } std::string detail::GetTypeString(const google::protobuf::Message& msg) { return fmt::format("proto:{}", msg.GetDescriptor()->full_name()); } static void ForEachProtobufDescriptorImpl( const FileDescriptor* desc, function_ref exists, function_ref schema)> fn, Arena* arena, FileDescriptorProto** descproto) { std::string name = fmt::format("proto:{}", desc->name()); if (exists(name)) { return; } for (int i = 0, ndep = desc->dependency_count(); i < ndep; ++i) { ForEachProtobufDescriptorImpl(desc->dependency(i), exists, fn, arena, descproto); } if (!*descproto) { *descproto = Arena::CreateMessage(arena); } (*descproto)->Clear(); desc->CopyTo(*descproto); SmallVector buf; detail::SerializeProtobuf(buf, **descproto); fn(name, buf); } void detail::ForEachProtobufDescriptor( const google::protobuf::Message& msg, function_ref exists, function_ref descriptor)> fn) { FileDescriptorProto* descproto = nullptr; ForEachProtobufDescriptorImpl(msg.GetDescriptor()->file(), exists, fn, msg.GetArena(), &descproto); if (descproto && !msg.GetArena()) { delete descproto; } }