mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
270 lines
8.6 KiB
C++
270 lines
8.6 KiB
C++
#ifndef GOOGLE_PROTOBUF_CONFORMANCE_BINARY_WIREFORMAT_H__
|
|
#define GOOGLE_PROTOBUF_CONFORMANCE_BINARY_WIREFORMAT_H__
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "absl/strings/escaping.h"
|
|
#include "absl/strings/str_cat.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "google/protobuf/wire_format_lite.h"
|
|
|
|
// This file contains helpers for building arbitrary proto payloads in binary
|
|
// wire format. These are used by the conformance tests to construct test
|
|
// inputs (both valid and invalid) and expected outputs.
|
|
|
|
namespace google {
|
|
namespace protobuf {
|
|
namespace conformance {
|
|
|
|
// Create a relatively opaque wrapper around a string that represents a binary
|
|
// wire format. This allows us to provide custom printing for better
|
|
// debuggability, and also to provide better type safety than just using
|
|
// std::string.
|
|
// Example:
|
|
// Wire wire(Tag(1, WireType::kVarint), Varint(123))
|
|
class Wire {
|
|
public:
|
|
template <
|
|
typename... Args,
|
|
std::enable_if_t<!std::is_same_v<void(std::decay_t<Args>...), void(Wire)>,
|
|
int> = 0>
|
|
explicit Wire(Args&&... args)
|
|
: buf_(TypeCheckedCat(std::forward<Args>(args)...)) {}
|
|
|
|
Wire() = default;
|
|
Wire(const Wire& other) = default;
|
|
Wire(Wire&& other) = default;
|
|
Wire& operator=(const Wire& other) = default;
|
|
Wire& operator=(Wire&& other) = default;
|
|
|
|
bool operator==(const Wire& other) const { return buf_ == other.buf_; }
|
|
bool operator!=(const Wire& other) const { return !(*this == other); }
|
|
absl::string_view data() const { return buf_; }
|
|
std::string str() && { return std::move(buf_); }
|
|
size_t size() const { return buf_.size(); }
|
|
|
|
template <typename Sink>
|
|
friend void AbslStringify(Sink& sink, const Wire& wire) {
|
|
sink.Append(wire.data());
|
|
}
|
|
friend void PrintTo(const Wire& wire, std::ostream* os) {
|
|
*os << absl::CEscape(wire.data());
|
|
}
|
|
|
|
private:
|
|
template <typename... Args>
|
|
struct TypeCheckImpl : std::true_type {};
|
|
|
|
template <typename T, typename... Args>
|
|
struct TypeCheckImpl<T, Args...> : std::false_type {};
|
|
template <typename... Args>
|
|
struct TypeCheckImpl<Wire, Args...> : TypeCheckImpl<Args...> {};
|
|
template <typename... Args>
|
|
struct TypeCheckImpl<absl::string_view, Args...> : TypeCheckImpl<Args...> {};
|
|
template <typename... Args>
|
|
struct TypeCheckImpl<std::string, Args...> : TypeCheckImpl<Args...> {};
|
|
template <typename... Args>
|
|
struct TypeCheckImpl<const char*, Args...> : TypeCheckImpl<Args...> {};
|
|
|
|
template <typename... Args>
|
|
static std::string TypeCheckedCat(Args&&... args) {
|
|
static_assert(
|
|
TypeCheckImpl<std::decay_t<Args>...>::value,
|
|
"Wire format can only be constructed from Wire elements and strings.");
|
|
return absl::StrCat(std::forward<Args>(args)...);
|
|
}
|
|
|
|
std::string buf_;
|
|
};
|
|
|
|
// Expose our internal wire types for conformance tests.
|
|
enum class WireType : uint8_t {
|
|
kVarint = internal::WireFormatLite::WIRETYPE_VARINT,
|
|
kFixed32 = internal::WireFormatLite::WIRETYPE_FIXED32,
|
|
kFixed64 = internal::WireFormatLite::WIRETYPE_FIXED64,
|
|
kLengthPrefixed = internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED,
|
|
kStartGroup = internal::WireFormatLite::WIRETYPE_START_GROUP,
|
|
kEndGroup = internal::WireFormatLite::WIRETYPE_END_GROUP,
|
|
kInvalid = 6
|
|
};
|
|
|
|
template <typename Sink>
|
|
void AbslStringify(Sink& sink, WireType wire_type) {
|
|
switch (wire_type) {
|
|
case WireType::kVarint:
|
|
sink.Append("Varint");
|
|
break;
|
|
case WireType::kFixed32:
|
|
sink.Append("Fixed32");
|
|
break;
|
|
case WireType::kFixed64:
|
|
sink.Append("Fixed64");
|
|
break;
|
|
case WireType::kLengthPrefixed:
|
|
sink.Append("LengthPrefixed");
|
|
break;
|
|
case WireType::kStartGroup:
|
|
sink.Append("StartGroup");
|
|
break;
|
|
case WireType::kEndGroup:
|
|
sink.Append("EndGroup");
|
|
break;
|
|
case WireType::kInvalid:
|
|
sink.Append("Invalid");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******** Partial data helpers ********/
|
|
|
|
// The following functions are helpers for building wire format elements, but
|
|
// don't individually produce a valid wire format. They must be combined into
|
|
// tag/value pairs using Wire() or one of the *Field() functions below.
|
|
|
|
// Encodes a field tag for a given field number and wire type.
|
|
Wire Tag(uint32_t fieldnum, WireType wire_type);
|
|
|
|
// Encodes a base-128 variable-width integer.
|
|
Wire Varint(uint64_t x);
|
|
|
|
// Encodes a varint that is |extra| bytes longer than it needs to be, but
|
|
// still valid. Varints longer than 10 bytes are not valid, and will assert.
|
|
Wire LongVarint(uint64_t x, int extra);
|
|
|
|
// Encodes a zig-zag encoded 32-bit signed varint.
|
|
Wire SInt32(int32_t x);
|
|
|
|
// Encodes a zig-zag encoded 64-bit signed varint.
|
|
Wire SInt64(int64_t x);
|
|
|
|
// Encodes a fixed-width 32-bit integer.
|
|
Wire Fixed32(uint32_t x);
|
|
|
|
// Encodes a fixed-width 64-bit integer.
|
|
Wire Fixed64(uint64_t x);
|
|
|
|
// Encodes a float.
|
|
Wire Float(float f);
|
|
|
|
// Encodes a double.
|
|
Wire Double(double d);
|
|
|
|
// Encodes a string with a length prefix.
|
|
// e.g. LengthPrefixed("foo") for string data
|
|
// e.g. LengthPrefixed(Wire(...)) for sub-message data
|
|
Wire LengthPrefixed(absl::string_view data);
|
|
inline Wire LengthPrefixed(const Wire& data) {
|
|
return LengthPrefixed(data.data());
|
|
}
|
|
|
|
// Encodes packed repeated data.
|
|
// e.g. Packed(Varint<int>, {1, 2, 3})
|
|
namespace internal {
|
|
template <typename Func, typename Container>
|
|
Wire PackedImpl(Func func, const Container& container) {
|
|
std::string buf = Varint(container.size()).str();
|
|
for (auto x : container) {
|
|
absl::StrAppend(&buf, func(x).str());
|
|
}
|
|
return Wire(buf);
|
|
}
|
|
} // namespace internal
|
|
|
|
template <typename Func, typename T>
|
|
Wire Packed(Func func, std::initializer_list<T> container) {
|
|
return internal::PackedImpl(func, container);
|
|
}
|
|
template <typename Func, typename Container>
|
|
Wire Packed(Func func, const Container& container) {
|
|
return internal::PackedImpl(func, container);
|
|
}
|
|
|
|
// Overloads to avoid forcing sign/size conversion warnings on call-sites.
|
|
template <typename T>
|
|
Wire Varint(T x) {
|
|
return Varint(static_cast<uint64_t>(x));
|
|
}
|
|
template <typename T>
|
|
Wire LongVarint(T x, int extra) {
|
|
return LongVarint(static_cast<uint64_t>(x), extra);
|
|
}
|
|
template <typename T>
|
|
Wire Fixed32(T x) {
|
|
return Fixed32(static_cast<uint32_t>(x));
|
|
}
|
|
template <typename T>
|
|
Wire Fixed64(T x) {
|
|
return Fixed64(static_cast<uint64_t>(x));
|
|
}
|
|
|
|
/******** Field helpers ********/
|
|
|
|
// The following functions encode an entire field, including both a tag and
|
|
// value. That means that the output of all of these functions *is* valid wire
|
|
// format, although it will typically be composed with additional elements
|
|
// using Wire(...). These are really just meant as lightweight helpers to make
|
|
// call-sites a little less verbose.
|
|
|
|
template <typename T>
|
|
Wire VarintField(uint32_t fieldnum, T value) {
|
|
return Wire(Tag(fieldnum, WireType::kVarint), Varint(value));
|
|
}
|
|
|
|
template <typename T>
|
|
Wire LongVarintField(uint32_t fieldnum, T value, int extra) {
|
|
return Wire(Tag(fieldnum, WireType::kVarint), LongVarint(value, extra));
|
|
}
|
|
|
|
inline Wire SInt32Field(uint32_t fieldnum, int32_t value) {
|
|
return Wire(Tag(fieldnum, WireType::kVarint), SInt32(value));
|
|
}
|
|
|
|
inline Wire SInt64Field(uint32_t fieldnum, int64_t value) {
|
|
return Wire(Tag(fieldnum, WireType::kVarint), SInt64(value));
|
|
}
|
|
template <typename T>
|
|
Wire Fixed32Field(uint32_t fieldnum, T value) {
|
|
return Wire(Tag(fieldnum, WireType::kFixed32), Fixed32(value));
|
|
}
|
|
template <typename T>
|
|
Wire Fixed64Field(uint32_t fieldnum, T value) {
|
|
return Wire(Tag(fieldnum, WireType::kFixed64), Fixed64(value));
|
|
}
|
|
inline Wire FloatField(uint32_t fieldnum, float value) {
|
|
return Wire(Tag(fieldnum, WireType::kFixed32), Float(value));
|
|
}
|
|
inline Wire DoubleField(uint32_t fieldnum, double value) {
|
|
return Wire(Tag(fieldnum, WireType::kFixed64), Double(value));
|
|
}
|
|
template <typename Func, typename T>
|
|
Wire PackedField(uint32_t fieldnum, Func func,
|
|
std::initializer_list<T> container) {
|
|
return Wire(Tag(fieldnum, WireType::kLengthPrefixed),
|
|
Packed(func, std::move(container)));
|
|
}
|
|
template <typename Func, typename Container>
|
|
Wire PackedField(uint32_t fieldnum, Func func, const Container& container) {
|
|
return Wire(Tag(fieldnum, WireType::kLengthPrefixed),
|
|
Packed(func, container));
|
|
}
|
|
template <typename T>
|
|
inline Wire LengthPrefixedField(uint32_t fieldnum, T content) {
|
|
return Wire(Tag(fieldnum, WireType::kLengthPrefixed),
|
|
LengthPrefixed(content));
|
|
}
|
|
inline Wire DelimitedField(uint32_t fieldnum, const Wire& content) {
|
|
return Wire(Tag(fieldnum, WireType::kStartGroup), content.data(),
|
|
Tag(fieldnum, WireType::kEndGroup));
|
|
}
|
|
|
|
} // namespace conformance
|
|
} // namespace protobuf
|
|
} // namespace google
|
|
|
|
#endif // GOOGLE_PROTOBUF_CONFORMANCE_BINARY_WIREFORMAT_H__
|