Start proto::parser, remove msgpack::reader

- proto::parser will likely contain helper functions for parsing
  messages from a buffer, for now it will explicitly define message
  parsers for each available message. It also leverages the new unpacker
  API, which has type safety in mind. These messages should not be
  unstructured, so it doesn't make sense to use the token API.
This commit is contained in:
Kurt Sassenrath 2024-01-12 19:36:56 -08:00
parent c0945246de
commit b2a6f25306
9 changed files with 304 additions and 69 deletions

View File

@ -59,8 +59,9 @@ constexpr decltype(auto) unpack_integral(Itr& inp) noexcept {
// Defines a range of bytes for binary formats.
template <typename T>
concept byte_range = std::ranges::contiguous_range<T>
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
concept unpackable_byte_range =
std::ranges::contiguous_range<T>
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
template <format_type F, typename Iter>
constexpr inline bool accept_format(Iter& itr) noexcept {
@ -168,6 +169,24 @@ struct builtin_unpacker<bool> {
}
};
template <>
struct builtin_unpacker<map_desc> {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<map_desc, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & ~format::fixmap::mask) == format::fixmap::marker) {
return detail::unpack_format<format::fixmap>(unpacker);
}
switch (marker) {
case format::map16::marker:
return detail::unpack_format<format::map16>(unpacker);
case format::map32::marker:
return detail::unpack_format<format::map32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
// This unpacker will accept any uintX, positive_fixint types.
struct unsigned_int_unpacker {
static constexpr auto unpack(auto& unpacker) noexcept

View File

@ -0,0 +1,35 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Error definitions for protocol-related functionality
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef parselink_proto_error_adef9d32bb51411a
#define parselink_proto_error_adef9d32bb51411a
namespace parselink {
namespace proto {
enum class error {
system_error,
incomplete,
unsupported,
bad_data,
too_large,
};
} // namespace proto
} // namespace parselink
#endif // parselink_proto_error_adef9d32bb51411a

View File

@ -18,12 +18,9 @@
#ifndef message_0c61530748b9f966
#define message_0c61530748b9f966
#include <tl/expected.hpp>
#include <cstdint>
#include <span>
#include <string_view>
#include <variant>
namespace parselink {
namespace proto {
@ -37,35 +34,40 @@ namespace proto {
// This may be revised in the future. The header could remain as msgpack, or
// switch to something hand-crafted for saving bits.
struct error_message {
struct base_message {};
template <typename T>
concept message_type = std::is_base_of_v<base_message, T>;
struct error_message : base_message {
std::uint32_t code; // An error code
std::string_view what; // A string
};
// C->S: Request to (re)connect.
struct connect_message {
struct connect_message : base_message {
std::uint32_t version; // The version of the client.
std::uint32_t user_id; // The user id.
std::string_view user_id; // The user id.
std::span<std::byte> session_token; // An optional existing session token.
};
// S->C: Challenge to authenticate client as user_id
struct challenge_message {
struct challenge_message : base_message {
std::uint32_t version;
std::span<std::byte> challenge;
};
// C->S: Calculated response to a challenge.
struct response_message {
struct response_message : base_message {
std::span<std::byte> response;
};
// S->C: Session token.
struct session_established_message {
struct session_established_message : base_message {
std::span<std::byte> session_token;
};
struct parser_data_message {
struct parser_data_message : base_message {
std::string_view opts;
std::span<std::byte> payload;
};

View File

@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Parser for extracting messages from msgpack.
//
// Note: Eventually, it would be nice to automatically generate the parser for
// message structures. Definitions below are templated to allow for this.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef parselink_proto_parser_ad351d41fe4c72dd
#define parselink_proto_parser_ad351d41fe4c72dd
#include "parselink/proto/error.h"
#include "parselink/proto/message.h"
#include <tl/expected.hpp>
namespace parselink {
namespace proto {
////////////////////////////////////////////////////////////////////////////////
// Default parse template instantiation -- unsupported message.
////////////////////////////////////////////////////////////////////////////////
template <message_type M>
tl::expected<M, error> parse(std::span<std::byte const> data) noexcept {
return tl::make_unexpected(error::unsupported);
}
////////////////////////////////////////////////////////////////////////////////
// Currently-implemented message types.
////////////////////////////////////////////////////////////////////////////////
#define PARSELINK_PROTO_SUPPORTED_MESSAGE(msgtype) \
tl::expected<msgtype, error> parse_##msgtype( \
std::span<std::byte const> data) noexcept; \
template <> \
constexpr tl::expected<msgtype, error> parse( \
std::span<std::byte const> data) noexcept { \
return parse_##msgtype(data); \
}
PARSELINK_PROTO_SUPPORTED_MESSAGE(connect_message);
#undef PARSELINK_PROTO_SUPPORTED_MESSAGE
} // namespace proto
} // namespace parselink
#endif // parselink_proto_parser_ad351d41fe4c72dd

View File

@ -19,6 +19,7 @@
#define session_07eae057feface79
#include "parselink/msgpack/token.h"
#include "parselink/proto/error.h"
#include "parselink/proto/session_id.h"
#include <tl/expected.hpp>
@ -46,14 +47,6 @@ struct std::hash<std::span<std::byte const>> {
namespace parselink {
namespace proto {
enum class error {
system_error,
incomplete,
unsupported,
bad_data,
too_large,
};
// Structure containing header information parsed from a buffer.
struct header_info {
std::uint32_t message_size; // Size of the message, minus the header.

View File

@ -24,6 +24,7 @@
#include <algorithm>
#include <fcntl.h>
#include <span>
#include <system_error>
#include <unistd.h>
#include <vector>
@ -69,24 +70,22 @@ struct [[nodiscard]] handle {
int fd_{-1};
};
inline tl::expected<handle, std::error_code> open(
std::string_view path, int flags) noexcept {
handle file(::open(std::string{path}.c_str(), flags));
inline tl::expected<handle, std::errc> open(
std::string_view path, int flags, int mode = {}) noexcept {
handle file(::open(std::string{path}.c_str(), flags, mode));
if (file) {
return file;
} else {
return tl::make_unexpected(
std::make_error_code(static_cast<std::errc>(-*file)));
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
}
inline tl::expected<std::vector<std::byte>, std::error_code> read_some(
inline tl::expected<std::vector<std::byte>, std::errc> read_some(
handle file, std::size_t amount) noexcept {
std::vector<std::byte> data;
if (!file) {
return tl::make_unexpected(
std::make_error_code(static_cast<std::errc>(-*file)));
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
data.resize(amount);
@ -102,13 +101,12 @@ inline tl::expected<std::vector<std::byte>, std::error_code> read_some(
template <typename T>
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
inline tl::expected<std::vector<T>, std::error_code> read(
handle file) noexcept {
inline tl::expected<std::vector<T>, std::errc> read(
handle const& file) noexcept {
std::vector<T> data;
if (!file) {
return tl::make_unexpected(
std::make_error_code(static_cast<std::errc>(-*file)));
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
auto cur = lseek(*file, 0, SEEK_CUR);
@ -126,6 +124,31 @@ inline tl::expected<std::vector<T>, std::error_code> read(
return data;
}
template <typename T>
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
inline tl::expected<std::size_t, std::errc> write(
handle const& file, std::span<T> data) noexcept {
if (!file) {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
lseek(*file, 0, SEEK_SET);
auto result = ftruncate(*file, 0);
if (result < 0) {
return tl::make_unexpected(static_cast<std::errc>(errno));
}
auto amt_written = ::write(*file, data.data(), data.size());
if (result < 0) {
return tl::make_unexpected(static_cast<std::errc>(errno));
}
return static_cast<std::size_t>(amt_written);
}
} // namespace file
} // namespace utility
} // namespace parselink

View File

@ -3,6 +3,7 @@
cc_library(
name = "proto",
srcs = [
"parser.cpp",
"session.cpp",
"session_id.cpp",
],

70
source/proto/parser.cpp Normal file
View File

@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Parser implementations for various msgpack messages.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/proto/parser.h"
#include "parselink/logging.h"
#include "parselink/msgpack/core/unpacker.h"
namespace parselink {
namespace proto {
namespace {
logging::logger logger{"parser"};
constexpr auto assign(auto& param, msgpack::unpacker& unpacker) noexcept
-> tl::expected<void, error> {
auto do_assign = [&param](auto&& v) { param = v; };
return unpacker.unpack<std::decay_t<decltype(param)>>()
.map(do_assign)
.map_error([](auto) { return error::bad_data; });
}
} // anonymous namespace
tl::expected<connect_message, error> parse_connect_message(
std::span<std::byte const> data) noexcept {
msgpack::unpacker unpacker(data);
connect_message msg;
constexpr static tl::unexpected<error> bad_data(error::bad_data);
auto type = unpacker.unpack<std::string_view>();
if (!type || type != "connect") return bad_data;
auto entries = unpacker.unpack<msgpack::map_desc>();
if (!entries) return bad_data;
for (auto i = entries->count; i != 0; --i) {
auto key = unpacker.unpack<std::string_view>();
if (!key) return bad_data;
if (key == "version") {
if (auto val = assign(msg.version, unpacker)) continue;
} else if (key == "user_id") {
if (auto val = assign(msg.user_id, unpacker)) continue;
}
logger.debug("Unknown key: {}", *key);
return bad_data;
}
return msg;
}
} // namespace proto
} // namespace parselink

View File

@ -21,9 +21,9 @@
#include "parselink/server.h"
#include "parselink/logging.h"
#include "parselink/msgpack/core/writer.h"
#include "parselink/msgpack/token/reader.h"
#include "parselink/msgpack/token/views.h"
#include "parselink/msgpack/core/packer.h"
#include "parselink/msgpack/core/unpacker.h"
#include "parselink/proto/parser.h"
#include "parselink/proto/session.h"
#include "parselink/utility/file.h"
@ -75,6 +75,13 @@ struct fmt::formatter<boost::system::error_code>
}
};
template <>
struct fmt::formatter<tl::monostate> : fmt::formatter<std::string_view> {
constexpr auto format(auto const&, auto& ctx) const {
return fmt::formatter<std::string_view>::format(".", ctx);
}
};
template <>
struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
@ -170,12 +177,12 @@ public:
net::awaitable<tl::expected<proto::session*, proto::error>> create_session(
std::shared_ptr<user_connection> const& conn,
proto::connect_info const& info);
proto::connect_message const& info);
private:
awaitable<void> user_listen();
tl::expected<tl::monostate, std::error_code> load_keys() noexcept;
tl::expected<tl::monostate, std::errc> load_keys() noexcept;
hydro_kx_keypair kp_;
net::io_context io_context_;
@ -268,12 +275,7 @@ public:
co_return;
}
auto reader = msgpack::token_reader(msgbuf);
std::array<msgpack::token, 32> tokens;
auto connect_info =
reader.read_some(tokens)
.map_error([](auto) { return proto::error::bad_data; })
.and_then(proto::parse_connect);
auto connect_info = proto::parse<proto::connect_message>(msgbuf);
if (!connect_info) {
logger.error("Session failed: {}", connect_info.error());
@ -303,7 +305,7 @@ monolithic_server::monolithic_server(std::string_view address,
, addr_(net::ip::address::from_string(std::string{address}))
, user_port_{user_port}
, websocket_port_{websocket_port} {
load_keys();
logger.debug("Loaded keys: {}", load_keys());
logger.debug("Creating monolithic_server(address = {}, user_port = {}, "
"websocket_port = {})",
@ -336,43 +338,73 @@ std::error_code monolithic_server::run() noexcept {
return {};
}
tl::expected<tl::monostate, std::error_code>
monolithic_server::load_keys() noexcept {
std::string_view filename = "server_kp.keys";
tl::expected<tl::monostate, std::errc> monolithic_server::load_keys() noexcept {
std::string_view filename = "/home/rihya/server_kp.keys";
auto load_key =
[this](auto raw) -> tl::expected<tl::monostate, std::error_code> {
if (sizeof(kp_) != raw.size()) {
return tl::make_unexpected(
std::make_error_code(std::errc::bad_message));
}
std::ranges::transform(std::begin(raw),
std::next(std::begin(raw), sizeof(kp_.pk)), std::begin(kp_.pk),
enum class result { loaded, generated };
auto load_key = [this](auto const& raw) -> tl::expected<result, std::errc> {
msgpack::unpacker unpacker(raw);
auto pk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.pk)>>();
if (!pk) return tl::make_unexpected(std::errc::bad_message);
auto sk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.sk)>>();
if (!sk) return tl::make_unexpected(std::errc::bad_message);
std::ranges::transform(pk->begin(), pk->end(), std::begin(kp_.pk),
[](auto c) { return std::bit_cast<unsigned char>(c); });
return tl::monostate{};
std::ranges::transform(sk->begin(), sk->end(), std::begin(kp_.sk),
[](auto c) { return std::bit_cast<unsigned char>(c); });
return result::loaded;
};
auto generate_keys = [this](auto const& err)
-> tl::expected<tl::monostate, std::error_code> {
auto generate_keys =
[this](auto const& err) -> tl::expected<result, std::errc> {
logger.warning("Could not load server keys, generating a keypair");
hydro_kx_keygen(&kp_);
std::span<std::byte, sizeof(kp_.pk)> pk(
reinterpret_cast<std::byte*>(kp_.pk), sizeof(kp_.pk));
std::span<std::byte, sizeof(kp_.sk)> sk(
reinterpret_cast<std::byte*>(kp_.sk), sizeof(kp_.pk));
logger.debug("\n\tPK: {}\n\tSK: {}", pk, sk);
return tl::monostate{};
return result::generated;
};
return utility::file::open(filename, O_RDWR | O_CREAT)
.and_then(utility::file::read<std::byte>)
.and_then(load_key)
.or_else(generate_keys);
auto commit = [this](auto const& handle)
-> tl::expected<tl::monostate, std::errc> {
std::vector<std::byte> buff(4 + sizeof(kp_.pk) + sizeof(kp_.sk));
std::span<std::byte> pk(
reinterpret_cast<std::byte*>(kp_.pk), sizeof(kp_.pk));
std::span<std::byte> sk(
reinterpret_cast<std::byte*>(kp_.sk), sizeof(kp_.pk));
msgpack::packer packer(buff);
packer.pack(pk);
packer.pack(sk);
return utility::file::write(handle, packer.subspan()).map([](auto) {
logger.info("Wrote new keys to disk");
return tl::monostate{};
});
};
auto load_or_generate_keys = [&load_key, &generate_keys, &commit](
auto const& handle)
-> tl::expected<tl::monostate, std::errc> {
return utility::file::read<std::byte>(handle)
.and_then(load_key)
.or_else(generate_keys)
.and_then([&handle, &commit](auto r)
-> tl::expected<tl::monostate, std::errc> {
if (r == result::generated) {
return commit(handle);
}
return tl::monostate{};
});
};
return utility::file::open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)
.and_then(load_or_generate_keys);
}
net::awaitable<tl::expected<proto::session*, proto::error>>
monolithic_server::create_session(std::shared_ptr<user_connection> const& conn,
proto::connect_info const& info) {
proto::connect_message const& info) {
// Move to session strand.
co_await net::post(session_strand_,
net::bind_executor(session_strand_, use_awaitable));