mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
This adds support for two serialization formats for complex data types: - Protobuf for complex objects with variable length internals that need forward and backward wire compatibility (lower speed, more flexible) - Raw struct (ByteBuffer-style) for fixed-length objects (higher speed, less flexible) Deserialization can be done either by creating a new object (for immutable objects) or overwriting the contents of an existing object (for mutable objects). Implementing classes should provide inner classes that implement the Protobuf or Struct interface (in Java) or specialize the wpi::Protobuf or wpi::Struct struct (in C++). It is possible for classes to implement both. If the class itself does not implement serialization, it's possible for third parties/users to provide an implementation instead. Uses the Google protobuf implementation for C++ and the QuickBuffers alternative protobuf implementation for Java.
195 lines
6.7 KiB
C++
195 lines
6.7 KiB
C++
// 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 <fmt/format.h>
|
|
#include <google/protobuf/descriptor.h>
|
|
#include <google/protobuf/descriptor.pb.h>
|
|
#include <google/protobuf/io/zero_copy_stream.h>
|
|
#include <google/protobuf/message.h>
|
|
|
|
#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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<int>::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<int>::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<const uint8_t> data) {
|
|
return msg->ParseFromArray(data.data(), data.size());
|
|
}
|
|
|
|
bool detail::SerializeProtobuf(wpi::SmallVectorImpl<uint8_t>& out,
|
|
const google::protobuf::Message& msg) {
|
|
SmallVectorOutputStream stream{out};
|
|
return msg.SerializeToZeroCopyStream(&stream);
|
|
}
|
|
|
|
bool detail::SerializeProtobuf(std::vector<uint8_t>& 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<bool(std::string_view typeString)> exists,
|
|
function_ref<void(std::string_view typeString,
|
|
std::span<const uint8_t> 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<FileDescriptorProto>(arena);
|
|
}
|
|
(*descproto)->Clear();
|
|
desc->CopyTo(*descproto);
|
|
SmallVector<uint8_t, 128> buf;
|
|
detail::SerializeProtobuf(buf, **descproto);
|
|
fn(name, buf);
|
|
}
|
|
|
|
void detail::ForEachProtobufDescriptor(
|
|
const google::protobuf::Message& msg,
|
|
function_ref<bool(std::string_view filename)> exists,
|
|
function_ref<void(std::string_view filename,
|
|
std::span<const uint8_t> descriptor)>
|
|
fn) {
|
|
FileDescriptorProto* descproto = nullptr;
|
|
ForEachProtobufDescriptorImpl(msg.GetDescriptor()->file(), exists, fn,
|
|
msg.GetArena(), &descproto);
|
|
if (descproto && !msg.GetArena()) {
|
|
delete descproto;
|
|
}
|
|
}
|