blob: 809dcd76d10e1986ed7e98f5eb757cfcdfd012f7 [file] [log] [blame]
#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__