add basic discord support
This commit is contained in:
21
DPP/mlspp/lib/tls_syntax/CMakeLists.txt
Executable file
21
DPP/mlspp/lib/tls_syntax/CMakeLists.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
set(CURRENT_LIB_NAME tls_syntax)
|
||||
|
||||
###
|
||||
### Library Config
|
||||
###
|
||||
|
||||
file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
|
||||
file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
||||
|
||||
add_library(${CURRENT_LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES})
|
||||
target_include_directories(${CURRENT_LIB_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
|
||||
)
|
||||
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/bytes/include")
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/hpke/include")
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/mls_vectors/include")
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/tls_syntax/include")
|
||||
61
DPP/mlspp/lib/tls_syntax/include/tls/compat.h
Executable file
61
DPP/mlspp/lib/tls_syntax/include/tls/compat.h
Executable file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef VARIANT_COMPAT
|
||||
#include <variant.hpp>
|
||||
#else
|
||||
#include <variant>
|
||||
#endif // VARIANT_COMPAT
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
namespace var = std;
|
||||
|
||||
// In a similar vein, we provide our own safe accessors for std::optional, since
|
||||
// std::optional::value() is not available on macOS 10.11.
|
||||
namespace opt {
|
||||
|
||||
template<typename T>
|
||||
T&
|
||||
get(std::optional<T>& opt)
|
||||
{
|
||||
if (!opt) {
|
||||
throw std::runtime_error("bad_optional_access");
|
||||
}
|
||||
return *opt;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T&
|
||||
get(const std::optional<T>& opt)
|
||||
{
|
||||
if (!opt) {
|
||||
throw std::runtime_error("bad_optional_access");
|
||||
}
|
||||
return *opt;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T&&
|
||||
get(std::optional<T>&& opt)
|
||||
{
|
||||
if (!opt) {
|
||||
throw std::runtime_error("bad_optional_access");
|
||||
}
|
||||
return std::move(*opt);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T&&
|
||||
get(const std::optional<T>&& opt)
|
||||
{
|
||||
if (!opt) {
|
||||
throw std::runtime_error("bad_optional_access");
|
||||
}
|
||||
return std::move(*opt);
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace mlspp::tls
|
||||
569
DPP/mlspp/lib/tls_syntax/include/tls/tls_syntax.h
Executable file
569
DPP/mlspp/lib/tls_syntax/include/tls/tls_syntax.h
Executable file
@@ -0,0 +1,569 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <tls/compat.h>
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
// For indicating no min or max in vector definitions
|
||||
const size_t none = std::numeric_limits<size_t>::max();
|
||||
|
||||
class WriteError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class ReadError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
///
|
||||
/// Declarations of Streams and Traits
|
||||
///
|
||||
|
||||
class ostream
|
||||
{
|
||||
public:
|
||||
static const size_t none = std::numeric_limits<size_t>::max();
|
||||
|
||||
void write_raw(const std::vector<uint8_t>& bytes);
|
||||
|
||||
const std::vector<uint8_t>& bytes() const { return _buffer; }
|
||||
size_t size() const { return _buffer.size(); }
|
||||
bool empty() const { return _buffer.empty(); }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> _buffer;
|
||||
ostream& write_uint(uint64_t value, int length);
|
||||
|
||||
friend ostream& operator<<(ostream& out, bool data);
|
||||
friend ostream& operator<<(ostream& out, uint8_t data);
|
||||
friend ostream& operator<<(ostream& out, uint16_t data);
|
||||
friend ostream& operator<<(ostream& out, uint32_t data);
|
||||
friend ostream& operator<<(ostream& out, uint64_t data);
|
||||
|
||||
template<typename T>
|
||||
friend ostream& operator<<(ostream& out, const std::vector<T>& data);
|
||||
|
||||
friend struct varint;
|
||||
};
|
||||
|
||||
class istream
|
||||
{
|
||||
public:
|
||||
istream(const std::vector<uint8_t>& data)
|
||||
: _buffer(data)
|
||||
{
|
||||
// So that we can use the constant-time pop_back
|
||||
std::reverse(_buffer.begin(), _buffer.end());
|
||||
}
|
||||
|
||||
size_t size() const { return _buffer.size(); }
|
||||
bool empty() const { return _buffer.empty(); }
|
||||
|
||||
std::vector<uint8_t> bytes()
|
||||
{
|
||||
auto bytes = _buffer;
|
||||
std::reverse(bytes.begin(), bytes.end());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
istream() {}
|
||||
std::vector<uint8_t> _buffer;
|
||||
uint8_t next();
|
||||
|
||||
template<typename T>
|
||||
istream& read_uint(T& data, size_t length)
|
||||
{
|
||||
uint64_t value = 0;
|
||||
for (size_t i = 0; i < length; i += 1) {
|
||||
value = (value << unsigned(8)) + next();
|
||||
}
|
||||
data = static_cast<T>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend istream& operator>>(istream& in, bool& data);
|
||||
friend istream& operator>>(istream& in, uint8_t& data);
|
||||
friend istream& operator>>(istream& in, uint16_t& data);
|
||||
friend istream& operator>>(istream& in, uint32_t& data);
|
||||
friend istream& operator>>(istream& in, uint64_t& data);
|
||||
|
||||
template<typename T>
|
||||
friend istream& operator>>(istream& in, std::vector<T>& data);
|
||||
|
||||
friend struct varint;
|
||||
};
|
||||
|
||||
// Traits must have static encode and decode methods, of the following form:
|
||||
//
|
||||
// static ostream& encode(ostream& str, const T& val);
|
||||
// static istream& decode(istream& str, T& val);
|
||||
//
|
||||
// Trait types will never be constructed; only these static methods are used.
|
||||
// The value arguments to encode and decode can be as strict or as loose as
|
||||
// desired.
|
||||
//
|
||||
// Ultimately, all interesting encoding should be done through traits.
|
||||
//
|
||||
// * vectors
|
||||
// * variants
|
||||
// * varints
|
||||
|
||||
struct pass
|
||||
{
|
||||
template<typename T>
|
||||
static ostream& encode(ostream& str, const T& val);
|
||||
|
||||
template<typename T>
|
||||
static istream& decode(istream& str, T& val);
|
||||
};
|
||||
|
||||
template<typename Ts>
|
||||
struct variant
|
||||
{
|
||||
template<typename... Tp>
|
||||
static inline Ts type(const var::variant<Tp...>& data);
|
||||
|
||||
template<typename... Tp>
|
||||
static ostream& encode(ostream& str, const var::variant<Tp...>& data);
|
||||
|
||||
template<size_t I = 0, typename Te, typename... Tp>
|
||||
static inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
read_variant(istream&, Te, var::variant<Tp...>&);
|
||||
|
||||
template<size_t I = 0, typename Te, typename... Tp>
|
||||
static inline typename std::enable_if <
|
||||
I<sizeof...(Tp), void>::type read_variant(istream& str,
|
||||
Te target_type,
|
||||
var::variant<Tp...>& v);
|
||||
|
||||
template<typename... Tp>
|
||||
static istream& decode(istream& str, var::variant<Tp...>& data);
|
||||
};
|
||||
|
||||
struct varint
|
||||
{
|
||||
static ostream& encode(ostream& str, const uint64_t& val);
|
||||
static istream& decode(istream& str, uint64_t& val);
|
||||
};
|
||||
|
||||
///
|
||||
/// Writer implementations
|
||||
///
|
||||
|
||||
// Primitive writers defined in .cpp file
|
||||
|
||||
// Array writer
|
||||
template<typename T, size_t N>
|
||||
ostream&
|
||||
operator<<(ostream& out, const std::array<T, N>& data)
|
||||
{
|
||||
for (const auto& item : data) {
|
||||
out << item;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Optional writer
|
||||
template<typename T>
|
||||
ostream&
|
||||
operator<<(ostream& out, const std::optional<T>& opt)
|
||||
{
|
||||
if (!opt) {
|
||||
return out << uint8_t(0);
|
||||
}
|
||||
|
||||
return out << uint8_t(1) << opt::get(opt);
|
||||
}
|
||||
|
||||
// Enum writer
|
||||
template<typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
|
||||
ostream&
|
||||
operator<<(ostream& str, const T& val)
|
||||
{
|
||||
auto u = static_cast<std::underlying_type_t<T>>(val);
|
||||
return str << u;
|
||||
}
|
||||
|
||||
// Vector writer
|
||||
template<typename T>
|
||||
ostream&
|
||||
operator<<(ostream& str, const std::vector<T>& vec)
|
||||
{
|
||||
// Pre-encode contents
|
||||
ostream temp;
|
||||
for (const auto& item : vec) {
|
||||
temp << item;
|
||||
}
|
||||
|
||||
// Write the encoded length, then the pre-encoded data
|
||||
varint::encode(str, temp._buffer.size());
|
||||
str.write_raw(temp.bytes());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
///
|
||||
/// Reader implementations
|
||||
///
|
||||
|
||||
// Primitive type readers defined in .cpp file
|
||||
|
||||
// Array reader
|
||||
template<typename T, size_t N>
|
||||
istream&
|
||||
operator>>(istream& in, std::array<T, N>& data)
|
||||
{
|
||||
for (auto& item : data) {
|
||||
in >> item;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
// Optional reader
|
||||
template<typename T>
|
||||
istream&
|
||||
operator>>(istream& in, std::optional<T>& opt)
|
||||
{
|
||||
uint8_t present = 0;
|
||||
in >> present;
|
||||
|
||||
switch (present) {
|
||||
case 0:
|
||||
opt.reset();
|
||||
return in;
|
||||
|
||||
case 1:
|
||||
opt.emplace();
|
||||
return in >> opt::get(opt);
|
||||
|
||||
default:
|
||||
throw std::invalid_argument("Malformed optional");
|
||||
}
|
||||
}
|
||||
|
||||
// Enum reader
|
||||
// XXX(rlb): It would be nice if this could enforce that the values are valid,
|
||||
// but C++ doesn't seem to have that ability. When used as a tag for variants,
|
||||
// the variant reader will enforce, at least.
|
||||
template<typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
|
||||
istream&
|
||||
operator>>(istream& str, T& val)
|
||||
{
|
||||
std::underlying_type_t<T> u;
|
||||
str >> u;
|
||||
val = static_cast<T>(u);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Vector reader
|
||||
template<typename T>
|
||||
istream&
|
||||
operator>>(istream& str, std::vector<T>& vec)
|
||||
{
|
||||
// Read the encoded data size
|
||||
auto size = uint64_t(0);
|
||||
varint::decode(str, size);
|
||||
if (size > str._buffer.size()) {
|
||||
throw ReadError("Vector is longer than remaining data");
|
||||
}
|
||||
|
||||
// Read the elements of the vector
|
||||
// NB: Remember that we store the vector in reverse order
|
||||
// NB: This requires that T be default-constructible
|
||||
istream r;
|
||||
r._buffer =
|
||||
std::vector<uint8_t>{ str._buffer.end() - size, str._buffer.end() };
|
||||
|
||||
vec.clear();
|
||||
while (r._buffer.size() > 0) {
|
||||
vec.emplace_back();
|
||||
r >> vec.back();
|
||||
}
|
||||
|
||||
// Truncate the primary buffer
|
||||
str._buffer.erase(str._buffer.end() - size, str._buffer.end());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// Abbreviations
|
||||
template<typename T>
|
||||
std::vector<uint8_t>
|
||||
marshal(const T& value)
|
||||
{
|
||||
ostream w;
|
||||
w << value;
|
||||
return w.bytes();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
unmarshal(const std::vector<uint8_t>& data, T& value)
|
||||
{
|
||||
istream r(data);
|
||||
r >> value;
|
||||
}
|
||||
|
||||
template<typename T, typename... Tp>
|
||||
T
|
||||
get(const std::vector<uint8_t>& data, Tp... args)
|
||||
{
|
||||
T value(args...);
|
||||
unmarshal(data, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Use this macro to define struct serialization with minimal boilerplate
|
||||
#define TLS_SERIALIZABLE(...) \
|
||||
static const bool _tls_serializable = true; \
|
||||
auto _tls_fields_r() \
|
||||
{ \
|
||||
return std::forward_as_tuple(__VA_ARGS__); \
|
||||
} \
|
||||
auto _tls_fields_w() const \
|
||||
{ \
|
||||
return std::forward_as_tuple(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
// If your struct contains nontrivial members (e.g., vectors), use this to
|
||||
// define traits for them.
|
||||
#define TLS_TRAITS(...) \
|
||||
static const bool _tls_has_traits = true; \
|
||||
using _tls_traits = std::tuple<__VA_ARGS__>;
|
||||
|
||||
template<typename T>
|
||||
struct is_serializable
|
||||
{
|
||||
template<typename U>
|
||||
static std::true_type test(decltype(U::_tls_serializable));
|
||||
|
||||
template<typename U>
|
||||
static std::false_type test(...);
|
||||
|
||||
static const bool value = decltype(test<T>(true))::value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct has_traits
|
||||
{
|
||||
template<typename U>
|
||||
static std::true_type test(decltype(U::_tls_has_traits));
|
||||
|
||||
template<typename U>
|
||||
static std::false_type test(...);
|
||||
|
||||
static const bool value = decltype(test<T>(true))::value;
|
||||
};
|
||||
|
||||
///
|
||||
/// Trait implementations
|
||||
///
|
||||
|
||||
// Pass-through (normal encoding/decoding)
|
||||
template<typename T>
|
||||
ostream&
|
||||
pass::encode(ostream& str, const T& val)
|
||||
{
|
||||
return str << val;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
istream&
|
||||
pass::decode(istream& str, T& val)
|
||||
{
|
||||
return str >> val;
|
||||
}
|
||||
|
||||
// Variant encoding
|
||||
template<typename Ts, typename Tv>
|
||||
constexpr Ts
|
||||
variant_map();
|
||||
|
||||
#define TLS_VARIANT_MAP(EnumType, MappedType, enum_value) \
|
||||
template<> \
|
||||
constexpr EnumType variant_map<EnumType, MappedType>() \
|
||||
{ \
|
||||
return EnumType::enum_value; \
|
||||
}
|
||||
|
||||
template<typename Ts>
|
||||
template<typename... Tp>
|
||||
inline Ts
|
||||
variant<Ts>::type(const var::variant<Tp...>& data)
|
||||
{
|
||||
const auto get_type = [](const auto& v) {
|
||||
return variant_map<Ts, std::decay_t<decltype(v)>>();
|
||||
};
|
||||
return var::visit(get_type, data);
|
||||
}
|
||||
|
||||
template<typename Ts>
|
||||
template<typename... Tp>
|
||||
ostream&
|
||||
variant<Ts>::encode(ostream& str, const var::variant<Tp...>& data)
|
||||
{
|
||||
const auto write_variant = [&str](auto&& v) {
|
||||
using Tv = std::decay_t<decltype(v)>;
|
||||
str << variant_map<Ts, Tv>() << v;
|
||||
};
|
||||
var::visit(write_variant, data);
|
||||
return str;
|
||||
}
|
||||
|
||||
template<typename Ts>
|
||||
template<size_t I, typename Te, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
variant<Ts>::read_variant(istream&, Te, var::variant<Tp...>&)
|
||||
{
|
||||
throw ReadError("Invalid variant type label");
|
||||
}
|
||||
|
||||
template<typename Ts>
|
||||
template<size_t I, typename Te, typename... Tp>
|
||||
inline
|
||||
typename std::enable_if < I<sizeof...(Tp), void>::type
|
||||
variant<Ts>::read_variant(istream& str,
|
||||
Te target_type,
|
||||
var::variant<Tp...>& v)
|
||||
{
|
||||
using Tc = var::variant_alternative_t<I, var::variant<Tp...>>;
|
||||
if (variant_map<Ts, Tc>() == target_type) {
|
||||
str >> v.template emplace<I>();
|
||||
return;
|
||||
}
|
||||
|
||||
read_variant<I + 1>(str, target_type, v);
|
||||
}
|
||||
|
||||
template<typename Ts>
|
||||
template<typename... Tp>
|
||||
istream&
|
||||
variant<Ts>::decode(istream& str, var::variant<Tp...>& data)
|
||||
{
|
||||
Ts target_type;
|
||||
str >> target_type;
|
||||
read_variant(str, target_type, data);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Struct writer without traits (enabled by macro)
|
||||
template<size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
write_tuple(ostream&, const std::tuple<Tp...>&)
|
||||
{
|
||||
}
|
||||
|
||||
template<size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if <
|
||||
I<sizeof...(Tp), void>::type
|
||||
write_tuple(ostream& str, const std::tuple<Tp...>& t)
|
||||
{
|
||||
str << std::get<I>(t);
|
||||
write_tuple<I + 1, Tp...>(str, t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline
|
||||
typename std::enable_if<is_serializable<T>::value && !has_traits<T>::value,
|
||||
ostream&>::type
|
||||
operator<<(ostream& str, const T& obj)
|
||||
{
|
||||
write_tuple(str, obj._tls_fields_w());
|
||||
return str;
|
||||
}
|
||||
|
||||
// Struct writer with traits (enabled by macro)
|
||||
template<typename Tr, size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
write_tuple_traits(ostream&, const std::tuple<Tp...>&)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename Tr, size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if <
|
||||
I<sizeof...(Tp), void>::type
|
||||
write_tuple_traits(ostream& str, const std::tuple<Tp...>& t)
|
||||
{
|
||||
std::tuple_element_t<I, Tr>::encode(str, std::get<I>(t));
|
||||
write_tuple_traits<Tr, I + 1, Tp...>(str, t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline
|
||||
typename std::enable_if<is_serializable<T>::value && has_traits<T>::value,
|
||||
ostream&>::type
|
||||
operator<<(ostream& str, const T& obj)
|
||||
{
|
||||
write_tuple_traits<typename T::_tls_traits>(str, obj._tls_fields_w());
|
||||
return str;
|
||||
}
|
||||
|
||||
// Struct reader without traits (enabled by macro)
|
||||
template<size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
read_tuple(istream&, const std::tuple<Tp...>&)
|
||||
{
|
||||
}
|
||||
|
||||
template<size_t I = 0, typename... Tp>
|
||||
inline
|
||||
typename std::enable_if < I<sizeof...(Tp), void>::type
|
||||
read_tuple(istream& str, const std::tuple<Tp...>& t)
|
||||
{
|
||||
str >> std::get<I>(t);
|
||||
read_tuple<I + 1, Tp...>(str, t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline
|
||||
typename std::enable_if<is_serializable<T>::value && !has_traits<T>::value,
|
||||
istream&>::type
|
||||
operator>>(istream& str, T& obj)
|
||||
{
|
||||
read_tuple(str, obj._tls_fields_r());
|
||||
return str;
|
||||
}
|
||||
|
||||
// Struct reader with traits (enabled by macro)
|
||||
template<typename Tr, size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if<I == sizeof...(Tp), void>::type
|
||||
read_tuple_traits(istream&, const std::tuple<Tp...>&)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename Tr, size_t I = 0, typename... Tp>
|
||||
inline typename std::enable_if <
|
||||
I<sizeof...(Tp), void>::type
|
||||
read_tuple_traits(istream& str, const std::tuple<Tp...>& t)
|
||||
{
|
||||
std::tuple_element_t<I, Tr>::decode(str, std::get<I>(t));
|
||||
read_tuple_traits<Tr, I + 1, Tp...>(str, t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline
|
||||
typename std::enable_if<is_serializable<T>::value && has_traits<T>::value,
|
||||
istream&>::type
|
||||
operator>>(istream& str, T& obj)
|
||||
{
|
||||
read_tuple_traits<typename T::_tls_traits>(str, obj._tls_fields_r());
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace mlspp::tls
|
||||
178
DPP/mlspp/lib/tls_syntax/src/tls_syntax.cpp
Executable file
178
DPP/mlspp/lib/tls_syntax/src/tls_syntax.cpp
Executable file
@@ -0,0 +1,178 @@
|
||||
#include <tls/tls_syntax.h>
|
||||
|
||||
// NOLINTNEXTLINE(llvmlibc-implementation-in-namespace)
|
||||
namespace mlspp::tls {
|
||||
|
||||
void
|
||||
ostream::write_raw(const std::vector<uint8_t>& bytes)
|
||||
{
|
||||
// Not sure what the default argument is here
|
||||
_buffer.insert(_buffer.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
// Primitive type writers
|
||||
ostream&
|
||||
ostream::write_uint(uint64_t value, int length)
|
||||
{
|
||||
for (int i = length - 1; i >= 0; --i) {
|
||||
_buffer.push_back(static_cast<uint8_t>(value >> unsigned(8 * i)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ostream&
|
||||
operator<<(ostream& out, bool data)
|
||||
{
|
||||
if (data) {
|
||||
return out << uint8_t(1);
|
||||
}
|
||||
|
||||
return out << uint8_t(0);
|
||||
}
|
||||
|
||||
ostream&
|
||||
operator<<(ostream& out, uint8_t data) // NOLINT(llvmlibc-callee-namespace)
|
||||
{
|
||||
return out.write_uint(data, 1);
|
||||
}
|
||||
|
||||
ostream&
|
||||
operator<<(ostream& out, uint16_t data)
|
||||
{
|
||||
return out.write_uint(data, 2);
|
||||
}
|
||||
|
||||
ostream&
|
||||
operator<<(ostream& out, uint32_t data)
|
||||
{
|
||||
return out.write_uint(data, 4);
|
||||
}
|
||||
|
||||
ostream&
|
||||
operator<<(ostream& out, uint64_t data)
|
||||
{
|
||||
return out.write_uint(data, 8);
|
||||
}
|
||||
|
||||
// Because pop_back() on an empty vector is undefined
|
||||
uint8_t
|
||||
istream::next()
|
||||
{
|
||||
if (_buffer.empty()) {
|
||||
throw ReadError("Attempt to read from empty buffer");
|
||||
}
|
||||
|
||||
const uint8_t value = _buffer.back();
|
||||
_buffer.pop_back();
|
||||
return value;
|
||||
}
|
||||
|
||||
// Primitive type readers
|
||||
|
||||
istream&
|
||||
operator>>(istream& in, bool& data)
|
||||
{
|
||||
uint8_t val = 0;
|
||||
in >> val;
|
||||
|
||||
// Linter thinks uint8_t is signed (?)
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
if ((val & 0xFE) != 0) {
|
||||
throw ReadError("Malformed boolean");
|
||||
}
|
||||
|
||||
data = (val == 1);
|
||||
return in;
|
||||
}
|
||||
|
||||
istream&
|
||||
operator>>(istream& in, uint8_t& data) // NOLINT(llvmlibc-callee-namespace)
|
||||
{
|
||||
return in.read_uint(data, 1);
|
||||
}
|
||||
|
||||
istream&
|
||||
operator>>(istream& in, uint16_t& data)
|
||||
{
|
||||
return in.read_uint(data, 2);
|
||||
}
|
||||
|
||||
istream&
|
||||
operator>>(istream& in, uint32_t& data)
|
||||
{
|
||||
return in.read_uint(data, 4);
|
||||
}
|
||||
|
||||
istream&
|
||||
operator>>(istream& in, uint64_t& data)
|
||||
{
|
||||
return in.read_uint(data, 8);
|
||||
}
|
||||
|
||||
// Varint encoding
|
||||
static constexpr size_t VARINT_HEADER_OFFSET = 6;
|
||||
static constexpr uint64_t VARINT_1_HEADER = 0x00; // 0 << V1_OFFSET
|
||||
static constexpr uint64_t VARINT_2_HEADER = 0x4000; // 1 << V2_OFFSET
|
||||
static constexpr uint64_t VARINT_4_HEADER = 0x80000000; // 2 << V4_OFFSET
|
||||
static constexpr uint64_t VARINT_1_MAX = 0x3f;
|
||||
static constexpr uint64_t VARINT_2_MAX = 0x3fff;
|
||||
static constexpr uint64_t VARINT_4_MAX = 0x3fffffff;
|
||||
|
||||
ostream&
|
||||
varint::encode(ostream& str, const uint64_t& val)
|
||||
{
|
||||
if (val <= VARINT_1_MAX) {
|
||||
return str.write_uint(VARINT_1_HEADER | val, 1);
|
||||
}
|
||||
|
||||
if (val <= VARINT_2_MAX) {
|
||||
return str.write_uint(VARINT_2_HEADER | val, 2);
|
||||
}
|
||||
|
||||
if (val <= VARINT_4_MAX) {
|
||||
return str.write_uint(VARINT_4_HEADER | val, 4);
|
||||
}
|
||||
|
||||
throw WriteError("Varint value exceeds maximum size");
|
||||
}
|
||||
|
||||
istream&
|
||||
varint::decode(istream& str, uint64_t& val)
|
||||
{
|
||||
auto log_size = size_t(str._buffer.back() >> VARINT_HEADER_OFFSET);
|
||||
if (log_size > 2) {
|
||||
throw ReadError("Malformed varint header");
|
||||
}
|
||||
|
||||
auto read = uint64_t(0);
|
||||
auto read_bytes = size_t(size_t(1) << log_size);
|
||||
str.read_uint(read, read_bytes);
|
||||
|
||||
switch (log_size) {
|
||||
case 0:
|
||||
read ^= VARINT_1_HEADER;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
read ^= VARINT_2_HEADER;
|
||||
if (read <= VARINT_1_MAX) {
|
||||
throw ReadError("Non-minimal varint");
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
read ^= VARINT_4_HEADER;
|
||||
if (read <= VARINT_2_MAX) {
|
||||
throw ReadError("Non-minimal varint");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ReadError("Malformed varint header");
|
||||
}
|
||||
|
||||
val = read;
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace mlspp::tls
|
||||
Reference in New Issue
Block a user