Compare commits

..

24 Commits

Author SHA1 Message Date
b2a6f25306 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.
2024-01-12 19:36:56 -08:00
c0945246de Refactor packer tests to make recompilation faster 2024-01-12 13:37:39 -08:00
eb7a02d5c4 Refactor tests, add formatters, etc. 2024-01-12 12:53:00 -08:00
Kurt Sassenrath
db601fb770 WIP unpacking 2024-01-08 19:58:12 -08:00
0e6f05dd68 WIP Server stuff: key gen 2024-01-08 10:39:35 -08:00
Kurt Sassenrath
e48ba60c68 more tests, allow signed int -> positive_fixint 2024-01-04 15:25:17 -08:00
Kurt Sassenrath
8f4ac703f4 Rapidcheck component, initial test cases rewritten 2024-01-03 15:51:22 -08:00
Kurt Sassenrath
0cafb9bcd7 Additional concepts work 2024-01-02 22:43:29 -08:00
b48eddb8d0 Range packing. 2024-01-02 07:55:40 -08:00
3d7ba40289 Add msgpack packer support for ranges of objects. 2023-12-30 19:18:09 -08:00
Kurt Sassenrath
cb2b5b47b0 WIP: Migrate packing to pass context object. 2023-12-28 15:51:13 -08:00
Kurt Sassenrath
a027d6b946 Add builtin_packer for array_desc / map_desc 2023-12-28 15:35:40 -08:00
Kurt Sassenrath
0e00edc378 Remove builtin_pack_helper. 2023-12-28 09:24:48 -08:00
c89d7cd541 Begin refactor of msgpack packer 2023-12-26 23:45:47 -08:00
Kurt Sassenrath
53665b8e70 Packer WIP 2023-12-08 08:46:48 -08:00
ebe2b070b9 Deductive writer POC. Start packer implementation. 2023-11-22 22:54:21 -08:00
84942171ea WIP Server stuff 2023-11-22 22:54:08 -08:00
cbca4be237 Add hash test 2023-11-06 22:56:35 -08:00
Kurt Sassenrath
578065be47 More session id work 2023-11-06 16:16:04 -08:00
1ceccd0720 WIP: Add hydrogen, session_id 2023-11-06 07:06:19 -08:00
8f8066c243 WIP 2023-10-26 07:23:51 -07:00
6874da27a3 Apply clang-format 2023-10-20 00:34:44 -07:00
Kurt Sassenrath
c46f68e759 Initial clang-format file 2023-10-20 00:34:31 -07:00
Kurt Sassenrath
3157e39169 WIP: Session decoupling from server impl 2023-10-19 23:56:22 -07:00
86 changed files with 8401 additions and 3127 deletions

View File

@ -6,40 +6,42 @@ AlignOperands: AlignAfterOperator
AllowShortCaseLabelsOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
AccessModifierOffset: -4
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BraceWrapping: BraceWrapping:
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: false SplitEmptyRecord: false
SplitEmptyNamespace: false SplitEmptyNamespace: false
BracedInitializerIndentWidth: 4 #BracedInitializerIndentWidth: 4
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma BreakInheritanceList: BeforeComma
ConstructorInitializerIndentWidth: 8 ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8 ContinuationIndentWidth: 8
IncludeBlocks: Regroup #IncludeBlocks: Regroup
InIncludeCategories: IncludeCategories:
- Regex: '^"parselink/' - Regex: '^"parselink/'
Priority: 3 Priority: 1
SortPriority: 3 SortPriority: 1
CaseSensitive: false CaseSensitive: false
- Regex: '^<(fmt|tl|magic_enum|ut)/' - Regex: '^<(fmt|tl|magic_enum|ut)/'
Priority: 2 Priority: 2
SortPriority: 2 SortPriority: 2
CaseSensitive: false CaseSensitive: false
- Regex: '.*' - Regex: '.*'
Priority: 1 Priority: 3
SortPriority: 0 SortPriority: 3
IncludeIsMainRegex: '(_test)?$' IncludeIsMainRegex: '(_test)?$'
IndentAccessModifiers: false
IndentCaseLabels: true IndentCaseLabels: true
IndentGotoLabels: false IndentGotoLabels: false
IndentWidth: 4 IndentWidth: 4
InsertNewlineAtEOF: true InsertNewlineAtEOF: true
KeepEmptyLinesAtEOF: true #KeepEmptyLinesAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Left PointerAlignment: Left
QualifierAlignment: Right QualifierAlignment: Right
QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'volatile'] #QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'volatile']
ReferenceAlignment: Pointer ReferenceAlignment: Pointer
SeparateDefinitionBlocks: Always
--- ---

30
.snippets/cpp.lua Normal file
View File

@ -0,0 +1,30 @@
local luasnip = require('luasnip')
local fmt = require('luasnip.extras.fmt').fmt
local header = [[
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: {}
//
// {}
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
]]
return {
s("hdr", fmt(header, {i(1, "<module>"), i(2, "<description>")}))
}

65
BUILD.rapidcheck Normal file
View File

@ -0,0 +1,65 @@
cc_library(
name = "headers",
includes = ["include"],
visibility = ["//visibility:private"],
copts = ["-DRC_DONT_USE_RTTI"],
hdrs = glob([
"include/**/*.hpp",
"include/**/*.h",
]),
)
cc_library(
name = "random",
deps = [":headers"],
visibility = ["//visibility:private"],
copts = ["-DRC_DONT_USE_RTTI", "-O3"],
srcs = [
"src/Random.cpp",
],
)
cc_library(
name = "rapidcheck",
visibility = ["//visibility:public"],
deps = [":headers", ":random"],
copts = ["-DRC_DONT_USE_RTTI"],
includes = ["include"],
srcs = [
"src/BeforeMinimalTestCase.cpp",
"src/Check.cpp",
"src/Classify.cpp",
"src/GenerationFailure.cpp",
"src/Log.cpp",
"src/Show.cpp",
"src/detail/Any.cpp",
"src/detail/Assertions.cpp",
"src/detail/Base64.cpp",
"src/detail/Configuration.cpp",
"src/detail/DefaultTestListener.cpp",
"src/detail/FrequencyMap.cpp",
"src/detail/ImplicitParam.cpp",
"src/detail/LogTestListener.cpp",
"src/detail/MapParser.cpp",
"src/detail/MulticastTestListener.cpp",
"src/detail/ParseException.cpp",
"src/detail/Platform.cpp",
"src/detail/Property.cpp",
"src/detail/PropertyContext.cpp",
"src/detail/ReproduceListener.cpp",
"src/detail/Results.cpp",
"src/detail/Serialization.cpp",
"src/detail/StringSerialization.cpp",
"src/detail/TestMetadata.cpp",
"src/detail/TestParams.cpp",
"src/detail/Testing.cpp",
"src/gen/Numeric.cpp",
"src/gen/Text.cpp",
"src/gen/detail/ExecHandler.cpp",
"src/gen/detail/GenerationHandler.cpp",
"src/gen/detail/Recipe.cpp",
"src/gen/detail/ScaleInteger.cpp",
] + glob(["src/detail/*.h"]),
)

6
MODULE.bazel Normal file
View File

@ -0,0 +1,6 @@
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################

1245
MODULE.bazel.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
workspace(name = "parselink") workspace(name = "parselink")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
#=============================================================================== #===============================================================================
# Imported Bazel modules # Imported Bazel modules
@ -24,7 +25,7 @@ boost_deps()
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# magic_enum: Used in logging implementation for enum names. # magic_enum: Used in logging implementation for enum names.
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
magic_enum_version = "0.8.2" magic_enum_version = "0.9.5"
magic_enum_base_url = \ magic_enum_base_url = \
"https://github.com/Neargye/magic_enum/archive/refs/tags/v" "https://github.com/Neargye/magic_enum/archive/refs/tags/v"
magic_enum_sha256 = \ magic_enum_sha256 = \
@ -32,7 +33,7 @@ magic_enum_sha256 = \
http_archive( http_archive(
name = "magic_enum", name = "magic_enum",
sha256 = magic_enum_sha256, # sha256 = magic_enum_sha256,
url = magic_enum_base_url + magic_enum_version + ".zip", url = magic_enum_base_url + magic_enum_version + ".zip",
strip_prefix = "magic_enum-" + magic_enum_version, strip_prefix = "magic_enum-" + magic_enum_version,
) )
@ -77,7 +78,30 @@ cc_library(
) )
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# ut: Unit test framework. # libhydrogen: A lightweight cryptography library
#-------------------------------------------------------------------------------
hydrogen_commit = "c382cbb"
hydrogen_base_url = \
"https://github.com/jedisct1/libhydrogen"
hydrogen_branch = "master"
git_repository(
name = "hydrogen",
remote = hydrogen_base_url,
commit = hydrogen_commit,
build_file_content =
"""
cc_library(
name = "hydrogen",
srcs = glob(["hydrogen.c", "impl/**/*.h"]),
hdrs = ["hydrogen.h"],
visibility = ["//visibility:public"],
)
""",
)
#-------------------------------------------------------------------------------
# ut: Testing framework.
# TODO(kss): Only if tests are needed? # TODO(kss): Only if tests are needed?
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
ut_version = "1.1.9" ut_version = "1.1.9"
@ -100,6 +124,22 @@ cc_library(
strip_prefix = "ut-" + ut_version, strip_prefix = "ut-" + ut_version,
) )
#-------------------------------------------------------------------------------
# rapidcheck: Property-based-testing framework.
# TODO(kss): Only if tests are needed?
#-------------------------------------------------------------------------------
rapidcheck_commit = "ff6af6fc683159deb51c543b065eba14dfcf329b"
rapidcheck_base_url = "https://github.com/emil-e/rapidcheck"
rapidcheck_branch = "master"
git_repository(
name = "rapidcheck",
remote = rapidcheck_base_url,
commit = rapidcheck_commit,
build_file = "@//:BUILD.rapidcheck"
)
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Support compile_commands.json generation for LSP. # Support compile_commands.json generation for LSP.
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------

0
bazel/BUILD Normal file
View File

View File

@ -1,11 +1,11 @@
#include <algorithm>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
#include <boost/beast/http.hpp> #include <boost/beast/http.hpp>
#include <boost/beast/version.hpp> #include <boost/beast/version.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp> #include <boost/config.hpp>
#include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
@ -20,15 +20,11 @@ namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
// Return a reasonable mime type based on the extension of a file. // Return a reasonable mime type based on the extension of a file.
beast::string_view beast::string_view mime_type(beast::string_view path) {
mime_type(beast::string_view path)
{
using beast::iequals; using beast::iequals;
auto const ext = [&path] auto const ext = [&path] {
{
auto const pos = path.rfind("."); auto const pos = path.rfind(".");
if(pos == beast::string_view::npos) if (pos == beast::string_view::npos) return beast::string_view{};
return beast::string_view{};
return path.substr(pos); return path.substr(pos);
}(); }();
if (iequals(ext, ".htm")) return "text/html"; if (iequals(ext, ".htm")) return "text/html";
@ -57,26 +53,18 @@ mime_type(beast::string_view path)
// Append an HTTP rel-path to a local filesystem path. // Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform. // The returned path is normalized for the platform.
std::string std::string path_cat(beast::string_view base, beast::string_view path) {
path_cat( if (base.empty()) return std::string(path);
beast::string_view base,
beast::string_view path)
{
if(base.empty())
return std::string(path);
std::string result(base); std::string result(base);
#ifdef BOOST_MSVC #ifdef BOOST_MSVC
char constexpr path_separator = '\\'; char constexpr path_separator = '\\';
if(result.back() == path_separator) if (result.back() == path_separator) result.resize(result.size() - 1);
result.resize(result.size() - 1);
result.append(path.data(), path.size()); result.append(path.data(), path.size());
for (auto& c : result) for (auto& c : result)
if(c == '/') if (c == '/') c = path_separator;
c = path_separator;
#else #else
char constexpr path_separator = '/'; char constexpr path_separator = '/';
if(result.back() == path_separator) if (result.back() == path_separator) result.resize(result.size() - 1);
result.resize(result.size() - 1);
result.append(path.data(), path.size()); result.append(path.data(), path.size());
#endif #endif
return result; return result;
@ -87,16 +75,12 @@ path_cat(
// The concrete type of the response message (which depends on the // The concrete type of the response message (which depends on the
// request), is type-erased in message_generator. // request), is type-erased in message_generator.
template <class Body, class Allocator> template <class Body, class Allocator>
http::message_generator http::message_generator handle_request(beast::string_view doc_root,
handle_request( http::request<Body, http::basic_fields<Allocator>>&& req) {
beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req)
{
// Returns a bad request response // Returns a bad request response
auto const bad_request = auto const bad_request = [&req](beast::string_view why) {
[&req](beast::string_view why) http::response<http::string_body> res{
{ http::status::bad_request, req.version()};
http::response<http::string_body> res{http::status::bad_request, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html"); res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive()); res.keep_alive(req.keep_alive());
@ -106,23 +90,22 @@ handle_request(
}; };
// Returns a not found response // Returns a not found response
auto const not_found = auto const not_found = [&req](beast::string_view target) {
[&req](beast::string_view target) http::response<http::string_body> res{
{ http::status::not_found, req.version()};
http::response<http::string_body> res{http::status::not_found, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html"); res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive()); res.keep_alive(req.keep_alive());
res.body() = "The resource '" + std::string(target) + "' was not found."; res.body() =
"The resource '" + std::string(target) + "' was not found.";
res.prepare_payload(); res.prepare_payload();
return res; return res;
}; };
// Returns a server error response // Returns a server error response
auto const server_error = auto const server_error = [&req](beast::string_view what) {
[&req](beast::string_view what) http::response<http::string_body> res{
{ http::status::internal_server_error, req.version()};
http::response<http::string_body> res{http::status::internal_server_error, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html"); res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive()); res.keep_alive(req.keep_alive());
@ -132,20 +115,17 @@ handle_request(
}; };
// Make sure we can handle the method // Make sure we can handle the method
if( req.method() != http::verb::get && if (req.method() != http::verb::get && req.method() != http::verb::head)
req.method() != http::verb::head)
return bad_request("Unknown HTTP-method"); return bad_request("Unknown HTTP-method");
// Request path must be absolute and not contain "..". // Request path must be absolute and not contain "..".
if( req.target().empty() || if (req.target().empty() || req.target()[0] != '/'
req.target()[0] != '/' || || req.target().find("..") != beast::string_view::npos)
req.target().find("..") != beast::string_view::npos)
return bad_request("Illegal request-target"); return bad_request("Illegal request-target");
// Build the path to the requested file // Build the path to the requested file
std::string path = path_cat(doc_root, req.target()); std::string path = path_cat(doc_root, req.target());
if(req.target().back() == '/') if (req.target().back() == '/') path.append("index.html");
path.append("index.html");
// Attempt to open the file // Attempt to open the file
beast::error_code ec; beast::error_code ec;
@ -157,15 +137,13 @@ handle_request(
return not_found(req.target()); return not_found(req.target());
// Handle an unknown error // Handle an unknown error
if(ec) if (ec) return server_error(ec.message());
return server_error(ec.message());
// Cache the size since we need it after the move // Cache the size since we need it after the move
auto const size = body.size(); auto const size = body.size();
// Respond to HEAD request // Respond to HEAD request
if(req.method() == http::verb::head) if (req.method() == http::verb::head) {
{
http::response<http::empty_body> res{http::status::ok, req.version()}; http::response<http::empty_body> res{http::status::ok, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(path)); res.set(http::field::content_type, mime_type(path));
@ -175,8 +153,7 @@ handle_request(
} }
// Respond to GET request // Respond to GET request
http::response<http::file_body> res{ http::response<http::file_body> res{std::piecewise_construct,
std::piecewise_construct,
std::make_tuple(std::move(body)), std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, req.version())}; std::make_tuple(http::status::ok, req.version())};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
@ -189,15 +166,12 @@ handle_request(
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Report a failure // Report a failure
void void fail(beast::error_code ec, char const* what) {
fail(beast::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n"; std::cerr << what << ": " << ec.message() << "\n";
} }
// Handles an HTTP server connection // Handles an HTTP server connection
class session : public std::enable_shared_from_this<session> class session : public std::enable_shared_from_this<session> {
{
beast::tcp_stream stream_; beast::tcp_stream stream_;
beast::flat_buffer buffer_; beast::flat_buffer buffer_;
std::shared_ptr<std::string const> doc_root_; std::shared_ptr<std::string const> doc_root_;
@ -205,31 +179,23 @@ class session : public std::enable_shared_from_this<session>
public: public:
// Take ownership of the stream // Take ownership of the stream
session( session(tcp::socket&& socket,
tcp::socket&& socket,
std::shared_ptr<std::string const> const& doc_root) std::shared_ptr<std::string const> const& doc_root)
: stream_(std::move(socket)) : stream_(std::move(socket))
, doc_root_(doc_root) , doc_root_(doc_root) {}
{
}
// Start the asynchronous operation // Start the asynchronous operation
void void run() {
run()
{
// We need to be executing within a strand to perform async operations // We need to be executing within a strand to perform async operations
// on the I/O objects in this session. Although not strictly necessary // on the I/O objects in this session. Although not strictly necessary
// for single-threaded contexts, this example code is written to be // for single-threaded contexts, this example code is written to be
// thread-safe by default. // thread-safe by default.
net::dispatch(stream_.get_executor(), net::dispatch(stream_.get_executor(),
beast::bind_front_handler( beast::bind_front_handler(
&session::do_read, &session::do_read, shared_from_this()));
shared_from_this()));
} }
void void do_read() {
do_read()
{
// Make the request empty before reading, // Make the request empty before reading,
// otherwise the operation behavior is undefined. // otherwise the operation behavior is undefined.
req_ = {}; req_ = {};
@ -240,55 +206,37 @@ public:
// Read a request // Read a request
http::async_read(stream_, buffer_, req_, http::async_read(stream_, buffer_, req_,
beast::bind_front_handler( beast::bind_front_handler(
&session::on_read, &session::on_read, shared_from_this()));
shared_from_this()));
} }
void void on_read(beast::error_code ec, std::size_t bytes_transferred) {
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred); boost::ignore_unused(bytes_transferred);
// This means they closed the connection // This means they closed the connection
if(ec == http::error::end_of_stream) if (ec == http::error::end_of_stream) return do_close();
return do_close();
if(ec) if (ec) return fail(ec, "read");
return fail(ec, "read");
// Send the response // Send the response
send_response( send_response(handle_request(*doc_root_, std::move(req_)));
handle_request(*doc_root_, std::move(req_)));
} }
void void send_response(http::message_generator&& msg) {
send_response(http::message_generator&& msg)
{
bool keep_alive = msg.keep_alive(); bool keep_alive = msg.keep_alive();
// Write the response // Write the response
beast::async_write( beast::async_write(stream_, std::move(msg),
stream_,
std::move(msg),
beast::bind_front_handler( beast::bind_front_handler(
&session::on_write, shared_from_this(), keep_alive)); &session::on_write, shared_from_this(), keep_alive));
} }
void void on_write(bool keep_alive, beast::error_code ec,
on_write( std::size_t bytes_transferred) {
bool keep_alive,
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred); boost::ignore_unused(bytes_transferred);
if(ec) if (ec) return fail(ec, "write");
return fail(ec, "write");
if(! keep_alive) if (!keep_alive) {
{
// This means we should close the connection, usually because // This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic. // the response indicated the "Connection: close" semantic.
return do_close(); return do_close();
@ -298,9 +246,7 @@ public:
do_read(); do_read();
} }
void void do_close() {
do_close()
{
// Send a TCP shutdown // Send a TCP shutdown
beast::error_code ec; beast::error_code ec;
stream_.socket().shutdown(tcp::socket::shutdown_send, ec); stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
@ -312,90 +258,66 @@ public:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions // Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener> class listener : public std::enable_shared_from_this<listener> {
{
net::io_context& ioc_; net::io_context& ioc_;
tcp::acceptor acceptor_; tcp::acceptor acceptor_;
std::shared_ptr<std::string const> doc_root_; std::shared_ptr<std::string const> doc_root_;
public: public:
listener( listener(net::io_context& ioc, tcp::endpoint endpoint,
net::io_context& ioc,
tcp::endpoint endpoint,
std::shared_ptr<std::string const> const& doc_root) std::shared_ptr<std::string const> const& doc_root)
: ioc_(ioc) : ioc_(ioc)
, acceptor_(net::make_strand(ioc)) , acceptor_(net::make_strand(ioc))
, doc_root_(doc_root) , doc_root_(doc_root) {
{
beast::error_code ec; beast::error_code ec;
// Open the acceptor // Open the acceptor
acceptor_.open(endpoint.protocol(), ec); acceptor_.open(endpoint.protocol(), ec);
if(ec) if (ec) {
{
fail(ec, "open"); fail(ec, "open");
return; return;
} }
// Allow address reuse // Allow address reuse
acceptor_.set_option(net::socket_base::reuse_address(true), ec); acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if(ec) if (ec) {
{
fail(ec, "set_option"); fail(ec, "set_option");
return; return;
} }
// Bind to the server address // Bind to the server address
acceptor_.bind(endpoint, ec); acceptor_.bind(endpoint, ec);
if(ec) if (ec) {
{
fail(ec, "bind"); fail(ec, "bind");
return; return;
} }
// Start listening for connections // Start listening for connections
acceptor_.listen( acceptor_.listen(net::socket_base::max_listen_connections, ec);
net::socket_base::max_listen_connections, ec); if (ec) {
if(ec)
{
fail(ec, "listen"); fail(ec, "listen");
return; return;
} }
} }
// Start accepting incoming connections // Start accepting incoming connections
void void run() { do_accept(); }
run()
{
do_accept();
}
private: private:
void void do_accept() {
do_accept()
{
// The new connection gets its own strand // The new connection gets its own strand
acceptor_.async_accept( acceptor_.async_accept(net::make_strand(ioc_),
net::make_strand(ioc_),
beast::bind_front_handler( beast::bind_front_handler(
&listener::on_accept, &listener::on_accept, shared_from_this()));
shared_from_this()));
} }
void void on_accept(beast::error_code ec, tcp::socket socket) {
on_accept(beast::error_code ec, tcp::socket socket) if (ec) {
{
if(ec)
{
fail(ec, "accept"); fail(ec, "accept");
return; // To avoid infinite loop return; // To avoid infinite loop
} } else {
else
{
// Create the session and run it // Create the session and run it
std::make_shared<session>( std::make_shared<session>(std::move(socket), doc_root_)->run();
std::move(socket),
doc_root_)->run();
} }
// Accept another connection // Accept another connection
@ -404,5 +326,3 @@ private:
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -15,6 +15,14 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
cc_library(
name = "server",
hdrs = glob(["server/**/*.h"]),
includes = ["."],
deps = ["@expected", "//include:path"],
visibility = ["//visibility:public"],
)
cc_library( cc_library(
name = "proto", name = "proto",
hdrs = glob(["proto/**/*.h"]), hdrs = glob(["proto/**/*.h"]),

View File

@ -19,16 +19,15 @@
#ifndef logging_982a89e400976f59 #ifndef logging_982a89e400976f59
#define logging_982a89e400976f59 #define logging_982a89e400976f59
#include "logging/formatters.h"
#include "logging/theme.h"
#include "logging/traits.h"
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "logging/formatters.h"
#include "logging/theme.h"
#include "logging/traits.h"
namespace parselink { namespace parselink {
namespace logging { namespace logging {
@ -51,7 +50,9 @@ struct message {
struct endpoint { struct endpoint {
virtual ~endpoint() = default; virtual ~endpoint() = default;
virtual std::span<char> buffer() { return {}; } virtual std::span<char> buffer() { return {}; }
virtual void write(message const& msg) = 0; virtual void write(message const& msg) = 0;
virtual bool colored() const noexcept = 0; virtual bool colored() const noexcept = 0;
level threshold{default_threshold}; level threshold{default_threshold};
@ -64,8 +65,7 @@ public:
// This constructor allows for an arbitrary number of logging endpoints // This constructor allows for an arbitrary number of logging endpoints
// to be passed in. // to be passed in.
logger(std::string_view name, logger(std::string_view name, std::vector<std::shared_ptr<endpoint>> eps);
std::vector<std::shared_ptr<endpoint>> eps);
template <typename... Endpoints> template <typename... Endpoints>
explicit logger(std::string_view name, Endpoints&&... eps) explicit logger(std::string_view name, Endpoints&&... eps)
@ -74,7 +74,7 @@ public:
template <level Level, typename... Args> template <level Level, typename... Args>
requires(Level <= static_threshold) requires(Level <= static_threshold)
void log(fmt::format_string<Args...> format, Args&&... args) const { void log(fmt::format_string<Args...> format, Args&&... args) const {
try_write(Level, format.get(), std::forward<Args>(args)...); do_write(Level, format.get(), std::forward<Args>(args)...);
} }
template <level Level, typename... Args> template <level Level, typename... Args>
@ -83,8 +83,10 @@ public:
#define LOG_API(lvl) \ #define LOG_API(lvl) \
template <typename... Args> \ template <typename... Args> \
[[gnu::always_inline]] void lvl(fmt::format_string<Args...>&& format, Args&&... args) const { \ [[gnu::always_inline]] void lvl( \
log<level::lvl>(std::forward<decltype(format)>(format), std::forward<Args>(args)...); \ fmt::format_string<Args...>&& format, Args&&... args) const { \
log<level::lvl>(std::forward<decltype(format)>(format), \
std::forward<Args>(args)...); \
} }
LOG_API(critical); LOG_API(critical);
@ -100,7 +102,6 @@ public:
void set_threshold(level new_threshold) noexcept; void set_threshold(level new_threshold) noexcept;
private: private:
template <typename... Args> template <typename... Args>
void write_endpoint(std::shared_ptr<endpoint> const& endpoint, message msg, void write_endpoint(std::shared_ptr<endpoint> const& endpoint, message msg,
fmt::string_view format, Args&&... args) const { fmt::string_view format, Args&&... args) const {
@ -114,16 +115,15 @@ private:
endpoint->write(msg); endpoint->write(msg);
} else { } else {
// Fill the static buffer. // Fill the static buffer.
fmt::vformat_to(buff.begin(), format, fmt::vformat_to(
fmt::make_format_args(args...)); buff.begin(), format, fmt::make_format_args(args...));
msg.message = std::string_view{buff.data(), buff.size()}; msg.message = std::string_view{buff.data(), buff.size()};
endpoint->write(msg); endpoint->write(msg);
} }
} }
template <typename... Args> template <typename... Args>
void try_write(level level, fmt::string_view format, void do_write(level level, fmt::string_view format, Args&&... args) const {
Args&&... args) const {
message msg{level, std::chrono::system_clock::now(), name_}; message msg{level, std::chrono::system_clock::now(), name_};
for (auto const& ep : endpoints_) { for (auto const& ep : endpoints_) {
if (level > ep->threshold) continue; if (level > ep->threshold) continue;

View File

@ -20,13 +20,12 @@
#ifndef logging_formatters_d22a64b1645a8134 #ifndef logging_formatters_d22a64b1645a8134
#define logging_formatters_d22a64b1645a8134 #define logging_formatters_d22a64b1645a8134
#include "traits.h"
#include "theme.h" #include "theme.h"
#include "traits.h"
#include <magic_enum.hpp>
#include <tl/expected.hpp> #include <tl/expected.hpp>
#include <magic_enum.hpp>
// Simple wrapper for error strings to be formatted like any other string_view. // Simple wrapper for error strings to be formatted like any other string_view.
template <> template <>
struct fmt::formatter<parselink::logging::error_str> struct fmt::formatter<parselink::logging::error_str>
@ -50,8 +49,7 @@ struct fmt::formatter<E> : fmt::formatter<std::string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(E const v, FormatContext& ctx) const { auto format(E const v, FormatContext& ctx) const {
auto str = [v] { auto str = [v] {
return fmt::format("{}::{}", return fmt::format("{}::{}", magic_enum::enum_type_name<E>(),
magic_enum::enum_type_name<E>(),
magic_enum::enum_name(v)); magic_enum::enum_name(v));
}(); }();
return fmt::formatter<std::string_view>::format(str, ctx); return fmt::formatter<std::string_view>::format(str, ctx);
@ -70,6 +68,7 @@ requires std::is_enum_v<E>
struct fmt::formatter<parselink::logging::enum_name_only<E>> struct fmt::formatter<parselink::logging::enum_name_only<E>>
: fmt::formatter<std::string_view> { : fmt::formatter<std::string_view> {
using enum_name_only = parselink::logging::enum_name_only<E>; using enum_name_only = parselink::logging::enum_name_only<E>;
template <typename FormatContext> template <typename FormatContext>
auto format(enum_name_only const v, FormatContext& ctx) const { auto format(enum_name_only const v, FormatContext& ctx) const {
return fmt::formatter<std::string_view>::format( return fmt::formatter<std::string_view>::format(
@ -119,7 +118,6 @@ struct fmt::formatter<T> : fmt::formatter<void const*> {
} }
}; };
// TODO(ksassenrath): Re-enable when expected has been integrated
template <typename T, typename Err> template <typename T, typename Err>
struct fmt::formatter<tl::expected<T, Err>> { struct fmt::formatter<tl::expected<T, Err>> {
template <typename ParseContext> template <typename ParseContext>
@ -141,8 +139,8 @@ struct fmt::formatter<tl::expected<T, Err>> {
// Support format_arg wrappers, which will be used to colorize output. // Support format_arg wrappers, which will be used to colorize output.
template <typename T> template <typename T>
struct fmt::formatter<parselink::logging::format_arg<T>> : struct fmt::formatter<parselink::logging::format_arg<T>>
fmt::formatter<typename parselink::logging::format_arg<T>::type> { : fmt::formatter<typename parselink::logging::format_arg<T>::type> {
using format_arg_type = parselink::logging::format_arg<T>; using format_arg_type = parselink::logging::format_arg<T>;
using resolved_type = typename format_arg_type::type; using resolved_type = typename format_arg_type::type;

View File

@ -31,35 +31,44 @@
#include "level.h" #include "level.h"
#include "traits.h" #include "traits.h"
#include <span>
#include <tl/expected.hpp>
#include <fmt/color.h>
#include <system_error> #include <system_error>
#include <type_traits> #include <type_traits>
#include <fmt/chrono.h>
#include <fmt/color.h>
#include <tl/expected.hpp>
namespace parselink { namespace parselink {
namespace logging { namespace logging {
template <typename T> template <typename T>
struct format_arg { struct format_arg {
using type = std::decay_t<T>; using type = std::decay_t<T>;
constexpr format_arg(type const& t) : v(t) {}
constexpr format_arg(type const& t)
: v(t) {}
type const& v; type const& v;
}; };
template <std::convertible_to<std::string_view> T> template <std::convertible_to<std::string_view> T>
struct format_arg<T> { struct format_arg<T> {
using type = std::string_view; using type = std::string_view;
constexpr format_arg(T&& t) noexcept : v(t) {}
constexpr format_arg(T&& t) noexcept
: v(t) {}
type v; type v;
}; };
template <detail::smart_pointer T> template <detail::smart_pointer T>
struct format_arg<T> { struct format_arg<T> {
using type = void const*; using type = void const*;
constexpr format_arg(T const& t) noexcept : v(fmt::ptr(t)) {}
constexpr format_arg(T const& t) noexcept
: v(fmt::ptr(t)) {}
void const* v; void const* v;
}; };
@ -98,7 +107,6 @@ struct theme<std::errc> : static_theme<fmt::color::fire_brick> {};
template <> template <>
struct theme<error_str> : static_theme<fmt::color::fire_brick> {}; struct theme<error_str> : static_theme<fmt::color::fire_brick> {};
template <> template <>
struct theme<bool> { struct theme<bool> {
static constexpr auto style(bool l) noexcept { static constexpr auto style(bool l) noexcept {
@ -108,6 +116,7 @@ struct theme<bool> {
template <> template <>
struct theme<enum_name_only<logging::level>> { struct theme<enum_name_only<logging::level>> {
// clang-format off
constexpr static fmt::color colors[] = { constexpr static fmt::color colors[] = {
fmt::color::black, fmt::color::black,
fmt::color::red, fmt::color::red,
@ -116,22 +125,24 @@ struct theme<enum_name_only<logging::level>> {
fmt::color::light_sky_blue, fmt::color::light_sky_blue,
fmt::color::lime_green, fmt::color::lime_green,
fmt::color::pink, fmt::color::pink,
fmt::color::slate_gray fmt::color::slate_gray };
};
// clang-format on
static constexpr auto style(auto l) noexcept { static constexpr auto style(auto l) noexcept {
return fmt::fg(*std::next(std::begin(colors), size_t(l.v))); return fmt::fg(*std::next(std::begin(colors), size_t(l.v)));
} }
}; };
template <typename T> template <typename T>
concept has_static_theme = concept has_static_theme = std::convertible_to<fmt::text_style,
std::convertible_to<fmt::text_style,
decltype(theme<std::remove_cvref_t<T>>::style)>; decltype(theme<std::remove_cvref_t<T>>::style)>;
template <typename T> template <typename T>
concept has_dynamic_theme = requires(T const& t) { concept has_dynamic_theme = requires(T const& t) {
{ theme<std::remove_cvref_t<T>>::style(t) } {
-> std::convertible_to<fmt::text_style>; theme<std::remove_cvref_t<T>>::style(t)
} -> std::convertible_to<fmt::text_style>;
}; };
template <typename T> template <typename T>
@ -156,11 +167,11 @@ constexpr auto get_theme(T const& value) {
return theme<T>::style(value); return theme<T>::style(value);
} }
[[gnu::always_inline]] constexpr auto styled(fmt::text_style const& style, [[gnu::always_inline]] constexpr auto styled(
auto out) { fmt::text_style const& style, auto out) {
if (style.has_foreground()) { if (style.has_foreground()) {
auto foreground = auto foreground = fmt::detail::make_foreground_color<char>(
fmt::detail::make_foreground_color<char>(style.get_foreground()); style.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out); out = std::copy(foreground.begin(), foreground.end(), out);
} }
return out; return out;

View File

@ -19,8 +19,8 @@
#ifndef logging_traits_34e410874c0179c6 #ifndef logging_traits_34e410874c0179c6
#define logging_traits_34e410874c0179c6 #define logging_traits_34e410874c0179c6
#include <type_traits>
#include <memory> #include <memory>
#include <type_traits>
namespace parselink { namespace parselink {
namespace logging { namespace logging {
@ -45,7 +45,9 @@ enum_name_only(E) -> enum_name_only<E>;
// a normal string. // a normal string.
struct error_str { struct error_str {
template <std::convertible_to<std::string_view> T> template <std::convertible_to<std::string_view> T>
error_str(T&& t) : v(std::forward<T>(t)) {} error_str(T&& t)
: v(std::forward<T>(t)) {}
std::string_view v; std::string_view v;
}; };

View File

@ -0,0 +1,288 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for packing various types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_builtin_packable_types_41bd88d512497527
#define msgpack_core_detail_builtin_packable_types_41bd88d512497527
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <algorithm>
#include <limits>
#include <ranges>
namespace msgpack {
namespace detail {
// This is a generic helper function for writing integral bytes
template <typename T, typename Itr>
constexpr auto pack_integral(T value, Itr out, std::size_t sz) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
struct dummy_packer {
std::byte* out;
};
// 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>;
} // namespace detail
struct builtin_packer_base {
static constexpr std::size_t rep_size(auto const&) noexcept { return 0; }
static constexpr std::size_t len_size(auto const& value) noexcept {
return 0;
}
static constexpr auto total_size(auto const& value) noexcept {
return 1 + len_size(value) + rep_size(value);
}
};
template <typename>
struct builtin_packer;
////////////////////////////////////////////////////////////////////////////////
// Built-in packer specializations
////////////////////////////////////////////////////////////////////////////////
template <>
struct builtin_packer<invalid> : builtin_packer_base {
static constexpr void pack(auto, auto& packer) noexcept {
*(packer.out)++ = format::invalid::marker;
}
};
template <>
struct builtin_packer<nil> : builtin_packer_base {
static constexpr void pack(auto, auto& packer) noexcept {
*(packer.out)++ = format::nil::marker;
}
};
template <>
struct builtin_packer<bool> : builtin_packer_base {
static constexpr void pack(bool value, auto& packer) noexcept {
*(packer.out)++ =
format::boolean::marker | static_cast<std::byte>(value);
}
};
template <std::unsigned_integral T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr std::size_t rep_size(std::uint64_t value) noexcept {
if (value < 128) return 0;
auto bytes_needed =
static_cast<std::uint64_t>((std::bit_width(value) + 7) >> 3);
return std::bit_ceil(bytes_needed);
}
static constexpr std::byte marker(std::uint64_t value) noexcept {
switch (rep_size(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::uint8::marker;
case 2: return format::uint16::marker;
case 4: return format::uint32::marker;
default: return format::uint64::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(value, packer.out, rep_size(value));
}
};
template <std::signed_integral T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr std::size_t rep_size(std::int64_t value) noexcept {
// Probably a better way to do this; fixints
if (value < 128 && value >= -32) return 0;
auto underlying = static_cast<std::uint64_t>(value);
// save a branch; these should be cheap on modern hardware.
std::uint64_t counts[2] = {
static_cast<std::uint64_t>(std::countl_zero(underlying)),
static_cast<std::uint64_t>(std::countl_one(underlying))};
std::uint64_t width = 1 + std::numeric_limits<std::uint64_t>::digits
- counts[underlying >> 63];
return std::bit_ceil((width + 7) >> 3);
}
static constexpr std::byte marker(std::int64_t value) noexcept {
switch (rep_size(value)) {
case 0: return static_cast<std::byte>(value);
case 1: return format::int8::marker;
case 2: return format::int16::marker;
case 4: return format::int32::marker;
default: return format::int64::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(value, packer.out, rep_size(value));
}
};
template <detail::byte_range T>
struct builtin_packer<T> : builtin_packer_base {
static constexpr uint32_t max_payload_length = 4;
static constexpr std::size_t rep_size(T const& value) noexcept {
return std::ranges::size(value);
}
static constexpr std::size_t len_size(T const& value) noexcept {
auto const len = rep_size(value);
auto const rounded_bytes = std::bit_ceil(static_cast<std::uint32_t>(
((std::bit_width(std::ranges::size(value)) + 7) >> 3)));
return std::min(max_payload_length, rounded_bytes);
}
static constexpr auto marker(T const& value) noexcept {
switch (len_size(value)) {
case 1: return format::bin8::marker;
case 2: return format::bin16::marker;
case 4:
default: return format::bin32::marker;
}
}
static constexpr void pack(T const& value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out = detail::pack_integral(
std::ranges::size(value), packer.out, len_size(value));
packer.out = std::ranges::copy(value, packer.out).out;
}
};
template <>
struct builtin_packer<std::string_view> : builtin_packer_base {
static constexpr uint32_t max_payload_length = 4;
static constexpr std::size_t rep_size(std::string_view value) noexcept {
return std::size(value);
}
static constexpr std::size_t len_size(std::string_view value) noexcept {
auto const len = std::size(value);
if (len <= static_cast<std::uint32_t>(format::fixstr::mask)) return 0;
return std::min(max_payload_length,
std::bit_ceil(std::uint32_t((std::bit_width(len) + 7) >> 3)));
}
static constexpr auto marker(std::string_view value) noexcept {
switch (len_size(value)) {
case 0:
return format::fixstr::marker
| static_cast<std::byte>(rep_size(value));
case 1: return format::str8::marker;
case 2: return format::str16::marker;
case 4:
default: return format::str32::marker;
}
}
static constexpr void pack(std::string_view value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
auto const size = std::size(value);
packer.out = detail::pack_integral(size, packer.out, len_size(value));
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
packer.out = std::ranges::copy(beg, beg + size, packer.out).out;
}
};
// All things that convert to std::string_view should use the string_view
// overload of builtin_packer.
template <std::convertible_to<std::string_view> T>
struct builtin_packer<T> : builtin_packer<std::string_view> {};
template <>
struct builtin_packer<array_desc> : builtin_packer_base {
static constexpr std::size_t rep_size(array_desc value) noexcept {
auto const len = value.count;
if (len <= static_cast<std::size_t>(format::fixarray::mask)) {
return 0;
}
if (len < 0x10000) return 2;
return 4;
}
static constexpr auto marker(array_desc value) noexcept {
switch (rep_size(value)) {
case 0:
return format::fixarray::marker
| static_cast<std::byte>(value.count);
case 2: return format::array16::marker;
case 4:
default: return format::array32::marker;
}
}
static constexpr auto pack(array_desc value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out =
detail::pack_integral(value.count, packer.out, rep_size(value));
}
};
template <>
struct builtin_packer<map_desc> : builtin_packer_base {
static constexpr std::size_t rep_size(map_desc value) noexcept {
auto const len = value.count;
if (len <= static_cast<std::size_t>(format::fixmap::mask)) {
return 0;
}
if (len < 0x10000) return 2;
return 4;
}
static constexpr auto marker(map_desc value) noexcept {
switch (rep_size(value)) {
case 0:
return format::fixmap::marker
| static_cast<std::byte>(value.count);
case 2: return format::map16::marker;
case 4:
default: return format::map32::marker;
}
}
static constexpr void pack(map_desc value, auto& packer) noexcept {
*(packer.out)++ = marker(value);
packer.out =
detail::pack_integral(value.count, packer.out, rep_size(value));
}
};
} // namespace msgpack
#endif // msgpack_core_detail_builtin_packable_types_41bd88d512497527

View File

@ -0,0 +1,347 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for unpacking various types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8
#define msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8
#include "parselink/msgpack/core/detail/unpackable_concepts.h"
#include "parselink/msgpack/core/error.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <tl/expected.hpp>
#include <algorithm>
#include <limits>
#include <ranges>
namespace msgpack {
namespace detail {
// Generic mechanism for reading a number of bytes out of an interator.
template <std::size_t N, typename Itr>
constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept {
std::array<std::byte, N> out;
auto const end = inp + N;
auto dst = out.begin();
while (inp != end) {
*dst = std::byte{*inp};
++dst;
++inp;
}
return out;
}
// This is a generic helper function for writing integral bytes
template <std::integral T, typename Itr>
constexpr decltype(auto) unpack_integral(Itr& inp) noexcept {
constexpr auto N = sizeof(T);
if constexpr (sizeof(T) == 1) {
return T(*inp++);
} else {
return be_to_host(::detail::value_cast<T>(read_bytes<N>(inp)));
}
}
// Defines a range of bytes for binary formats.
template <typename T>
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 {
if constexpr (is_fixtype<F>) {
// Don't advance the iterator -- part of the format is locked away
// within the format byte.
return (std::byte{*itr} & ~F::mask) == F::marker;
} else {
// Advance the iterator past the format byte.
return std::byte{*itr++} == F::marker;
}
}
// This helper structure provides a customization point for converting an
// iterator to some other type (e.g. a pointer) in the event that the
// final format does not have a suitable constructor. It's only currently
// used for string_view, but may be overridden on platforms that provide
// their own types.
template <typename T, typename Iter>
struct iterator_adapter {
static constexpr auto convert(Iter itr) { return itr; }
};
template <typename Iter>
struct iterator_adapter<std::string_view, Iter> {
static constexpr auto convert(Iter itr) {
return ::detail::value_cast<std::string_view::pointer>(&*itr);
}
};
// Using information expressed within each format type, this singular template
// can instantiate the unpacking of most formats. Notable exceptions are
// the array/map types and extension types.
template <format_type F>
constexpr inline auto unpack_format(auto& unpacker) noexcept
-> tl::expected<typename F::value_type, error> {
if (!accept_format<F>(unpacker.in)) {
return tl::make_unexpected(error::wrong_type);
}
// First thing's first. Read the format's "first_type", which is either
// the payload or the length of the payload. Ensure we have enough data
// from the span before we do so, though.
using first_type = typename F::first_type;
using value_type = typename F::value_type;
using diff_type = decltype(unpacker.remaining());
if (unpacker.remaining() < diff_type{sizeof(first_type)}) {
return tl::make_unexpected(error::incomplete_message);
}
auto f = unpack_integral<first_type>(unpacker.in);
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
f &= decltype(f)(F::mask);
}
if constexpr (F::payload_type == format::payload::fixed) {
// Prefer constructions that utilize the unpacked value, but allow
// tag types (e.g. invalid, nil) to be directly constructed.
if constexpr (std::constructible_from<value_type, decltype(f)>) {
return value_type(f);
} else {
return value_type{};
}
} else {
// We're reading a variable length payload. `f` is the length of the
// payload. Ensure that the span has enough data and construct the
// value_type accordingly.
if (unpacker.remaining() < diff_type(f)) {
return tl::make_unexpected(error::incomplete_message);
}
using adapt = iterator_adapter<value_type, decltype(unpacker.in)>;
auto value = value_type(adapt::convert(unpacker.in), f);
unpacker.in += f;
return value;
}
}
} // namespace detail
template <typename>
struct builtin_unpacker;
////////////////////////////////////////////////////////////////////////////////
// Built-in unpacker specializations
////////////////////////////////////////////////////////////////////////////////
template <>
struct builtin_unpacker<invalid> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::invalid>(unpacker);
}
};
template <>
struct builtin_unpacker<nil> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::nil>(unpacker);
}
};
template <>
struct builtin_unpacker<bool> {
static constexpr auto unpack(auto& unpacker) noexcept {
return detail::unpack_format<format::boolean>(unpacker);
}
};
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
-> tl::expected<std::uint64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
}
switch (marker) {
case format::uint8::marker:
return detail::unpack_format<format::uint8>(unpacker);
case format::uint16::marker:
return detail::unpack_format<format::uint16>(unpacker);
case format::uint32::marker:
return detail::unpack_format<format::uint32>(unpacker);
case format::uint64::marker:
return detail::unpack_format<format::uint64>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::unsigned_integral T>
struct builtin_unpacker<T> {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return unsigned_int_unpacker::unpack(unpacker).and_then(convert);
}
};
struct signed_int_unpacker {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::int64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
} else if ((marker & std::byte{0xe0}) == std::byte{0xe0}) {
return detail::unpack_format<format::negative_fixint>(unpacker);
}
switch (marker) {
case format::int8::marker:
return detail::unpack_format<format::int8>(unpacker);
case format::int16::marker:
return detail::unpack_format<format::int16>(unpacker);
case format::int32::marker:
return detail::unpack_format<format::int32>(unpacker);
case format::int64::marker:
return detail::unpack_format<format::int64>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::signed_integral T>
struct builtin_unpacker<T> {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto min = std::numeric_limits<T>::min();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max || value < min) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return signed_int_unpacker::unpack(unpacker).and_then(convert);
}
};
template <>
struct builtin_unpacker<std::string_view> {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::string_view, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & ~format::fixstr::mask) == format::fixstr::marker) {
return detail::unpack_format<format::fixstr>(unpacker);
}
switch (marker) {
case format::str8::marker:
return detail::unpack_format<format::str8>(unpacker);
case format::str16::marker:
return detail::unpack_format<format::str16>(unpacker);
case format::str32::marker:
return detail::unpack_format<format::str32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>>)
struct builtin_unpacker<std::span<T, Extent>> {
template <std::size_t E = Extent>
using expected_type = tl::expected<std::span<T, E>, msgpack::error>;
static constexpr auto unpack_bytes(auto& unpacker) noexcept
-> expected_type<std::dynamic_extent> {
auto marker = *(unpacker.in);
switch (marker) {
case format::bin8::marker:
return detail::unpack_format<format::bin8>(unpacker);
case format::bin16::marker:
return detail::unpack_format<format::bin16>(unpacker);
case format::bin32::marker:
return detail::unpack_format<format::bin32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
static constexpr expected_type<> unpack(auto& unpacker) noexcept {
auto ensure_extent = [](std::span<std::byte const> value) noexcept
-> expected_type<> {
if constexpr (Extent != std::dynamic_extent) {
if (value.size() != Extent) {
return tl::make_unexpected(msgpack::error::wrong_length);
}
return value.template first<Extent>();
} else {
return value;
}
};
return unpack_bytes(unpacker).and_then(ensure_extent);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>> && Extent > 0)
struct builtin_unpacker<std::array<T, Extent>> {
using expected_type = tl::expected<std::array<T, Extent>, msgpack::error>;
static constexpr expected_type unpack(auto& unpacker) noexcept {
constexpr auto copy_to_array = [](auto span) noexcept -> expected_type {
if (span.size() != Extent) {
return tl::make_unexpected(msgpack::error::wrong_length);
} else {
std::array<std::byte, Extent> dest;
std::copy(span.begin(), span.end(), dest.begin());
return dest;
}
};
return builtin_unpacker<std::span<std::byte const>>::unpack(unpacker)
.and_then(copy_to_array);
}
};
} // namespace msgpack
#endif // msgpack_core_detail_builtin_unpackable_types_bf5de632f088a7d8

View File

@ -0,0 +1,85 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Concepts used for types that are packable by a packer.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_packable_concepts_397b5e0ae6de61bf
#define msgpack_core_detail_packable_concepts_397b5e0ae6de61bf
#include <concepts>
#include <cstddef>
#include <type_traits>
namespace msgpack {
namespace detail {
// This structure models a minimal packer context. It is used to validate
// whether a particular type is considered packable or not.
struct packer_context_model {
std::byte* out;
};
} // namespace detail
// Packers with built-in support will specialize builtin_packer.
template <typename>
struct builtin_packer;
// Packers
template <typename>
struct packer_adapter;
template <typename T, template <typename> typename Packer,
typename Context = detail::packer_context_model>
concept packable_with = requires(T&& value, Context& dest) {
{
Packer<std::remove_cvref_t<T>>::total_size(value)
} -> std::same_as<std::size_t>;
{
Packer<std::remove_cvref_t<T>>::pack(value, dest)
} -> std::same_as<void>;
};
template <typename T>
concept custom_packable = packable_with<T, packer_adapter>;
template <typename T>
concept builtin_packable = packable_with<T, builtin_packer>;
template <typename T>
concept packable = builtin_packable<T> || custom_packable<T>;
namespace detail {
template <typename>
struct packer_impl_s;
template <custom_packable T>
struct packer_impl_s<T> {
using type = packer_adapter<T>;
};
template <builtin_packable T>
struct packer_impl_s<T> {
using type = builtin_packer<T>;
};
template <typename T>
using packer_impl = packer_impl_s<T>::type;
} // namespace detail
} // namespace msgpack
#endif // msgpack_core_detail_packable_concepts_397b5e0ae6de61bf

View File

@ -0,0 +1,104 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Support for packing ranges of packable types into array/map formats.
//
// If the range size is known at compile time or can be queried at runtime, it
// will be leveraged to find the most efficient version of the format.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_packable_ranges_10d28c24498828c7
#define msgpack_core_detail_packable_ranges_10d28c24498828c7
#include "parselink/msgpack/core/detail/builtin_packable_types.h"
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include <ranges>
#include <tuple>
namespace msgpack {
namespace detail {
//
template <typename T>
concept packable_array =
std::ranges::input_range<T> && !std::convertible_to<T, std::string_view>
&& packable<std::ranges::range_value_t<T>>;
template <typename T>
concept key_value_pairlike = requires(T t) {
typename std::tuple_size<T>::type;
} && std::tuple_size_v<T> == 2;
template <typename T>
concept packable_map =
std::ranges::input_range<T>
&& key_value_pairlike<std::ranges::range_value_t<T>>
&& builtin_packable<
std::tuple_element_t<0, std::ranges::range_value_t<T>>>
&& builtin_packable<
std::tuple_element_t<1, std::ranges::range_value_t<T>>>;
template <typename>
struct desc_traits;
template <packable_map T>
struct desc_traits<T> {
using type = map_desc;
};
template <packable_array T>
struct desc_traits<T> {
using type = array_desc;
};
} // namespace detail
template <typename T>
concept packable_range = detail::packable_array<T> || detail::packable_map<T>;
template <packable_range T>
struct builtin_packer<T> : builtin_packer_base {
using desc_type = detail::desc_traits<T>::type;
static constexpr std::size_t rep_size(T const& value) {
return std::ranges::size(value);
}
static constexpr auto desc(T const& value) {
return desc_type(rep_size(value));
}
static constexpr auto marker(T const& value) {
return builtin_packer<desc_type>::marker(desc(value));
}
static constexpr void pack(T const& value, auto& packer) noexcept {
packer.pack(desc(value));
if constexpr (detail::packable_map<T>) {
for (auto const& [key, value] : value) {
packer.pack(key);
packer.pack(value);
}
} else {
for (auto const& element : value) {
packer.pack(element);
}
}
}
};
} // namespace msgpack
#endif // msgpack_core_detail_packable_ranges_10d28c24498828c7

View File

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Concepts used for types that are deserializable by an unpacker.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_detail_unpackable_concepts_cf142e37b34a598f
#define msgpack_core_detail_unpackable_concepts_cf142e37b34a598f
#include "parselink/msgpack/core/error.h"
#include <tl/expected.hpp>
#include <concepts>
#include <cstddef>
#include <type_traits>
namespace msgpack {
namespace detail {
// This structure models a minimal packer context. It is used to validate
// whether a particular type is considered packable or not.
struct unpacker_context_model {
std::byte* in;
std::size_t remaining() { return 1; }
};
} // namespace detail
// Datatypes with built-in support will specialize builtin_unpacker.
template <typename>
struct builtin_unpacker;
// Datatypes can have a custom adapter for unpacking. This _can_ be used to
// override default behavior if necessary, but its intended for custom types.
template <typename>
struct unpacker_adapter;
template <typename T, typename... U>
concept is_any_of = (std::same_as<T, U> || ...);
template <typename T, template <typename> typename Unpacker,
typename Context = detail::unpacker_context_model>
concept unpackable_with = requires(T&& value, Context& dest) {
{
Unpacker<std::remove_cvref_t<T>>::unpack(dest)
} -> is_any_of<T, tl::expected<T, msgpack::error>>;
};
template <typename T>
concept custom_unpackable = unpackable_with<T, unpacker_adapter>;
template <typename T>
concept builtin_unpackable = unpackable_with<T, builtin_unpacker>;
template <typename T>
concept supports_unpack = builtin_unpackable<T> || custom_unpackable<T>;
namespace detail {
template <typename>
struct unpacker_impl_s;
template <custom_unpackable T>
struct unpacker_impl_s<T> {
using type = unpacker_adapter<T>;
};
template <builtin_unpackable T>
struct unpacker_impl_s<T> {
using type = builtin_unpacker<T>;
};
template <typename T>
using unpacker_impl = unpacker_impl_s<T>::type;
} // namespace detail
} // namespace msgpack
#endif // msgpack_core_detail_unpackable_concepts_cf142e37b34a598f

View File

@ -29,6 +29,7 @@ enum class error {
wrong_type, // The format is incompatible with requested type. wrong_type, // The format is incompatible with requested type.
bad_value, // Value does not fit within constraints. bad_value, // Value does not fit within constraints.
will_truncate, // Integer return type is smaller than stored value. will_truncate, // Integer return type is smaller than stored value.
wrong_length, // Variable-length format does not match desired length.
}; };
} // namespace msgpack } // namespace msgpack

View File

@ -31,29 +31,37 @@ namespace msgpack {
// Supporting types/tags for formats // Supporting types/tags for formats
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
struct nil { struct nil {
nil() = default; constexpr nil() noexcept = default;
// This constructor is used by the reader implementation. constexpr bool operator==(nil const& other) const noexcept = default;
nil(auto) {}; };
struct invalid {
constexpr invalid() noexcept = default;
constexpr bool operator==(invalid const& other) const noexcept = default;
}; };
struct invalid {};
using boolean = bool;
struct array_desc { struct array_desc {
constexpr array_desc() noexcept = default; constexpr array_desc() noexcept = default;
constexpr array_desc(auto sz) noexcept : count(std::size_t(sz)) {}; constexpr array_desc(auto sz) noexcept
: count(std::size_t(sz)){};
// This constructor is implemented for use by reader // This constructor is implemented for use by reader
constexpr array_desc(auto, auto sz) noexcept : count(std::size_t(sz)) {}; constexpr array_desc(auto, auto sz) noexcept
: count(std::size_t(sz)){};
constexpr bool operator==(array_desc const& other) const noexcept { constexpr bool operator==(array_desc const& other) const noexcept {
return count == other.count; return count == other.count;
} }
std::size_t count = 0; std::size_t count = 0;
}; };
struct map_desc { struct map_desc {
constexpr map_desc() noexcept = default; constexpr map_desc() noexcept = default;
constexpr map_desc(auto sz) noexcept : count(std::size_t(sz)) {}; constexpr map_desc(auto sz) noexcept
: count(std::size_t(sz)){};
// This constructor is implemented for use by reader // This constructor is implemented for use by reader
constexpr map_desc(auto, auto sz) noexcept : count(std::size_t(sz)) {}; constexpr map_desc(auto, auto sz) noexcept
: count(std::size_t(sz)){};
std::size_t count = 0; std::size_t count = 0;
}; };
@ -87,10 +95,7 @@ namespace format {
// //
// In addition, there are "fix" types that embed the literal value or the // In addition, there are "fix" types that embed the literal value or the
// payload length within the marker byte. // payload length within the marker byte.
enum payload : std::uint8_t { enum payload : std::uint8_t { fixed, variable };
fixed,
variable
};
// Base structure for all MessagePack formats to inherit from. // Base structure for all MessagePack formats to inherit from.
struct base_format { struct base_format {
@ -118,8 +123,8 @@ namespace format {
template <std::integral T> template <std::integral T>
struct resolve_token_type<T> { struct resolve_token_type<T> {
constexpr static type value = std::is_signed_v<T> ? constexpr static type value =
type::signed_int : type::unsigned_int; std::is_signed_v<T> ? type::signed_int : type::unsigned_int;
}; };
template <> template <>
@ -194,18 +199,25 @@ namespace format {
struct positive_fixint : fixtype<0x00, 0x7f> { struct positive_fixint : fixtype<0x00, 0x7f> {
constexpr static flag flags{}; constexpr static flag flags{};
}; };
struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> { struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
constexpr static flag flags{}; constexpr static flag flags{};
}; };
struct uint8 : format<0xcc, std::uint8_t> {}; struct uint8 : format<0xcc, std::uint8_t> {};
struct uint16 : format<0xcd, std::uint16_t> {}; struct uint16 : format<0xcd, std::uint16_t> {};
struct uint32 : format<0xce, std::uint32_t> {}; struct uint32 : format<0xce, std::uint32_t> {};
struct uint64 : format<0xcf, std::uint64_t> {}; struct uint64 : format<0xcf, std::uint64_t> {};
struct int8 : format<0xd0, std::int8_t> {}; struct int8 : format<0xd0, std::int8_t> {};
struct int16 : format<0xd1, std::int16_t> {}; struct int16 : format<0xd1, std::int16_t> {};
struct int32 : format<0xd2, std::int32_t> {}; struct int32 : format<0xd2, std::int32_t> {};
struct int64 : format<0xd3, std::int64_t> {}; struct int64 : format<0xd3, std::int64_t> {};
/* /*
@ -213,12 +225,12 @@ namespace format {
*/ */
struct nil : fixtype<0xc0, 0x00> { struct nil : fixtype<0xc0, 0x00> {
using value_type = msgpack::nil; using value_type = msgpack::nil;
constexpr static flag flags{flag::fixed_size | flag::apply_mask}; constexpr static flag flags{flag::fixed_size};
}; };
struct invalid : fixtype<0xc1, 0x00> { struct invalid : fixtype<0xc1, 0x00> {
using value_type = msgpack::invalid; using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size | flag::apply_mask}; constexpr static flag flags{flag::fixed_size};
}; };
struct boolean : fixtype<0xc2, 0x01> { struct boolean : fixtype<0xc2, 0x01> {
@ -230,67 +242,87 @@ namespace format {
* Maps * Maps
*/ */
template <typename Fmt> struct map_format : Fmt { template <typename Fmt>
struct map_format : Fmt {
using value_type = map_desc; using value_type = map_desc;
}; };
struct fixmap : map_format<fixtype<0x80, 0x0f>> {}; struct fixmap : map_format<fixtype<0x80, 0x0f>> {};
struct map16 : map_format<format<0xde, std::uint16_t>> {}; struct map16 : map_format<format<0xde, std::uint16_t>> {};
struct map32 : map_format<format<0xdf, std::uint32_t>> {}; struct map32 : map_format<format<0xdf, std::uint32_t>> {};
/* /*
* Arrays * Arrays
*/ */
template <typename Fmt> struct array_format : Fmt { template <typename Fmt>
struct array_format : Fmt {
using value_type = array_desc; using value_type = array_desc;
}; };
struct fixarray : array_format<fixtype<0x90, 0x0f>> {}; struct fixarray : array_format<fixtype<0x90, 0x0f>> {};
struct array16 : array_format<format<0xdc, std::uint16_t>> {}; struct array16 : array_format<format<0xdc, std::uint16_t>> {};
struct array32 : array_format<format<0xdd, std::uint32_t>> {}; struct array32 : array_format<format<0xdd, std::uint32_t>> {};
/* /*
* Strings * Strings
*/ */
template <typename Fmt> struct string_format : Fmt { template <typename Fmt>
struct string_format : Fmt {
using value_type = std::string_view; using value_type = std::string_view;
constexpr static auto payload_type = payload::variable; constexpr static auto payload_type = payload::variable;
}; };
struct fixstr : string_format<fixtype<0xa0, 0x1f>> {}; struct fixstr : string_format<fixtype<0xa0, 0x1f>> {};
struct str8 : string_format<format<0xd9, std::uint8_t>> {}; struct str8 : string_format<format<0xd9, std::uint8_t>> {};
struct str16 : string_format<format<0xda, std::uint16_t>> {}; struct str16 : string_format<format<0xda, std::uint16_t>> {};
struct str32 : string_format<format<0xdb, std::uint32_t>> {}; struct str32 : string_format<format<0xdb, std::uint32_t>> {};
/* /*
* Binary arrays * Binary arrays
*/ */
template <typename Fmt> struct bin_format : Fmt { template <typename Fmt>
struct bin_format : Fmt {
using value_type = std::span<std::byte const>; using value_type = std::span<std::byte const>;
constexpr static auto payload_type = payload::variable; constexpr static auto payload_type = payload::variable;
}; };
struct bin8 : bin_format<format<0xc4, std::uint8_t>> {}; struct bin8 : bin_format<format<0xc4, std::uint8_t>> {};
struct bin16 : bin_format<format<0xc5, std::uint16_t>> {}; struct bin16 : bin_format<format<0xc5, std::uint16_t>> {};
struct bin32 : bin_format<format<0xc6, std::uint32_t>> {}; struct bin32 : bin_format<format<0xc6, std::uint32_t>> {};
/* /*
* Extension types, not yet supported. * Extension types, not yet supported.
*/ */
template <typename Fmt> struct unimplemented_format : Fmt {}; template <typename Fmt>
struct unimplemented_format : Fmt {};
struct fixext1 : unimplemented_format<fixtype<0xd4, 0x00>> {}; struct fixext1 : unimplemented_format<fixtype<0xd4, 0x00>> {};
struct fixext2 : unimplemented_format<fixtype<0xd5, 0x00>> {}; struct fixext2 : unimplemented_format<fixtype<0xd5, 0x00>> {};
struct fixext4 : unimplemented_format<fixtype<0xd6, 0x00>> {}; struct fixext4 : unimplemented_format<fixtype<0xd6, 0x00>> {};
struct fixext8 : unimplemented_format<fixtype<0xd7, 0x00>> {}; struct fixext8 : unimplemented_format<fixtype<0xd7, 0x00>> {};
struct fixext16 : unimplemented_format<fixtype<0xd8, 0x00>> {}; struct fixext16 : unimplemented_format<fixtype<0xd8, 0x00>> {};
struct ext8 : unimplemented_format<format<0xc7, std::uint8_t>> {}; struct ext8 : unimplemented_format<format<0xc7, std::uint8_t>> {};
struct ext16 : unimplemented_format<format<0xc8, std::uint16_t>> {}; struct ext16 : unimplemented_format<format<0xc8, std::uint16_t>> {};
struct ext32 : unimplemented_format<format<0xc9, std::uint32_t>> {}; struct ext32 : unimplemented_format<format<0xc9, std::uint32_t>> {};
template <typename T> template <typename T>
@ -303,29 +335,34 @@ namespace detail {
// Simple typelist for looking up compatible types. // Simple typelist for looking up compatible types.
// TODO: Use traits to generate the compatibility list automatically? // TODO: Use traits to generate the compatibility list automatically?
template <std::size_t I, typename T> template <std::size_t I, typename T>
struct type_list_entry { using type = T; }; struct type_list_entry {
using type = T;
};
template <typename...> template <typename...>
struct type_list_impl; struct type_list_impl;
template <std::size_t... Is, typename... Ts> template <std::size_t... Is, typename... Ts>
struct type_list_impl<std::index_sequence<Is...>, Ts...> : struct type_list_impl<std::index_sequence<Is...>, Ts...>
type_list_entry<Is, Ts>... { : type_list_entry<Is, Ts>... {
static constexpr auto size = sizeof...(Ts); static constexpr auto size = sizeof...(Ts);
}; };
template <typename T> struct typelist_lookup_tag { using type = T; }; template <typename T>
struct typelist_lookup_tag {
using type = T;
};
template <std::size_t I, typename T> template <std::size_t I, typename T>
typelist_lookup_tag<T> typelist_lookup(type_list_entry<I, T> const&); typelist_lookup_tag<T> typelist_lookup(type_list_entry<I, T> const&);
template <std::size_t I, typename List> template <std::size_t I, typename List>
using type_list_at = typename decltype( using type_list_at =
typelist_lookup<I>(std::declval<List>()))::type; typename decltype(typelist_lookup<I>(std::declval<List>()))::type;
template <typename... Ts> template <typename... Ts>
using type_list = detail::type_list_impl< using type_list =
std::make_index_sequence<sizeof...(Ts)>, Ts...>; detail::type_list_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;
template <typename, typename, template <typename> typename> template <typename, typename, template <typename> typename>
struct filter_type_list; struct filter_type_list;
@ -335,12 +372,14 @@ namespace detail {
using type = type_list<Ts...>; using type = type_list<Ts...>;
}; };
template <typename Head, typename... Tail, typename... Ts, template <typename> typename Predicate> template <typename Head, typename... Tail, typename... Ts,
template <typename> typename Predicate>
struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> { struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> {
using type = typename std::conditional_t< using type = typename std::conditional_t<Predicate<Head>::value,
Predicate<Head>::value, filter_type_list<type_list<Tail...>, type_list<Ts..., Head>,
filter_type_list<type_list<Tail...>, type_list<Ts..., Head>, Predicate>, Predicate>,
filter_type_list<type_list<Tail...>, type_list<Ts...>, Predicate>>::type; filter_type_list<type_list<Tail...>, type_list<Ts...>,
Predicate>>::type;
}; };
template <typename List, template <typename> typename Predicate> template <typename List, template <typename> typename Predicate>
@ -368,14 +407,12 @@ namespace format {
constexpr static auto inst = [] { constexpr static auto inst = [] {
// Fixtypes define the size within the identifier byte, so the // Fixtypes define the size within the identifier byte, so the
// trait size must be zero. // trait size must be zero.
auto size = is_fixtype<Format> ? auto size =
0 : sizeof(typename Format::first_type); is_fixtype<Format> ? 0 : sizeof(typename Format::first_type);
traits inst{ traits inst{.flags = Format::flags,
.flags = Format::flags,
.size = std::uint8_t(size), .size = std::uint8_t(size),
.token_type = resolve_type_v<typename Format::value_type> .token_type = resolve_type_v<typename Format::value_type>};
};
// Add in the fixed_size flag for integral types. // Add in the fixed_size flag for integral types.
if constexpr (std::is_integral_v<typename Format::value_type>) { if constexpr (std::is_integral_v<typename Format::value_type>) {
inst.flags |= flag::fixed_size; inst.flags |= flag::fixed_size;

View File

@ -0,0 +1,113 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer implementation, which aims to deduce the best format to use
// for a given value. For example, if a 32-bit unsigned integer type only
// contains the value 5, a uint32 format would serialize into:
//
// 0xce, 0x00, 0x00, 0x00, 0x05
//
// Instead, the packer will note that this value could be stored in a positive
// fixint, which is simply:
//
// 0x05
//
// The same optimization will be applied to variable-length types, like strings,
// bytes, arrays, and maps.
//
// This flexibility comes at the cost of CPU instructions. For embedded targets,
// writer (to be renamed verbatim_packer in the future) may be a better choice.
//
// Future goals for this particular packer:
// 1. Support containers/ranges seamlessly.
// 2. Support packing of trivial POD structures without an explicit
// pack_adapter.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_packer_1d5939e9c1498568
#define msgpack_core_packer_1d5939e9c1498568
#include "parselink/msgpack/core/detail/packable_concepts.h"
#include "parselink/msgpack/core/detail/builtin_packable_types.h"
#include "parselink/msgpack/core/detail/packable_ranges.h"
#include "parselink/msgpack/core/error.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <tl/expected.hpp>
#include <limits>
#include <type_traits>
namespace msgpack {
class packer {
// Replace with output_range?
using BufferType = std::span<std::byte>;
public:
constexpr packer(BufferType buff)
: buff_(buff)
, curr_(std::begin(buff_))
, end_(std::end(buff_)) {}
struct context {
decltype(std::begin(BufferType{})) out;
decltype(std::begin(BufferType{})) end;
constexpr auto remaining() noexcept {
return std::ranges::distance(out, end);
}
template <packable T>
constexpr tl::expected<tl::monostate, error> pack(T const& v) noexcept {
auto rem = remaining();
using packer_impl = detail::packer_impl<T>;
decltype(rem) needed = packer_impl::total_size(v);
if (needed > rem) {
return tl::make_unexpected(error::out_of_space);
} else {
packer_impl::pack(v, *this);
return tl::monostate{};
}
}
};
template <packable T>
constexpr tl::expected<tl::monostate, error> pack(T&& v) noexcept {
context ctx{curr_, end_};
// If packing is successful, it updates iterators.
auto ret = ctx.pack(std::forward<T>(v));
if (ret) {
curr_ = ctx.out;
}
return ret;
}
constexpr auto tell() const noexcept {
return std::distance(std::begin(buff_), curr_);
}
constexpr auto subspan() const noexcept { return buff_.subspan(0, tell()); }
private:
BufferType buff_;
decltype(std::begin(buff_)) curr_;
decltype(std::end(buff_)) end_;
};
} // namespace msgpack
#endif // msgpack_core_packer_1d5939e9c1498568

View File

@ -18,9 +18,9 @@
#ifndef msgpack_core_reader_6c2f66f02585565 #ifndef msgpack_core_reader_6c2f66f02585565
#define msgpack_core_reader_6c2f66f02585565 #define msgpack_core_reader_6c2f66f02585565
#include "../util/endianness.h"
#include "error.h" #include "error.h"
#include "format.h" #include "format.h"
#include "../util/endianness.h"
#include <tl/expected.hpp> #include <tl/expected.hpp>
@ -35,6 +35,7 @@ namespace msgpack {
*/ */
namespace reader_policy { namespace reader_policy {
struct relaxed {}; // Similar formats are acceptable. struct relaxed {}; // Similar formats are acceptable.
struct strict {}; // Formats must exactly match the caller's request. struct strict {}; // Formats must exactly match the caller's request.
} // namespace reader_policy } // namespace reader_policy
@ -48,7 +49,8 @@ namespace detail {
auto dst = out.begin(); auto dst = out.begin();
while (inp != end) { while (inp != end) {
*dst = std::byte{*inp}; *dst = std::byte{*inp};
++dst; ++inp; ++dst;
++inp;
} }
return out; return out;
} }
@ -64,7 +66,6 @@ namespace detail {
} }
} }
// This helper structure provides a customization point for converting an // This helper structure provides a customization point for converting an
// iterator to some other type (e.g. a pointer) in the event that the // iterator to some other type (e.g. a pointer) in the event that the
// final format does not have a suitable constructor. It's only currently // final format does not have a suitable constructor. It's only currently
@ -78,7 +79,8 @@ namespace detail {
template <typename Iter> template <typename Iter>
struct iterator_adapter<std::string_view, Iter> { struct iterator_adapter<std::string_view, Iter> {
static constexpr auto convert(Iter itr) { static constexpr auto convert(Iter itr) {
return ::detail::value_cast<std::string_view::pointer>(&*itr); } return ::detail::value_cast<std::string_view::pointer>(&*itr);
}
}; };
template <typename T> template <typename T>
@ -100,8 +102,8 @@ namespace detail {
// start with. // start with.
template <format_type F, typename Iter> template <format_type F, typename Iter>
requires(!format::unimplemented<F>::value) requires(!format::unimplemented<F>::value)
constexpr inline auto read(Iter& itr, Iter const end) noexcept -> constexpr inline auto read(Iter& itr, Iter const end) noexcept
expected<typename F::value_type> { -> expected<typename F::value_type> {
// Copy off the iterator. Update it at the end _if_ everything goes // Copy off the iterator. Update it at the end _if_ everything goes
// smoothly. // smoothly.
auto cur = itr; auto cur = itr;
@ -159,14 +161,14 @@ namespace detail {
template <> template <>
struct relaxed_conversions<format::uint16> { struct relaxed_conversions<format::uint16> {
using type_list = format_list<format::uint16, format::uint8, using type_list =
format::positive_fixint>; format_list<format::uint16, format::uint8, format::positive_fixint>;
}; };
template <> template <>
struct relaxed_conversions<format::uint32> { struct relaxed_conversions<format::uint32> {
using type_list = format_list<format::uint32, format::uint16, using type_list = format_list<format::uint32, format::uint16, format::uint8,
format::uint8, format::positive_fixint>; format::positive_fixint>;
}; };
template <> template <>
@ -182,20 +184,20 @@ namespace detail {
template <> template <>
struct relaxed_conversions<format::int16> { struct relaxed_conversions<format::int16> {
using type_list = format_list<format::int16, format::int8, using type_list =
format::negative_fixint>; format_list<format::int16, format::int8, format::negative_fixint>;
}; };
template <> template <>
struct relaxed_conversions<format::int32> { struct relaxed_conversions<format::int32> {
using type_list = format_list<format::int32, format::int16, using type_list = format_list<format::int32, format::int16, format::int8,
format::int8, format::negative_fixint>; format::negative_fixint>;
}; };
template <> template <>
struct relaxed_conversions<format::int64> { struct relaxed_conversions<format::int64> {
using type_list = format_list<format::int64, format::int32, using type_list = format_list<format::int64, format::int32, format::int16,
format::int16, format::int8, format::negative_fixint>; format::int8, format::negative_fixint>;
}; };
template <> template <>
@ -205,8 +207,7 @@ namespace detail {
template <> template <>
struct relaxed_conversions<format::str16> { struct relaxed_conversions<format::str16> {
using type_list = format_list<format::str16, format::str8, using type_list = format_list<format::str16, format::str8, format::fixstr>;
format::fixstr>;
}; };
template <> template <>
@ -216,8 +217,7 @@ namespace detail {
template <> template <>
struct relaxed_conversions<format::map32> { struct relaxed_conversions<format::map32> {
using type_list = format_list<format::map32, format::map16, using type_list = format_list<format::map32, format::map16, format::fixmap>;
format::fixmap>;
}; };
template <typename F> template <typename F>
@ -236,16 +236,14 @@ namespace detail {
} }
template <format_type F, typename Iter> template <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const end) constexpr static decltype(auto) read(Iter& itr, Iter const end) noexcept {
noexcept {
return detail::read<F>(itr, end); return detail::read<F>(itr, end);
}; };
}; };
template <format_type fmt, typename fmtlist, typename Iter, template <format_type fmt, typename fmtlist, typename Iter, std::size_t I = 0>
std::size_t I = 0> constexpr static auto relaxed_read_impl(Iter& itr, Iter const end) noexcept
constexpr static auto relaxed_read_impl(Iter& itr, Iter const end) -> expected<typename fmt::value_type> {
noexcept -> expected<typename fmt::value_type> {
if constexpr (I < fmtlist::size) { if constexpr (I < fmtlist::size) {
using this_format = format_list_at<I, fmtlist>; using this_format = format_list_at<I, fmtlist>;
auto v = detail::read<this_format>(itr, end); auto v = detail::read<this_format>(itr, end);
@ -264,8 +262,7 @@ namespace detail {
template <> template <>
struct reader_policy_traits<reader_policy::relaxed> { struct reader_policy_traits<reader_policy::relaxed> {
template <format_type F, typename Iter> template <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const& end) constexpr static decltype(auto) read(Iter& itr, Iter const& end) noexcept {
noexcept {
if constexpr (has_conversions<F>) { if constexpr (has_conversions<F>) {
using format_list = typename relaxed_conversions<F>::type_list; using format_list = typename relaxed_conversions<F>::type_list;
return relaxed_read_impl<F, format_list, Iter, 0>(itr, end); return relaxed_read_impl<F, format_list, Iter, 0>(itr, end);
@ -281,22 +278,22 @@ namespace detail {
template <typename policy = reader_policy::relaxed> template <typename policy = reader_policy::relaxed>
class reader { class reader {
public: public:
template <typename T> template <typename T>
using expected = detail::expected<T>; using expected = detail::expected<T>;
constexpr reader(std::span<std::byte const> src) : constexpr reader(std::span<std::byte const> src)
data(src), curr(std::begin(data)), end(std::end(data)) {} : data(src)
, curr(std::begin(data))
, end(std::end(data)) {}
template <format_type F> template <format_type F>
constexpr expected<typename F::value_type> read() noexcept { constexpr expected<typename F::value_type> read() noexcept {
if (curr == end) return tl::make_unexpected(error::end_of_message); if (curr == end) return tl::make_unexpected(error::end_of_message);
return detail::reader_policy_traits<policy>::template read<F>(curr, end); return detail::reader_policy_traits<policy>::template read<F>(
curr, end);
} }
constexpr auto pos() const noexcept { constexpr auto pos() const noexcept { return curr; }
return curr;
}
constexpr auto subview() const noexcept { constexpr auto subview() const noexcept {
return std::span<std::byte const>(curr, end); return std::span<std::byte const>(curr, end);

View File

@ -0,0 +1,94 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default unpacker implementation, utilizing pull-style parsing semantics for
// deserializing data with an expected structure akin to a schema.
//
// The default unpacker maintains some flexibility in how data was originally
// packed.
//
// E.g. If a std::uint8_t is requested, it is perfectly valid to read a uint32
// format, provided the stored value is less than 255.
//
// core/reader (to be renamed verbatim_unpacker) can be utilized in scenarios
// where code size is critical and exact type matching is not a considerable
// downside.
//
// For schemaless data, the token API can be used instead.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_unpacker_910ec357d397ac7e
#define msgpack_core_unpacker_910ec357d397ac7e
#include "parselink/msgpack/core/detail/builtin_unpackable_types.h"
#include "parselink/msgpack/core/detail/unpackable_concepts.h"
#include "parselink/msgpack/core/error.h"
#include <tl/expected.hpp>
#include <cstddef>
#include <span>
namespace msgpack {
class unpacker {
using BufferType = std::span<std::byte const>;
public:
constexpr unpacker(BufferType buff)
: buff_(buff)
, curr_(std::begin(buff_))
, end_(std::end(buff_)) {}
// IDK if we need this yet
struct context {
decltype(std::begin(BufferType{})) in;
decltype(std::begin(BufferType{})) end;
constexpr auto remaining() noexcept {
return std::ranges::distance(in, end);
}
template <supports_unpack T>
constexpr tl::expected<T, error> unpack() noexcept {
if (remaining() == 0)
return tl::make_unexpected(error::end_of_message);
using unpacker_impl = detail::unpacker_impl<T>;
return unpacker_impl::unpack(*this);
}
};
template <supports_unpack T>
constexpr tl::expected<T, error> unpack() noexcept {
context ctx{curr_, end_};
auto ret = ctx.unpack<T>();
if (ret) {
curr_ = ctx.in;
}
return ret;
}
constexpr auto remaining() noexcept {
return std::ranges::distance(curr_, end_);
}
private:
BufferType buff_;
decltype(std::begin(buff_)) curr_;
decltype(std::end(buff_)) end_;
};
} // namespace msgpack
#endif // msgpack_core_unpacker_910ec357d397ac7e

View File

@ -18,14 +18,14 @@
#ifndef msgpack_core_writer_ce48a51aa6ed0858 #ifndef msgpack_core_writer_ce48a51aa6ed0858
#define msgpack_core_writer_ce48a51aa6ed0858 #define msgpack_core_writer_ce48a51aa6ed0858
#include <tl/expected.hpp>
#include "../util/endianness.h"
#include "error.h" #include "error.h"
#include "format.h" #include "format.h"
#include "../util/endianness.h"
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <tl/expected.hpp>
namespace msgpack { namespace msgpack {
enum class writer_error { enum class writer_error {
@ -44,29 +44,155 @@ enum class writer_error {
//}; //};
template <std::size_t N, typename Itr> template <std::size_t N, typename Itr>
constexpr inline decltype(auto) write_bytes(std::array<std::byte, N>&& data, constexpr inline decltype(auto) write_bytes(
Itr out) noexcept { std::array<std::byte, N>&& data, Itr out) noexcept {
for (auto b : data) { for (auto b : data) {
*out++ = b; *out++ = b;
} }
return out; return out;
} }
namespace detail {
constexpr std::size_t bytes_needed_for_str(std::uint32_t v) {
if (v <= std::uint32_t(format::fixstr::mask)) return 0;
return std::bit_ceil(std::uint32_t((std::bit_width(v) + 7) >> 3));
}
constexpr std::size_t bytes_needed(std::uint64_t v) {
if (v <= std::uint64_t(format::positive_fixint::mask)) return 0;
return std::bit_ceil(std::uint64_t((std::bit_width(v) + 7) >> 3));
}
constexpr std::size_t bytes_needed(std::int64_t v) {
if (v < 0 && v >= -32) return 0;
auto width = 1 + std::numeric_limits<std::uint64_t>::digits
- (v < 0 ? std::countl_one(std::uint64_t(v))
: std::countl_zero(std::uint64_t(v)));
return std::bit_ceil(std::uint64_t((width + 7) >> 3));
}
constexpr std::byte get_format_signed(auto value) {
switch (bytes_needed(std::int64_t{value})) {
case 0: return std::byte(value);
case 1: return format::int8::marker;
case 2: return format::int16::marker;
case 4: return format::int32::marker;
default: return format::int64::marker;
}
}
constexpr std::byte get_format(auto value) {
switch (bytes_needed(std::uint64_t{value})) {
case 0: return std::byte(value);
case 1: return format::uint8::marker;
case 2: return format::uint16::marker;
case 4: return format::uint32::marker;
default: return format::uint64::marker;
}
}
} // namespace detail
template <typename T> template <typename T>
struct write_adapter {}; struct write_adapter {};
template <std::integral T> template <std::integral T>
struct write_adapter<T> { struct write_adapter<T> {
static constexpr auto size(T) noexcept { static constexpr auto size(T t) noexcept { return sizeof(T); }
return sizeof(T);
}
template <typename Itr> template <typename Itr>
static constexpr auto write(T t, Itr out) noexcept { static constexpr auto write(T t, Itr out) noexcept {
return write_bytes(detail::raw_cast(host_to_be(t)), out); return write_bytes(::detail::raw_cast(host_to_be(t)), out);
} }
}; };
template <typename T>
struct deduced_write_adapter {};
template <std::signed_integral T>
struct deduced_write_adapter<T> {
static constexpr auto size(T value) noexcept {
return detail::bytes_needed(std::int64_t{value});
}
template <typename Itr>
static constexpr auto write(T value, Itr out) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
auto sz = size(value);
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
static constexpr auto format_hint(T value) noexcept {
return detail::get_format_signed(value);
}
};
template <std::unsigned_integral T>
struct deduced_write_adapter<T> {
static constexpr auto size(T value) noexcept {
return detail::bytes_needed(std::uint64_t{value});
}
template <typename Itr>
static constexpr auto write(T value, Itr out) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
auto sz = size(value);
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
template <typename Itr>
static constexpr auto write(T value, Itr out, std::size_t sz) noexcept {
auto bytes = ::detail::as_bytes(host_to_be(value));
for (std::size_t i = 0; i < sz; ++i) {
*out++ = *(bytes.end() - sz + i);
}
return out;
}
static constexpr auto format_hint(T value) noexcept {
return detail::get_format(value);
}
};
template <>
struct deduced_write_adapter<std::string_view> {
static constexpr auto size(std::string_view value) noexcept {
return value.size() + detail::bytes_needed_for_str(value.size());
}
template <typename Itr>
static constexpr auto write(std::string_view value, Itr out) noexcept {
auto bytes_needed = detail::bytes_needed_for_str(value.size());
if (bytes_needed) {
out = deduced_write_adapter<std::uint64_t>::write(
value.size(), out, bytes_needed);
}
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
std::copy(beg, beg + value.size(), out);
return out + value.size();
}
static constexpr auto format_hint(std::string_view value) noexcept {
switch (detail::bytes_needed_for_str(value.size())) {
case 0: return format::fixstr::marker | std::byte(value.size());
case 1: return format::str8::marker;
case 2: return format::str16::marker;
case 4:
default: return format::str32::marker;
}
}
};
template <std::convertible_to<std::string_view> T>
struct deduced_write_adapter<T> : deduced_write_adapter<std::string_view> {};
template <> template <>
struct write_adapter<std::string_view> { struct write_adapter<std::string_view> {
static constexpr auto size(std::string_view str) noexcept { static constexpr auto size(std::string_view str) noexcept {
@ -75,9 +201,10 @@ struct write_adapter<std::string_view> {
template <typename Itr> template <typename Itr>
static constexpr auto write(std::string_view str, Itr out) noexcept { static constexpr auto write(std::string_view str, Itr out) noexcept {
std::byte const* beg = reinterpret_cast<std::byte const*>(&*str.begin()); std::byte const* beg =
reinterpret_cast<std::byte const*>(&*str.begin());
std::copy(beg, beg + str.size(), out); std::copy(beg, beg + str.size(), out);
return out += str.size(); return out + str.size();
} }
}; };
@ -88,8 +215,8 @@ struct write_adapter<std::span<std::byte const>> {
} }
template <typename Itr> template <typename Itr>
static constexpr auto write(std::span<std::byte const> bytes, static constexpr auto write(
Itr out) noexcept { std::span<std::byte const> bytes, Itr out) noexcept {
std::copy(bytes.begin(), bytes.end(), out); std::copy(bytes.begin(), bytes.end(), out);
return out += bytes.size(); return out += bytes.size();
} }
@ -97,32 +224,24 @@ struct write_adapter<std::span<std::byte const>> {
template <> template <>
struct write_adapter<map_desc> { struct write_adapter<map_desc> {
static constexpr auto value(map_desc desc) noexcept { static constexpr auto value(map_desc desc) noexcept { return desc.count; }
return desc.count;
}
}; };
template <> template <>
struct write_adapter<array_desc> { struct write_adapter<array_desc> {
static constexpr auto value(array_desc desc) noexcept { static constexpr auto value(array_desc desc) noexcept { return desc.count; }
return desc.count;
}
}; };
// TODO: These could be optimized away because invalid/nil never contain real // TODO: These could be optimized away because invalid/nil never contain real
// data. // data.
template <> template <>
struct write_adapter<invalid> { struct write_adapter<invalid> {
static constexpr auto value(invalid) noexcept { static constexpr auto value(invalid) noexcept { return 0; }
return 0;
}
}; };
template <> template <>
struct write_adapter<nil> { struct write_adapter<nil> {
static constexpr auto value(nil) noexcept { static constexpr auto value(nil) noexcept { return 0; }
return 0;
}
}; };
namespace detail { namespace detail {
@ -130,8 +249,8 @@ namespace detail {
using expected = tl::expected<T, error>; using expected = tl::expected<T, error>;
template <format_type F> template <format_type F>
constexpr inline std::size_t calculate_space(typename F::value_type val) constexpr inline std::size_t calculate_space(
noexcept { typename F::value_type val) noexcept {
// At a minimum, one byte is needed to store the format. // At a minimum, one byte is needed to store the format.
std::size_t size = sizeof(typename F::first_type); std::size_t size = sizeof(typename F::first_type);
@ -158,13 +277,21 @@ namespace detail {
return typename F::first_type(write_adapter<value_type>::size(value)); return typename F::first_type(write_adapter<value_type>::size(value));
} else { } else {
if constexpr (requires { write_adapter<value_type>::value; }) { if constexpr (requires { write_adapter<value_type>::value; }) {
return typename F::first_type(write_adapter<value_type>::value(value)); return typename F::first_type(
write_adapter<value_type>::value(value));
} else { } else {
return typename F::first_type(value); return typename F::first_type(value);
} }
} }
} }
template <typename T>
concept v2_adapted = requires(T const& t, std::byte* b) {
{ deduced_write_adapter<T>::size(t) } -> std::same_as<std::size_t>;
{ deduced_write_adapter<T>::write(t, b) } -> std::same_as<decltype(b)>;
{ deduced_write_adapter<T>::format_hint(t) } -> std::same_as<std::byte>;
};
template <format_type F, typename Itr> template <format_type F, typename Itr>
constexpr inline expected<Itr> write( constexpr inline expected<Itr> write(
typename F::value_type&& value, Itr out, Itr const end) { typename F::value_type&& value, Itr out, Itr const end) {
@ -197,7 +324,8 @@ namespace detail {
*out++ = marker; *out++ = marker;
if constexpr (!is_fixtype<F>) { if constexpr (!is_fixtype<F>) {
out = write_adapter<typename decltype(result)::value_type>::write(*result, out); out = write_adapter<typename decltype(result)::value_type>::write(
*result, out);
} }
if constexpr (F::payload_type == format::payload::variable) { if constexpr (F::payload_type == format::payload::variable) {
@ -223,18 +351,18 @@ namespace detail {
class writer { class writer {
public: public:
template <typename T> template <typename T>
using expected = detail::expected<T>; using expected = detail::expected<T>;
constexpr writer(std::span<std::byte> dest) : constexpr writer(std::span<std::byte> dest)
data(dest), curr(std::begin(data)), end(std::end(data)) {} : data(dest)
, curr(std::begin(data))
, end(std::end(data)) {}
template <format_type F> template <format_type F>
constexpr expected<tl::monostate> write(typename F::value_type&& v) constexpr expected<tl::monostate> write(
noexcept { typename F::value_type&& v) noexcept {
using value_type = typename F::value_type; using value_type = typename F::value_type;
if (curr == end) return tl::make_unexpected(error::out_of_space);
auto result = detail::write<F>(std::forward<value_type>(v), curr, end); auto result = detail::write<F>(std::forward<value_type>(v), curr, end);
if (!result) { if (!result) {
return tl::make_unexpected(result.error()); return tl::make_unexpected(result.error());
@ -244,15 +372,23 @@ public:
return tl::monostate{}; return tl::monostate{};
} }
template <typename T> // Deduced-type write, automatically chooses smallest representative format
requires requires { typename detail::format_hint<T>::type; } template <detail::v2_adapted T>
constexpr expected<tl::monostate> write(T&& v) { constexpr expected<tl::monostate> write2(T&& v) {
return write<typename detail::format_hint<T>::type>(std::forward<T>(v)); using diff_type = std::iterator_traits<decltype(curr)>::difference_type;
auto const space_needed =
diff_type(1 + deduced_write_adapter<T>::size(v));
if (space_needed > std::distance(curr, end)) {
return tl::make_unexpected(error::out_of_space);
}
*curr++ = deduced_write_adapter<T>::format_hint(v);
if (space_needed > 1) {
curr = deduced_write_adapter<T>::write(v, curr);
}
return tl::monostate{};
} }
constexpr auto pos() const noexcept { constexpr auto pos() const noexcept { return curr; }
return curr;
}
constexpr auto tell() const noexcept { constexpr auto tell() const noexcept {
return std::distance(std::begin(data), curr); return std::distance(std::begin(data), curr);

View File

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Extra functionality: fmt formatters for msgpack types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_extra_formatters_d5fd427a7f749dcb
#define msgpack_extra_formatters_d5fd427a7f749dcb
#define FMT_VERSION
#ifdef FMT_VERSION
#include "parselink/msgpack/core/format.h"
#include <fmt/format.h>
template <>
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const noexcept {
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
};
};
template <>
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
}
};
template <>
struct fmt::formatter<msgpack::map_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::map_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::map_desc: {}}}", value.count);
}
};
template <>
struct fmt::formatter<msgpack::array_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::array_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::array_desc: {}}}", value.count);
}
};
#ifdef PARSELINK_MSGPACK_TOKEN_API
template <>
struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const {
using parselink::logging::themed_arg;
auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", themed_arg(v.type()));
switch (v.type()) {
case msgpack::format::type::unsigned_int:
fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::signed_int:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::boolean:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>())));
break;
case msgpack::format::type::string:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
themed_arg(*(v.get<std::span<std::byte const>>())));
break;
case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::map_desc>()->count));
break;
case msgpack::format::type::array:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::array_desc>()->count));
break;
case msgpack::format::type::nil:
out = fmt::format_to(out, "(nil)");
break;
case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)");
break;
default: break;
}
return fmt::format_to(out, ">");
}
};
#endif // PARSELINK_MSGPACK_TOKEN_API
#endif // FMT_VERSION
#endif // msgpack_extra_formatters_d5fd427a7f749dcb

View File

@ -1,8 +1,8 @@
#ifndef msgpack_object_f1f3a9e5c8be6a11 #ifndef msgpack_object_f1f3a9e5c8be6a11
#define msgpack_object_f1f3a9e5c8be6a11 #define msgpack_object_f1f3a9e5c8be6a11
#include "token/type.h"
#include "token/reader.h" #include "token/reader.h"
#include "token/type.h"
#include "token/views.h" #include "token/views.h"
#endif // msgpack_object_f1f3a9e5c8be6a11 #endif // msgpack_object_f1f3a9e5c8be6a11

View File

@ -21,10 +21,10 @@
#ifndef msgpack_token_reader_8daff350a0b1a519 #ifndef msgpack_token_reader_8daff350a0b1a519
#define msgpack_token_reader_8daff350a0b1a519 #define msgpack_token_reader_8daff350a0b1a519
#include "type.h"
#include "../core/error.h" #include "../core/error.h"
#include "../util/endianness.h"
#include "../core/format.h" #include "../core/format.h"
#include "../util/endianness.h"
#include "type.h"
#include <tl/expected.hpp> #include <tl/expected.hpp>
@ -44,8 +44,7 @@ constexpr std::int64_t sign_extend(std::size_t size, auto bytes) noexcept {
return {}; return {};
} }
constexpr decltype(auto) read(std::size_t size, auto& inp) constexpr decltype(auto) read(std::size_t size, auto& inp) noexcept {
noexcept {
std::array<std::byte, token::word_size> value{}; std::array<std::byte, token::word_size> value{};
be_to_host(inp, inp + size, std::begin(value)); be_to_host(inp, inp + size, std::begin(value));
inp += size; inp += size;
@ -76,8 +75,7 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
} }
switch (id) { switch (id) {
case format::uint8::marker: case format::uint8::marker: return format::get_traits<format::uint8>();
return format::get_traits<format::uint8>();
case format::uint16::marker: case format::uint16::marker:
return format::get_traits<format::uint16>(); return format::get_traits<format::uint16>();
case format::uint32::marker: case format::uint32::marker:
@ -85,47 +83,33 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
case format::uint64::marker: case format::uint64::marker:
return format::get_traits<format::uint64>(); return format::get_traits<format::uint64>();
case format::int8::marker: case format::int8::marker: return format::get_traits<format::int8>();
return format::get_traits<format::int8>(); case format::int16::marker: return format::get_traits<format::int16>();
case format::int16::marker: case format::int32::marker: return format::get_traits<format::int32>();
return format::get_traits<format::int16>(); case format::int64::marker: return format::get_traits<format::int64>();
case format::int32::marker:
return format::get_traits<format::int32>();
case format::int64::marker:
return format::get_traits<format::int64>();
case format::str8::marker: case format::str8::marker: return format::get_traits<format::str8>();
return format::get_traits<format::str8>(); case format::str16::marker: return format::get_traits<format::str16>();
case format::str16::marker: case format::str32::marker: return format::get_traits<format::str32>();
return format::get_traits<format::str16>(); case format::bin8::marker: return format::get_traits<format::bin8>();
case format::str32::marker: case format::bin16::marker: return format::get_traits<format::bin16>();
return format::get_traits<format::str32>(); case format::bin32::marker: return format::get_traits<format::bin32>();
case format::bin8::marker:
return format::get_traits<format::bin8>();
case format::bin16::marker:
return format::get_traits<format::bin16>();
case format::bin32::marker:
return format::get_traits<format::bin32>();
case format::array16::marker: case format::array16::marker:
return format::get_traits<format::array16>(); return format::get_traits<format::array16>();
case format::array32::marker: case format::array32::marker:
return format::get_traits<format::array32>(); return format::get_traits<format::array32>();
case format::map16::marker: case format::map16::marker: return format::get_traits<format::map16>();
return format::get_traits<format::map16>(); case format::map32::marker: return format::get_traits<format::map32>();
case format::map32::marker:
return format::get_traits<format::map32>();
case format::nil::marker: case format::nil::marker: return format::get_traits<format::nil>();
return format::get_traits<format::nil>();
case format::invalid::marker: case format::invalid::marker:
return format::get_traits<format::invalid>(); return format::get_traits<format::invalid>();
case format::boolean::marker: case format::boolean::marker:
case format::boolean::marker | std::byte{1}: case format::boolean::marker | std::byte{1}:
return format::get_traits<format::boolean>(); return format::get_traits<format::boolean>();
default: default: break;
break;
} }
return format::no_traits; return format::no_traits;
@ -135,26 +119,22 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
class token_reader { class token_reader {
public: public:
constexpr token_reader(std::span<std::byte const> src) noexcept
constexpr token_reader(std::span<std::byte const> src) noexcept : : data_(src)
data_(src), curr_{} {} , curr_{} {}
constexpr auto current() const noexcept { constexpr auto current() const noexcept {
return std::next(data_.begin(), curr_); return std::next(data_.begin(), curr_);
} }
constexpr auto end() const noexcept { constexpr auto end() const noexcept { return data_.end(); }
return data_.end();
}
constexpr auto remaining(auto itr) noexcept { constexpr auto remaining(auto itr) noexcept {
using dist_type = decltype(std::distance(itr, data_.end())); using dist_type = decltype(std::distance(itr, data_.end()));
return std::max(dist_type(0), std::distance(itr, data_.end())); return std::max(dist_type(0), std::distance(itr, data_.end()));
} }
constexpr auto remaining() noexcept { constexpr auto remaining() noexcept { return remaining(current()); }
return remaining(current());
}
// Read the next token. If the reader currently points to the end of the // Read the next token. If the reader currently points to the end of the
// byte buffer, then end_of_message is returned, and if there is still // byte buffer, then end_of_message is returned, and if there is still
@ -206,14 +186,11 @@ public:
case format::type::boolean: case format::type::boolean:
tok = token{bool(first_bytes[0] & traits.mask)}; tok = token{bool(first_bytes[0] & traits.mask)};
break; break;
case format::type::invalid: case format::type::invalid: tok = token{invalid{}}; break;
tok = token{invalid{}}; case format::type::nil: tok = token{nil{}}; break;
break;
case format::type::nil:
tok = token{nil{}};
break;
case format::type::unsigned_int: case format::type::unsigned_int:
tok = detail::make_token<format::type::unsigned_int>(first_bytes); tok = detail::make_token<format::type::unsigned_int>(
first_bytes);
break; break;
case format::type::signed_int: { case format::type::signed_int: {
auto value = detail::sign_extend(traits.size, first_bytes); auto value = detail::sign_extend(traits.size, first_bytes);
@ -242,8 +219,7 @@ public:
tok = token{map_desc(var_size)}; tok = token{map_desc(var_size)};
break; break;
} }
default: default: return tl::make_unexpected(error::not_implemented);
return tl::make_unexpected(error::not_implemented);
} }
curr_ = std::distance(data_.begin(), curr); curr_ = std::distance(data_.begin(), curr);
@ -280,8 +256,7 @@ public:
} }
template <typename T> template <typename T>
constexpr tl::expected<T, error> read() { constexpr tl::expected<T, error> read() {}
}
private: private:
std::span<std::byte const> data_; std::span<std::byte const> data_;

View File

@ -26,17 +26,16 @@
#include "../core/error.h" #include "../core/error.h"
#include "../core/format.h" #include "../core/format.h"
#include <string_view>
#include <tl/expected.hpp>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <string_view>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include <tl/expected.hpp>
namespace msgpack { namespace msgpack {
// This API is _currently_ optimizing on the fact that most desktop/server // This API is _currently_ optimizing on the fact that most desktop/server
@ -54,7 +53,8 @@ namespace msgpack {
// Of course, this means custom code! // Of course, this means custom code!
template <std::integral Size, typename E> template <std::integral Size, typename E>
requires (sizeof(Size) + sizeof(std::underlying_type_t<E>) <= sizeof(uintptr_t)) requires(sizeof(Size) + sizeof(std::underlying_type_t<E>)
<= sizeof(uintptr_t))
class size_and_enum { class size_and_enum {
public: public:
using size_type = Size; using size_type = Size;
@ -67,6 +67,7 @@ public:
// Constructors // Constructors
constexpr size_and_enum() noexcept = default; constexpr size_and_enum() noexcept = default;
constexpr size_and_enum(size_type size, enum_type enum_value) noexcept { constexpr size_and_enum(size_type size, enum_type enum_value) noexcept {
set_both(size, enum_value); set_both(size, enum_value);
} }
@ -75,30 +76,34 @@ public:
constexpr auto get_size() const noexcept { constexpr auto get_size() const noexcept {
return static_cast<size_type>((value & size_mask) >> size_shift); return static_cast<size_type>((value & size_mask) >> size_shift);
} }
constexpr auto get_enum() const noexcept { constexpr auto get_enum() const noexcept {
return static_cast<enum_type>((value & enum_mask)); return static_cast<enum_type>((value & enum_mask));
} }
constexpr auto rep() const noexcept { return value; } constexpr auto rep() const noexcept { return value; }
// Mutators // Mutators
constexpr auto set_size(size_type size) noexcept { constexpr auto set_size(size_type size) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) | value = (static_cast<uintptr_t>(size) << size_shift)
(value & enum_mask); | (value & enum_mask);
} }
constexpr auto set_enum(enum_type enum_value) noexcept { constexpr auto set_enum(enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(enum_value) & enum_mask) | value = (static_cast<uintptr_t>(enum_value) & enum_mask)
(value & size_mask); | (value & size_mask);
} }
constexpr auto set_both(size_type size, enum_type enum_value) noexcept { constexpr auto set_both(size_type size, enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) | value = (static_cast<uintptr_t>(size) << size_shift)
(static_cast<uintptr_t>(enum_value) & enum_mask); | (static_cast<uintptr_t>(enum_value) & enum_mask);
} }
// Explicit conversion // Explicit conversion
constexpr explicit operator size_type() const noexcept { constexpr explicit operator size_type() const noexcept {
return get_size(); return get_size();
} }
constexpr explicit operator enum_type() const noexcept { constexpr explicit operator enum_type() const noexcept {
return get_enum(); return get_enum();
} }
@ -156,6 +161,7 @@ class token_base<8> {
public: public:
constexpr static std::size_t word_size = 8; constexpr static std::size_t word_size = 8;
token_base() noexcept = default; token_base() noexcept = default;
token_base(token_base const& other) noexcept token_base(token_base const& other) noexcept
: value_(other.value_) : value_(other.value_)
, size_and_type_(other.size_and_type_) {} , size_and_type_(other.size_and_type_) {}
@ -217,9 +223,10 @@ public:
template <std::integral T> template <std::integral T>
constexpr tl::expected<T, error> get() const noexcept { constexpr tl::expected<T, error> get() const noexcept {
constexpr auto expected_type = std::is_same_v<T, bool> ? constexpr auto expected_type =
format::type::boolean : std::is_signed_v<T> ? std::is_same_v<T, bool> ? format::type::boolean
format::type::signed_int : format::type::unsigned_int; : std::is_signed_v<T> ? format::type::signed_int
: format::type::unsigned_int;
if (type() != expected_type) { if (type() != expected_type) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
@ -227,8 +234,8 @@ public:
if constexpr (expected_type == format::type::boolean) { if constexpr (expected_type == format::type::boolean) {
return T(value_.b); return T(value_.b);
} else if constexpr (expected_type == format::type::signed_int) { } else if constexpr (expected_type == format::type::signed_int) {
if (std::numeric_limits<T>::max() < value_.i || if (std::numeric_limits<T>::max() < value_.i
std::numeric_limits<T>::lowest() > value_.i) { || std::numeric_limits<T>::lowest() > value_.i) {
return tl::make_unexpected(error::will_truncate); return tl::make_unexpected(error::will_truncate);
} }
return T(value_.i); return T(value_.i);
@ -266,13 +273,12 @@ private:
// TODO: What to store here for array/map types? Possibly a pointer // TODO: What to store here for array/map types? Possibly a pointer
// to the end of the array/map, if/when known? // to the end of the array/map, if/when known?
} value_; } value_;
size_and_enum<std::uint32_t, format::type> size_and_type_{}; size_and_enum<std::uint32_t, format::type> size_and_type_{};
}; };
template <> template <>
inline tl::expected<std::string, error> token_base<8>::get() inline tl::expected<std::string, error> token_base<8>::get() const noexcept {
const noexcept
{
if (type() != format::type::string) { if (type() != format::type::string) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -280,9 +286,8 @@ inline tl::expected<std::string, error> token_base<8>::get()
} }
template <> template <>
constexpr tl::expected<std::string_view, error> token_base<8>::get() constexpr tl::expected<std::string_view, error>
const noexcept token_base<8>::get() const noexcept {
{
if (type() != format::type::string) { if (type() != format::type::string) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -291,29 +296,27 @@ constexpr tl::expected<std::string_view, error> token_base<8>::get()
template <std::size_t N> template <std::size_t N>
constexpr bool token_base<8>::operator==(char const (&t)[N]) const noexcept { constexpr bool token_base<8>::operator==(char const (&t)[N]) const noexcept {
auto result = get<std::string_view>().map([&t](auto v) { auto result = get<std::string_view>().map(
return v == std::string_view{t}; }); [&t](auto v) { return v == std::string_view{t}; });
return result && *result; return result && *result;
} }
template <> template <>
inline tl::expected<std::vector<std::byte>, error> token_base<8>::get() inline tl::expected<std::vector<std::byte>, error>
const noexcept token_base<8>::get() const noexcept {
{
tl::expected<std::vector<std::byte>, error> result; tl::expected<std::vector<std::byte>, error> result;
if (type() != format::type::binary) { if (type() != format::type::binary) {
result = tl::make_unexpected(error::wrong_type); result = tl::make_unexpected(error::wrong_type);
} else { } else {
result = std::vector<std::byte>(value_.bp, result = std::vector<std::byte>(
value_.bp + size_and_type_.get_size()); value_.bp, value_.bp + size_and_type_.get_size());
} }
return result; return result;
} }
template <> template <>
constexpr tl::expected<std::span<std::byte const>, error> constexpr tl::expected<std::span<std::byte const>, error>
token_base<8>::get() const noexcept token_base<8>::get() const noexcept {
{
if (type() != format::type::binary) { if (type() != format::type::binary) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -321,9 +324,7 @@ token_base<8>::get() const noexcept
} }
template <> template <>
constexpr tl::expected<nil, error> constexpr tl::expected<nil, error> token_base<8>::get() const noexcept {
token_base<8>::get() const noexcept
{
if (type() != format::type::nil) { if (type() != format::type::nil) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -331,9 +332,7 @@ token_base<8>::get() const noexcept
} }
template <> template <>
constexpr tl::expected<invalid, error> constexpr tl::expected<invalid, error> token_base<8>::get() const noexcept {
token_base<8>::get() const noexcept
{
if (type() != format::type::invalid) { if (type() != format::type::invalid) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -341,9 +340,7 @@ token_base<8>::get() const noexcept
} }
template <> template <>
constexpr tl::expected<array_desc, error> constexpr tl::expected<array_desc, error> token_base<8>::get() const noexcept {
token_base<8>::get() const noexcept
{
if (type() != format::type::array) { if (type() != format::type::array) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -351,9 +348,7 @@ token_base<8>::get() const noexcept
} }
template <> template <>
constexpr tl::expected<map_desc, error> constexpr tl::expected<map_desc, error> token_base<8>::get() const noexcept {
token_base<8>::get() const noexcept
{
if (type() != format::type::map) { if (type() != format::type::map) {
return tl::make_unexpected(error::wrong_type); return tl::make_unexpected(error::wrong_type);
} }
@ -364,5 +359,4 @@ using token = token_base<sizeof(void*)>;
} // namespace msgpack } // namespace msgpack
#endif // msgpack_token_type_f43c22522692063f #endif // msgpack_token_type_f43c22522692063f

View File

@ -24,16 +24,16 @@
#define msgpack_token_views_f19c250e782ed51c #define msgpack_token_views_f19c250e782ed51c
#include "type.h" #include "type.h"
#include <bits/iterator_concepts.h> #include <bits/iterator_concepts.h>
#include <fmt/format.h>
#include <ranges> #include <ranges>
#include <fmt/format.h>
namespace msgpack { namespace msgpack {
template <std::ranges::view V> template <std::ranges::view V>
requires std::ranges::input_range<V> && std::same_as<token, requires std::ranges::input_range<V>
std::ranges::range_value_t<V>> && std::same_as<token, std::ranges::range_value_t<V>>
struct map_view : public std::ranges::view_interface<map_view<V>> { struct map_view : public std::ranges::view_interface<map_view<V>> {
public: public:
class sentinel; class sentinel;
@ -66,6 +66,7 @@ namespace msgpack {
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
iterator() = default; iterator() = default;
iterator(V const& base) iterator(V const& base)
: base_{&base} : base_{&base}
, k_{std::ranges::begin(base)} { , k_{std::ranges::begin(base)} {
@ -79,9 +80,7 @@ namespace msgpack {
} }
} }
[[nodiscard]] reference operator*() const { [[nodiscard]] reference operator*() const { return {*k_, *v_}; }
return { *k_, *v_ };
}
iterator& operator++() { iterator& operator++() {
k_ = next(v_); k_ = next(v_);
@ -97,8 +96,7 @@ namespace msgpack {
} }
[[nodiscard]] bool operator==(iterator const& rhs) const { [[nodiscard]] bool operator==(iterator const& rhs) const {
return k_ == rhs.remaining_ && return k_ == rhs.remaining_ && base_ == rhs.base_;
base_ == rhs.base_;
} }
private: private:
@ -110,9 +108,7 @@ namespace msgpack {
class sentinel { class sentinel {
public: public:
[[nodiscard]] bool operator==(sentinel const&) const { [[nodiscard]] bool operator==(sentinel const&) const { return true; }
return true;
}
[[nodiscard]] bool operator==(iterator const& rhs) const { [[nodiscard]] bool operator==(iterator const& rhs) const {
return rhs.remaining_ == 0 return rhs.remaining_ == 0
@ -121,14 +117,14 @@ namespace msgpack {
}; };
constexpr map_view() noexcept = default; constexpr map_view() noexcept = default;
constexpr map_view(V base) : base_{std::move(base)} {}
[[nodiscard]] constexpr iterator begin() const { constexpr map_view(V base)
return { base_ }; : base_{std::move(base)} {}
}
[[nodiscard]] constexpr sentinel end() const { [[nodiscard]] constexpr iterator begin() const { return {base_}; }
return {};
} [[nodiscard]] constexpr sentinel end() const { return {}; }
private: private:
V base_; V base_;
}; };
@ -138,5 +134,4 @@ namespace msgpack {
} // namespace msgpack } // namespace msgpack
#endif // msgpack_token_views_f19c250e782ed51c #endif // msgpack_token_views_f19c250e782ed51c

View File

@ -0,0 +1,267 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// "Token" writing implementation, which supports both tokens and deduction for
// common types.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_core_writer_ce48a51aa6ed0858
#define msgpack_core_writer_ce48a51aa6ed0858
#include "parselink/msgpack/core/error.h"
#include "parselink/msgpack/core/format.h"
#include "parselink/msgpack/util/endianness.h"
#include <tl/expected.hpp>
#include <limits>
#include <type_traits>
namespace msgpack {
enum class writer_error {
none,
unsupported,
not_implemented,
bad_value,
out_of_space
};
// Helper template for writing a non-integral datatype to an output.
// template <typename T>
// struct write_adapter {
// template <typename Itr>
// static constexpr tl::expected<Itr, error> write(T const& t);
//};
template <std::size_t N, typename Itr>
constexpr inline decltype(auto) write_bytes(
std::array<std::byte, N>&& data, Itr out) noexcept {
for (auto b : data) {
*out++ = b;
}
return out;
}
template <typename T>
struct write_adapter {};
template <std::integral T>
struct write_adapter<T> {
static constexpr auto size(T) noexcept { return sizeof(T); }
template <typename Itr>
static constexpr auto write(T t, Itr out) noexcept {
return write_bytes(detail::raw_cast(host_to_be(t)), out);
}
};
template <>
struct write_adapter<std::string_view> {
static constexpr auto size(std::string_view str) noexcept {
return str.size();
}
template <typename Itr>
static constexpr auto write(std::string_view str, Itr out) noexcept {
std::byte const* beg =
reinterpret_cast<std::byte const*>(&*str.begin());
std::copy(beg, beg + str.size(), out);
return out += str.size();
}
};
template <>
struct write_adapter<std::span<std::byte const>> {
static constexpr auto size(std::span<std::byte const> bytes) noexcept {
return bytes.size();
}
template <typename Itr>
static constexpr auto write(
std::span<std::byte const> bytes, Itr out) noexcept {
std::copy(bytes.begin(), bytes.end(), out);
return out += bytes.size();
}
};
template <>
struct write_adapter<map_desc> {
static constexpr auto value(map_desc desc) noexcept { return desc.count; }
};
template <>
struct write_adapter<array_desc> {
static constexpr auto value(array_desc desc) noexcept { return desc.count; }
};
// TODO: These could be optimized away because invalid/nil never contain real
// data.
template <>
struct write_adapter<invalid> {
static constexpr auto value(invalid) noexcept { return 0; }
};
template <>
struct write_adapter<nil> {
static constexpr auto value(nil) noexcept { return 0; }
};
namespace detail {
template <typename T>
using expected = tl::expected<T, error>;
template <format_type F>
constexpr inline std::size_t calculate_space(
typename F::value_type val) noexcept {
// At a minimum, one byte is needed to store the format.
std::size_t size = sizeof(typename F::first_type);
if (!is_fixtype<F>) {
++size; // For format
}
if constexpr (F::payload_type == format::payload::variable) {
size += write_adapter<typename F::value_type>::size(val);
}
return size;
}
// The "first type" is either the size of the variable length payload or
// a fixed-length value. Additionally, this "first type" may be inlined
// into the marker byte if it's a fix type.
template <format_type F>
constexpr inline expected<typename F::first_type> pack_first(
typename F::value_type value) noexcept {
using value_type = typename F::value_type;
if constexpr (F::payload_type == format::payload::variable) {
return typename F::first_type(write_adapter<value_type>::size(value));
} else {
if constexpr (requires { write_adapter<value_type>::value; }) {
return typename F::first_type(
write_adapter<value_type>::value(value));
} else {
return typename F::first_type(value);
}
}
}
template <format_type F, typename Itr>
constexpr inline expected<Itr> write(
typename F::value_type&& value, Itr out, Itr const end) {
using diff_type = typename std::iterator_traits<Itr>::difference_type;
if (diff_type(calculate_space<F>(value)) > std::distance(out, end)) {
return tl::make_unexpected(error::out_of_space);
}
auto marker = F::marker;
auto result = pack_first<F>(value);
if (!result) {
return tl::make_unexpected(result.error());
}
if constexpr (is_fixtype<F>) {
if (*result > 0xff) {
return tl::make_unexpected(error::bad_value);
}
if constexpr (F::flags & format::flag::apply_mask) {
marker |= std::byte(*result);
} else {
marker = std::byte(*result);
}
if ((marker & ~F::mask) != F::marker) {
return tl::make_unexpected(error::bad_value);
}
}
*out++ = marker;
if constexpr (!is_fixtype<F>) {
out = write_adapter<typename decltype(result)::value_type>::write(
*result, out);
}
if constexpr (F::payload_type == format::payload::variable) {
out = write_adapter<typename F::value_type>::write(value, out);
}
return out;
}
template <typename T>
struct format_hint;
template <>
struct format_hint<std::uint8_t> {
using type = format::positive_fixint;
};
template <>
struct format_hint<std::uint16_t> {
using type = format::uint16;
};
} // namespace detail
class writer {
public:
template <typename T>
using expected = detail::expected<T>;
constexpr writer(std::span<std::byte> dest)
: data(dest)
, curr(std::begin(data))
, end(std::end(data)) {}
template <format_type F>
constexpr expected<tl::monostate> write(
typename F::value_type&& v) noexcept {
using value_type = typename F::value_type;
if (curr == end) return tl::make_unexpected(error::out_of_space);
auto result = detail::write<F>(std::forward<value_type>(v), curr, end);
if (!result) {
return tl::make_unexpected(result.error());
}
curr = *result;
return tl::monostate{};
}
template <typename T>
requires requires { typename detail::format_hint<T>::type; }
constexpr expected<tl::monostate> write(T&& v) {
return write<typename detail::format_hint<T>::type>(std::forward<T>(v));
}
constexpr auto pos() const noexcept { return curr; }
constexpr auto tell() const noexcept {
return std::distance(std::begin(data), curr);
}
constexpr auto subspan() const noexcept {
return std::span<std::byte>(std::begin(data), tell());
}
private:
std::span<std::byte> data;
decltype(data)::iterator curr;
decltype(data)::iterator end;
};
} // namespace msgpack
#endif // msgpack_core_writer_ce48a51aa6ed0858

View File

@ -24,11 +24,7 @@
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
enum class endianness { enum class endianness { big, little, other };
big,
little,
other
};
namespace detail { namespace detail {
@ -37,7 +33,7 @@ namespace detail {
*/ */
struct host_endianness_check { struct host_endianness_check {
constexpr static inline std::uint32_t impl = 0x01020304; constexpr static inline std::uint32_t impl = 0x01020304;
constexpr static inline auto test = static_cast<const unsigned char&>(impl); constexpr static inline auto test = static_cast<unsigned char const&>(impl);
constexpr static inline auto value = [] { constexpr static inline auto value = [] {
switch (test) { switch (test) {
case 4: return endianness::little; case 4: return endianness::little;
@ -98,10 +94,16 @@ constexpr auto raw_cast(T val) noexcept {
T val; T val;
std::array<std::byte, sizeof(T)> data; std::array<std::byte, sizeof(T)> data;
}; };
trick_to_array u{val}; trick_to_array u{val};
return u.data; return u.data;
} }
template <typename T>
constexpr auto as_bytes(T val) noexcept {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(val);
}
/** /**
* A helper function for converting a std::array into some arbitrary type. * A helper function for converting a std::array into some arbitrary type.
* Beware, this must be used on trivial data types. * Beware, this must be used on trivial data types.
@ -112,6 +114,7 @@ constexpr auto value_cast(Array&& data) noexcept {
Array data; Array data;
T val; T val;
}; };
trick_to_value u{std::forward<Array>(data)}; trick_to_value u{std::forward<Array>(data)};
return u.val; return u.val;
} }
@ -127,7 +130,7 @@ constexpr T byte_swap(T val) noexcept {
maybe_swap<From, To>(std::bit_cast<array_type>(val))); maybe_swap<From, To>(std::bit_cast<array_type>(val)));
} }
} // namespace detail; } // namespace detail
/** /**
* Exposes endianness information about a target. * Exposes endianness information about a target.

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

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

@ -18,48 +18,103 @@
#ifndef session_07eae057feface79 #ifndef session_07eae057feface79
#define session_07eae057feface79 #define session_07eae057feface79
#include "parselink/msgpack/token.h"
#include "parselink/proto/error.h"
#include "parselink/proto/session_id.h"
#include <tl/expected.hpp> #include <tl/expected.hpp>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <functional>
#include <span> #include <span>
#include <string> #include <string>
template <>
struct std::hash<std::span<std::byte const>> {
constexpr static std::uint32_t seed_var = 0x811c9dc5;
constexpr static std::uint32_t factor = 0x01000193;
constexpr auto operator()(std::span<std::byte const> data) const noexcept {
std::uint32_t digest = seed_var * factor;
for (auto byte : data) {
digest = (digest ^ static_cast<std::uint32_t>(byte)) * factor;
}
return digest >> 8;
}
};
namespace parselink { namespace parselink {
namespace proto { namespace proto {
enum class error {
system_error,
incomplete,
bad_data,
too_large,
};
// Structure containing header information parsed from a buffer. // Structure containing header information parsed from a buffer.
struct header_info { struct header_info {
std::uint32_t message_size; // Size of the message, minus the header. std::uint32_t message_size; // Size of the message, minus the header.
std::uint32_t bytes_read; // How many bytes of the buffer were used. std::uint32_t bytes_read; // How many bytes of the buffer were used.
std::uint32_t bytes_parsed; // How many bytes were parsed as part of the
// header.
};
struct connect_info {
std::uint32_t version;
std::string_view user_id;
std::span<std::byte const> session_id;
};
template <typename T>
struct transparent_hash {
using is_transparent = void;
using type = T;
using hash_type = std::hash<type>;
template <typename Arg>
[[nodiscard]] constexpr auto operator()(Arg&& arg) const {
return hash_type{}(std::forward<Arg>(arg));
}
}; };
class session { class session {
public: public:
session() = default; using close_handle = std::function<void(std::string_view)>;
session(std::string_view user_id) noexcept;
// Parse the protocol header out of a buffer. This is a member function ~session();
// as we may choose to omit data once a session is established.
session_id id() const noexcept { return id_; }
std::string_view user_id() const noexcept { return user_id_; }
auto last_activity() const noexcept { return last_activity_; }
void update_last_activity(
std::chrono::system_clock::time_point =
std::chrono::system_clock::now()) noexcept {}
private:
session_id id_;
std::string user_id_;
std::chrono::system_clock::time_point last_activity_;
};
template <>
struct transparent_hash<std::string> : transparent_hash<std::string_view> {};
template <>
struct transparent_hash<session_id>
: transparent_hash<std::span<std::byte const>> {
[[nodiscard]] auto operator()(session_id const& s) const {
return std::hash<std::span<std::byte const>>{}(s.raw());
}
};
// Parse the protocol header out of a buffer.
tl::expected<header_info, error> parse_header( tl::expected<header_info, error> parse_header(
std::span<std::byte const> buffer) noexcept; std::span<std::byte const> buffer) noexcept;
// The maximum size of a single message. Should probably depend on whether tl::expected<connect_info, error> parse_connect(
// the session is established or not. std::span<msgpack::token> tokens) noexcept;
constexpr static std::uint32_t max_message_size = 128 * 1024;
private:
std::string user_id;
};
} // namespace proto } // namespace proto
} // namespace parselink } // namespace parselink
#endif // session_0c61530748b9f966 #endif // session_0c61530748b9f966

View File

@ -0,0 +1,88 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: proto
//
// Session ID. Used as a handle to access an existing user session, which can
// also be used to share parse data without any linking of users.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef session_id_6598f9bae1cbb501
#define session_id_6598f9bae1cbb501
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <span>
namespace parselink {
namespace proto {
struct session_id {
public:
session_id() noexcept;
// Not the intended way to build a session id. Ideally, they'll be randomly
// generated.
explicit constexpr session_id(std::array<std::byte, 32> const& v) noexcept {
std::copy(v.begin(), v.end(), bytes_.begin());
}
[[nodiscard]] constexpr auto operator<=>(
session_id const& other) const noexcept {
return bytes_ <=> other.bytes_;
}
[[nodiscard]] constexpr auto operator==(
session_id const& other) const noexcept {
return bytes_ == other.bytes_;
}
[[nodiscard]] constexpr auto operator<=>(
std::span<std::byte const> other) const noexcept {
return std::lexicographical_compare_three_way(bytes_.begin(),
bytes_.end(), other.begin(), other.end(),
std::compare_three_way());
}
[[nodiscard]] constexpr auto operator==(
std::span<std::byte const> other) const noexcept {
return std::equal(
bytes_.begin(), bytes_.end(), other.begin(), other.end());
}
constexpr std::span<std::byte const> raw() const noexcept { return bytes_; }
private:
std::array<std::byte, 32> bytes_;
};
} // namespace proto
} // namespace parselink
// Hashing support
template <>
struct std::hash<parselink::proto::session_id> {
constexpr static std::uint32_t seed_var = 0x811c9dc5;
constexpr static std::uint32_t factor = 0x01000193;
constexpr auto operator()(auto const& sid) const noexcept {
std::uint32_t digest = seed_var * factor;
for (auto byte : sid.raw()) {
digest = (digest ^ static_cast<std::uint32_t>(byte)) * factor;
}
return digest >> 8;
}
};
#endif // session_id_6598f9bae1cbb501

View File

@ -18,8 +18,8 @@
#ifndef server_5b46f075be3caa00 #ifndef server_5b46f075be3caa00
#define server_5b46f075be3caa00 #define server_5b46f075be3caa00
#include <memory>
#include <cstdint> #include <cstdint>
#include <memory>
namespace parselink { namespace parselink {

View File

@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Simple in-memory session manager.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef memory_session_manager_b3851872babe001d
#define memory_session_manager_b3851872babe001d
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <parselink/server/session_manager.h>
#include <unordered_set>
namespace parselink {
class memory_session_manager {
public:
memory_session_manager(boost::asio::io_context& ctx);
~memory_session_manager();
// Allocate a new session.
tl::expected<proto::session*, proto::error> create_session(
std::string_view user_id);
// Destroy an existing session.
tl::expected<tl::monostate, proto::error> destroy_session(
proto::session* session);
// Find a session by its user id.
tl::expected<proto::session*, proto::error> find(std::string_view user_id);
// Find a session by its ID.
tl::expected<proto::session*, proto::error> find(
proto::session_id const& session);
private:
std::unordered_map<std::string, proto::session,
proto::transparent_hash<std::string>, std::equal_to<>>
sessions_;
std::unordered_map<proto::session_id, proto::session*,
proto::transparent_hash<proto::session_id>, std::equal_to<>>
lookup_by_sid_;
boost::asio::io_context& ctx_;
boost::asio::io_context::strand strand_;
};
// Sanity check
static_assert(session_manager<memory_session_manager>);
static_assert(sync_session_manager<memory_session_manager>);
static_assert(!async_session_manager<memory_session_manager>);
} // namespace parselink
#endif // memory_session_manager_b3851872babe001d

View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Server interface.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef server_5b46f075be3caa00
#define server_5b46f075be3caa00
#include <cstdint>
#include <memory>
namespace parselink {
template <typename Server>
concept server_concept = requires(Server& srv) {
{ srv.run() } -> std::same_as<std::error_code>;
};
class server {
public:
virtual ~server() = default;
virtual std::error_code run() noexcept = 0;
};
std::unique_ptr<server> make_server(std::string_view address,
std::uint16_t user_port, std::uint16_t websocket_port);
} // namespace parselink
#endif // server_5b46f075be3caa00

View File

@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Session manager concept.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef session_manager_779b9fc5781fb66f
#define session_manager_779b9fc5781fb66f
#include <boost/asio/awaitable.hpp>
#include <parselink/proto/session.h>
#include <parselink/proto/session_id.h>
namespace parselink {
using boost::asio::awaitable;
template <typename T>
concept sync_session_manager =
requires(T& mgr, std::string_view sv, proto::session* s) {
{
mgr.create_session(sv)
} -> std::same_as<tl::expected<proto::session*, proto::error>>;
} && requires(T& mgr, proto::session* s) {
{
mgr.destroy_session(s)
} -> std::same_as<tl::expected<tl::monostate, proto::error>>;
} && requires(T& mgr, std::string_view user_id) {
{
mgr.find(user_id)
} -> std::same_as<tl::expected<proto::session*, proto::error>>;
} && requires(T& mgr, proto::session_id const& sid) {
{
mgr.find(sid)
} -> std::same_as<tl::expected<proto::session*, proto::error>>;
};
template <typename T>
concept async_session_manager = requires(T& mgr, proto::session* s) {
{
mgr.create_session()
} -> std::same_as<awaitable<tl::expected<proto::session*, proto::error>>>;
} && requires(T& mgr, proto::session* s) {
{
mgr.destroy_session(s)
} -> std::same_as<awaitable<tl::expected<tl::monostate, proto::error>>>;
} && requires(T& mgr, proto::session_id const& sid) {
{
mgr.find(sid)
} -> std::same_as<awaitable<tl::expected<proto::session*, proto::error>>>;
};
template <typename T>
concept session_manager = sync_session_manager<T> || async_session_manager<T>;
} // namespace parselink
#endif // session_manager_779b9fc5781fb66f

View File

@ -20,11 +20,11 @@
#ifndef argparse_d2ddac0dab0d7b88 #ifndef argparse_d2ddac0dab0d7b88
#define argparse_d2ddac0dab0d7b88 #define argparse_d2ddac0dab0d7b88
#include <chrono>
#include <charconv> #include <charconv>
#include <chrono>
#include <initializer_list> #include <initializer_list>
#include <optional>
#include <map> #include <map>
#include <optional>
#include <span> #include <span>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@ -43,10 +43,11 @@ namespace argparse {
template <typename T> template <typename T>
concept has_parser = requires { concept has_parser = requires {
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) } {
-> std::same_as<T*>; argument_parser<std::decay_t<T>>::parse(std::string_view{})
} -> std::same_as<T*>;
}; };
} } // namespace custom
template <typename T> template <typename T>
struct argument_parser {}; struct argument_parser {};
@ -64,7 +65,8 @@ namespace argparse {
}; };
inline constexpr std::initializer_list< inline constexpr std::initializer_list<
std::tuple<char const*, std::chrono::nanoseconds>> unit_map = { std::tuple<char const*, std::chrono::nanoseconds>>
unit_map = {
{"ns", std::chrono::nanoseconds{1}}, {"ns", std::chrono::nanoseconds{1}},
{"us", std::chrono::microseconds{1}}, {"us", std::chrono::microseconds{1}},
{"ms", std::chrono::milliseconds{1}}, {"ms", std::chrono::milliseconds{1}},
@ -96,9 +98,7 @@ namespace argparse {
}; };
template <typename T> template <typename T>
requires requires (T& t) { requires requires(T& t) { std::from_chars(nullptr, nullptr, t); }
std::from_chars(nullptr, nullptr, t);
}
struct argument_parser<T> { struct argument_parser<T> {
static T* parse(std::string_view value) noexcept { static T* parse(std::string_view value) noexcept {
T result; T result;
@ -116,8 +116,9 @@ namespace argparse {
template <typename T> template <typename T>
concept has_parser = requires { concept has_parser = requires {
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) } {
-> std::same_as<T*>; argument_parser<std::decay_t<T>>::parse(std::string_view{})
} -> std::same_as<T*>;
}; };
static_assert(has_parser<int>); static_assert(has_parser<int>);
@ -129,21 +130,24 @@ namespace detail {
// by creating an argparse::custom::argument_parser template specialization // by creating an argparse::custom::argument_parser template specialization
// for a given type. // for a given type.
struct any_arg { struct any_arg {
constexpr any_arg() = default; constexpr any_arg() = default;
template <typename T> template <typename T>
any_arg(T&& value) : iface(&dispatcher<std::decay_t<T>>::table) { any_arg(T&& value)
: iface(&dispatcher<std::decay_t<T>>::table) {
dispatcher<std::decay_t<T>>::create(*this, std::forward<T>(value)); dispatcher<std::decay_t<T>>::create(*this, std::forward<T>(value));
} }
any_arg(any_arg const& other) : iface(other.iface) { any_arg(any_arg const& other)
: iface(other.iface) {
if (other.iface) { if (other.iface) {
data = other.iface->copy(other.data); data = other.iface->copy(other.data);
} }
} }
any_arg(any_arg&& other) : data(std::move(other.data)), iface(other.iface) { any_arg(any_arg&& other)
: data(std::move(other.data))
, iface(other.iface) {
other.iface = nullptr; other.iface = nullptr;
} }
@ -164,13 +168,9 @@ namespace detail {
} }
} }
bool parse(std::string_view sv) { bool parse(std::string_view sv) { return iface->parse(*this, sv); }
return iface->parse(*this, sv);
}
bool has_value() const noexcept { bool has_value() const noexcept { return static_cast<bool>(iface); }
return static_cast<bool>(iface);
}
template <typename T> template <typename T>
bool holds() const noexcept { bool holds() const noexcept {
@ -181,6 +181,7 @@ namespace detail {
friend T const* arg_cast(any_arg const*) noexcept; friend T const* arg_cast(any_arg const*) noexcept;
void* data = nullptr; void* data = nullptr;
struct interface { struct interface {
void (*destroy)(void*); void (*destroy)(void*);
void* (*copy)(void*); void* (*copy)(void*);
@ -196,9 +197,7 @@ namespace detail {
self.data = new T{std::forward<Args>(args)...}; self.data = new T{std::forward<Args>(args)...};
} }
static void* copy(void *ptr) { static void* copy(void* ptr) { return new T{*static_cast<T*>(ptr)}; }
return new T{*static_cast<T*>(ptr)};
}
static bool parse(any_arg& self, std::string_view sv) { static bool parse(any_arg& self, std::string_view sv) {
if constexpr (custom::has_parser<T>) { if constexpr (custom::has_parser<T>) {
@ -212,16 +211,13 @@ namespace detail {
} }
} }
static void destroy(void* ptr) { static void destroy(void* ptr) { delete static_cast<T*>(ptr); }
delete static_cast<T*>(ptr);
}
static T const* cast(void* ptr) { static T const* cast(void* ptr) { return static_cast<T const*>(ptr); }
return static_cast<T const*>(ptr);
}
static constexpr struct interface table { static constexpr struct interface table {
&dispatcher::destroy, &dispatcher::copy, &dispatcher::parse }; &dispatcher::destroy, &dispatcher::copy, &dispatcher::parse
};
}; };
}; };
@ -232,15 +228,14 @@ namespace detail {
} }
return nullptr; return nullptr;
} }
} } // namespace detail
class command_line_parser { class command_line_parser {
public: public:
using argument = detail::any_arg; using argument = detail::any_arg;
constexpr static char delimiter = '='; constexpr static char delimiter = '=';
using opt_list = std::initializer_list< using opt_list =
std::tuple<std::string_view, argument>>; std::initializer_list<std::tuple<std::string_view, argument>>;
struct result { struct result {
enum class code { enum class code {
@ -261,8 +256,8 @@ public:
template <typename T> template <typename T>
T const* maybe_opt(std::string_view name) const noexcept { T const* maybe_opt(std::string_view name) const noexcept {
auto entry = opts.find(name); auto entry = opts.find(name);
return entry != opts.end() ? return entry != opts.end() ? detail::arg_cast<T>(&entry->second)
detail::arg_cast<T>(&entry->second) : nullptr; : nullptr;
} }
template <typename T> template <typename T>
@ -306,7 +301,6 @@ public:
} }
private: private:
static auto split_option(std::string_view kv) { static auto split_option(std::string_view kv) {
auto delim = kv.find(delimiter); auto delim = kv.find(delimiter);
if (delim != kv.npos) { if (delim != kv.npos) {
@ -350,7 +344,6 @@ private:
std::vector<std::string> arguments_; std::vector<std::string> arguments_;
}; };
} } // namespace argparse
#endif // argparse_d2ddac0dab0d7b88 #endif // argparse_d2ddac0dab0d7b88

View File

@ -0,0 +1,156 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Utility
//
// Utility wrappers for dealing with files.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef utility_file_8b49e5e471a7c3e5
#define utility_file_8b49e5e471a7c3e5
#include <fmt/format.h>
#include <tl/expected.hpp>
#include <algorithm>
#include <fcntl.h>
#include <span>
#include <system_error>
#include <unistd.h>
#include <vector>
namespace parselink {
namespace utility {
namespace file {
struct [[nodiscard]] handle {
constexpr handle() noexcept = default;
constexpr handle(int fd) noexcept
: fd_(fd) {
if (fd_ < 0) fd_ = -errno;
}
constexpr handle(handle const&) noexcept = delete;
constexpr handle(handle&& other) noexcept
: fd_(other.fd_) {
other.fd_ = -1;
}
constexpr handle& operator=(handle const&) noexcept = delete;
constexpr handle& operator=(handle&& other) noexcept {
close();
fd_ = other.fd_;
return *this;
}
constexpr operator bool() const noexcept { return fd_ >= 0; }
constexpr int operator*() const noexcept { return fd_; }
void close() {
if (*this) {
::close(fd_);
fd_ = -1;
}
}
int fd_{-1};
};
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(static_cast<std::errc>(-*file));
}
}
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(static_cast<std::errc>(-*file));
}
data.resize(amount);
auto amt_read = ::read(*file, data.data(), amount);
if (amt_read < data.size()) {
data.resize(amt_read);
}
return data;
}
template <typename T>
requires(std::is_trivially_default_constructible_v<T> && sizeof(T) == 1)
inline tl::expected<std::vector<T>, std::errc> read(
handle const& file) noexcept {
std::vector<T> data;
if (!file) {
return tl::make_unexpected(static_cast<std::errc>(-*file));
}
auto cur = lseek(*file, 0, SEEK_CUR);
auto end = lseek(*file, 0, SEEK_END);
std::size_t amount = end - cur;
lseek(*file, SEEK_SET, cur);
data.resize(amount);
auto amt_read = ::read(*file, data.data(), amount);
if (amt_read < data.size()) {
data.resize(amt_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
#endif // utility_file_8b49e5e471a7c3e5

View File

@ -1,238 +0,0 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

View File

@ -18,6 +18,7 @@ cc_binary(
"//include/parselink:utility", "//include/parselink:utility",
"//source/logging", "//source/logging",
"//source/proto", "//source/proto",
"//source/server",
"@boost//:beast", "@boost//:beast",
], ],
) )

View File

@ -38,23 +38,25 @@ struct console_endpoint : public endpoint {
} }
}; };
} } // namespace
auto& console() { auto& console() {
static auto console = std::make_shared<console_endpoint>(); static auto console = std::make_shared<console_endpoint>();
return console; return console;
} }
logger::logger(std::string_view name) : name_{name} { logger::logger(std::string_view name)
: name_{name} {
endpoints_.emplace_back(console()); endpoints_.emplace_back(console());
} }
logger::logger(std::string_view name, std::vector<std::shared_ptr<endpoint>> eps) logger::logger(
: name_{name}, endpoints_{std::move(eps)} {} std::string_view name, std::vector<std::shared_ptr<endpoint>> eps)
: name_{name}
, endpoints_{std::move(eps)} {}
void logger::set_threshold(level new_threshold) noexcept { void logger::set_threshold(level new_threshold) noexcept {
for (auto& ep : endpoints_) { for (auto& ep : endpoints_) {
ep->threshold = new_threshold; ep->threshold = new_threshold;
} }
} }

View File

@ -1,6 +1,6 @@
#include "parselink/logging.h" #include "parselink/logging.h"
#include <utility/argparse.h>
#include <server.h> #include <server.h>
#include <utility/argparse.h>
namespace { namespace {
parselink::logging::logger logger("main"); parselink::logging::logger logger("main");
@ -28,8 +28,6 @@ int run(std::span<std::string_view> arg_list) {
logger.set_threshold(level::trace); logger.set_threshold(level::trace);
} }
auto server = parselink::make_server(args.opt<std::string>("address"), auto server = parselink::make_server(args.opt<std::string>("address"),
args.opt<std::uint16_t>("user_port"), args.opt<std::uint16_t>("user_port"),
args.opt<std::uint16_t>("websocket_port")); args.opt<std::uint16_t>("websocket_port"));
@ -41,8 +39,7 @@ int run(std::span<std::string_view> arg_list) {
return 0; return 0;
} }
int main(int argc, char* argv[]) int main(int argc, char* argv[]) {
{
// TODO(ksassenrath): Add configuration file to the mix. // TODO(ksassenrath): Add configuration file to the mix.
std::vector<std::string_view> args; std::vector<std::string_view> args;

View File

@ -3,12 +3,15 @@
cc_library( cc_library(
name = "proto", name = "proto",
srcs = [ srcs = [
"parser.cpp",
"session.cpp", "session.cpp",
"session_id.cpp",
], ],
deps = [ deps = [
"//include/parselink:proto", "//include/parselink:proto",
"//include/parselink:msgpack", "//include/parselink:msgpack",
"//source/logging", "//source/logging",
"@hydrogen",
], ],
visibility = [ visibility = [
# TODO: Fix visibility # TODO: Fix visibility

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

@ -17,20 +17,101 @@
// //
// License TBD. // License TBD.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "parselink/proto/session.h" #include "parselink/proto/session.h"
#include "parselink/logging.h" #include "parselink/logging.h"
#include "parselink/msgpack/token.h" #include "parselink/msgpack/token.h"
#include <fmt/ranges.h>
#include "hydrogen.h"
namespace {
void ensure_initialized() {
static auto x [[maybe_unused]] = [] { return hydro_init(); }();
}
template <std::size_t N>
auto get_random_bytes() {
ensure_initialized();
std::array<std::byte, N> out;
hydro_random_buf(out.data(), N);
return out;
}
} // anonymous namespace
using namespace parselink; using namespace parselink;
using namespace parselink::proto; using namespace parselink::proto;
namespace { template <>
logging::logger logger("session"); struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
} }
tl::expected<header_info, error> session::parse_header( template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const {
using parselink::logging::themed_arg;
auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", themed_arg(v.type()));
switch (v.type()) {
case msgpack::format::type::unsigned_int:
fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::signed_int:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::boolean:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>())));
break;
case msgpack::format::type::string:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
themed_arg(*(v.get<std::span<std::byte const>>())));
break;
case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::map_desc>()->count));
break;
case msgpack::format::type::array:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::array_desc>()->count));
break;
case msgpack::format::type::nil:
out = fmt::format_to(out, "(nil)");
break;
case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)");
break;
default: break;
}
return fmt::format_to(out, ">");
}
};
namespace {
logging::logger logger("session");
constexpr std::uint32_t max_size = 128 * 1024;
constexpr tl::expected<std::uint32_t, error> check_support(
tl::expected<msgpack::token, error> const& val) {
if (val == 1u) {
return *(*val).get<std::uint32_t>();
}
return tl::make_unexpected(error::unsupported);
}
} // namespace
tl::expected<header_info, error> proto::parse_header(
std::span<std::byte const> buffer) noexcept { std::span<std::byte const> buffer) noexcept {
auto reader = msgpack::token_reader(buffer); auto reader = msgpack::token_reader(buffer);
auto magic = reader.read_one(); auto magic = reader.read_one();
@ -47,11 +128,71 @@ tl::expected<header_info, error> session::parse_header(
return tl::unexpected(magic ? error::bad_data : error::incomplete); return tl::unexpected(magic ? error::bad_data : error::incomplete);
} }
if (*size > max_message_size) { if (*size > max_size) {
logger.error("Message {} exceeds max size {}", *size, max_message_size); logger.error("Message size {} exceeds max {}", *size, max_size);
return tl::unexpected(error::too_large); return tl::unexpected(error::too_large);
} }
std::uint32_t amt = std::distance(buffer.begin(), reader.current()); std::uint32_t amt = std::distance(buffer.begin(), reader.current());
return tl::expected<header_info, error>(tl::in_place, *size, amt); return tl::expected<header_info, error>(tl::in_place, *size, amt);
} }
constexpr tl::expected<msgpack::token, error> lookup(
auto const& map_view, auto const& key) {
auto find_key = [&key](auto const& kv) { return kv.first == key; };
if (auto field = std::ranges::find_if(map_view, find_key);
field != map_view.end()) {
return (*field).second;
}
return tl::make_unexpected(error::bad_data);
}
tl::expected<connect_info, error> proto::parse_connect(
std::span<msgpack::token> tokens) noexcept {
auto message_type = tokens.begin()->get<std::string_view>();
if (message_type && message_type == "connect") {
logger.debug("Received '{}' packet. Parsing body", *message_type);
auto map = msgpack::map_view(tokens.subspan(1));
connect_info info;
auto version = lookup(map, "version").and_then(check_support);
if (version) {
info.version = *version;
} else {
return tl::make_unexpected(version.error());
}
for (auto const& [k, v] : msgpack::map_view(tokens.subspan(1))) {
tl::expected<void, msgpack::error> result;
if (k.type() != msgpack::format::type::string) {
logger.error("connect failed: non-string key {}", k);
return tl::make_unexpected(error::bad_data);
}
if (k == "user_id") {
result = v.get<std::string_view>().map(
[&info](auto uid) { info.user_id = uid; });
}
if (!result) {
logger.error(
"connect failed: {} -> {}: {}", k, v, result.error());
return tl::make_unexpected(error::bad_data);
}
}
return info;
} else {
logger.error("Did not get message type: {}", message_type.error());
return tl::make_unexpected(error::bad_data);
}
}
session::session(std::string_view user_id) noexcept
: id_()
, user_id_(std::string{user_id})
, last_activity_(std::chrono::system_clock::now()) {
logger.debug("New session with id {} created for {}", id_.raw(), user_id_);
}
session::~session() {}

View File

@ -0,0 +1,38 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: proto
//
// Session ID implementation
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/proto/session_id.h"
#include "hydrogen.h"
namespace {
void ensure_initialized() {
static auto init_crng [[maybe_unused]] = [] { return hydro_init(); }();
}
template <std::size_t N>
auto get_random_bytes() {
ensure_initialized();
std::array<std::byte, N> out;
hydro_random_buf(out.data(), N);
return out;
}
}
using namespace parselink::proto;
session_id::session_id() noexcept : bytes_(get_random_bytes<32>()) {}

View File

@ -18,40 +18,44 @@
// License TBD. // License TBD.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "parselink/logging.h"
#include "parselink/server.h" #include "parselink/server.h"
#include "parselink/msgpack/token/reader.h" #include "parselink/logging.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/proto/session.h"
#include "parselink/utility/file.h"
#include <fmt/ranges.h> #include <fmt/ranges.h>
#include <boost/asio/io_context.hpp> #include "hydrogen.h"
#include <boost/asio/signal_set.hpp> #include <boost/asio/as_tuple.hpp>
#include <boost/asio/redirect_error.hpp> #include <boost/asio/bind_executor.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/co_spawn.hpp> #include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp> #include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp> #include <boost/asio/detached.hpp>
#include <boost/asio/as_tuple.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp>
#include <unordered_set>
#include <chrono> using namespace parselink;
#include <map> #include <fmt/ranges.h>
using namespace parselink; using namespace parselink;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace net = boost::asio; namespace net = boost::asio;
using net::co_spawn;
using net::awaitable; using net::awaitable;
using net::use_awaitable; using net::co_spawn;
using net::deferred; using net::deferred;
using net::detached; using net::detached;
using net::use_awaitable;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// TODO(ksassenrath): These are logging formatters for various boost/asio types. // TODO(ksassenrath): These are logging formatters for various boost/asio types.
@ -71,28 +75,40 @@ 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 <> template <>
struct fmt::formatter<msgpack::token> { struct fmt::formatter<msgpack::token> {
template <typename ParseContext> template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
template <typename FormatContext> template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const { auto format(msgpack::token const& v, FormatContext& ctx) const {
using parselink::logging::themed_arg; using parselink::logging::themed_arg;
auto out = fmt::format_to(ctx.out(), "<msgpack {} = ", themed_arg(v.type())); auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", themed_arg(v.type()));
switch (v.type()) { switch (v.type()) {
case msgpack::format::type::unsigned_int: case msgpack::format::type::unsigned_int:
fmt::format_to(out, "{}", themed_arg(*(v.get<std::uint64_t>()))); fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break; break;
case msgpack::format::type::signed_int: case msgpack::format::type::signed_int:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<std::uint64_t>()))); out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break; break;
case msgpack::format::type::boolean: case msgpack::format::type::boolean:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>()))); out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>())));
break; break;
case msgpack::format::type::string: case msgpack::format::type::string:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<std::string_view>()))); out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break; break;
case msgpack::format::type::binary: case msgpack::format::type::binary:
out = fmt::format_to(out, "{}", out = fmt::format_to(out, "{}",
@ -112,8 +128,7 @@ struct fmt::formatter<msgpack::token> {
case msgpack::format::type::invalid: case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)"); out = fmt::format_to(out, "(invalid)");
break; break;
default: default: break;
break;
} }
return fmt::format_to(out, ">"); return fmt::format_to(out, ">");
} }
@ -130,12 +145,11 @@ struct parselink::logging::theme<T>
: parselink::logging::static_theme<fmt::color::coral> {}; : parselink::logging::static_theme<fmt::color::coral> {};
template <endpoint T> template <endpoint T>
struct fmt::formatter<T> struct fmt::formatter<T> : fmt::formatter<std::string_view> {
: fmt::formatter<std::string_view> {
template <typename FormatContext> template <typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const { constexpr auto format(auto const& v, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}:{}", v.address().to_string(), return fmt::format_to(
v.port()); ctx.out(), "{}:{}", v.address().to_string(), v.port());
} }
}; };
@ -147,13 +161,12 @@ namespace {
logging::logger logger("server"); logging::logger logger("server");
constexpr auto no_ex_coro = net::as_tuple(use_awaitable); constexpr auto no_ex_coro = net::as_tuple(use_awaitable);
constexpr auto no_ex_defer = net::as_tuple(deferred); constexpr auto no_ex_defer = net::as_tuple(deferred);
}
struct user_session { } // namespace
std::string user_id;
std::array<std::byte, 32> session_id; #include <parselink/server/memory_session_manager.h>
std::chrono::system_clock::time_point expires_at;
}; class user_connection;
class monolithic_server : public server { class monolithic_server : public server {
public: public:
@ -162,73 +175,33 @@ public:
std::error_code run() noexcept override; std::error_code run() noexcept override;
net::awaitable<tl::expected<proto::session*, proto::error>> create_session(
std::shared_ptr<user_connection> const& conn,
proto::connect_message const& info);
private: private:
friend user_session;
awaitable<void> user_listen(); awaitable<void> user_listen();
user_session* establish_session(user_session session); tl::expected<tl::monostate, std::errc> load_keys() noexcept;
std::map<std::string, user_session, std::less<>> active_user_sessions_;
hydro_kx_keypair kp_;
net::io_context io_context_; net::io_context io_context_;
net::io_context::strand session_strand_;
memory_session_manager session_mgr_;
net::ip::address addr_; net::ip::address addr_;
std::uint16_t user_port_; std::uint16_t user_port_;
std::uint16_t websocket_port_; std::uint16_t websocket_port_;
}; };
tl::expected<user_session, msgpack::error> handle_connect(
std::span<msgpack::token> tokens) noexcept {
user_session user;
auto message_type = tokens.begin()->get<std::string_view>();
if (message_type) {
logger.debug("Received '{}' packet. Parsing body", *message_type);
auto map = msgpack::map_view(tokens.subspan(1));
constexpr auto find_version = [](auto const& kv) {
return kv.first == "version";
};
auto field = std::ranges::find_if(map, find_version);
if (field != map.end() && (*field).second == 1u) {
logger.debug("Version {}\n", (*field).second);
} else {
logger.error("connect failed: missing / unsupported version");
return tl::make_unexpected(msgpack::error::unsupported);
}
for (auto const& [k, v] : msgpack::map_view(tokens.subspan(1))) {
tl::expected<void, msgpack::error> result;
if (k.type() != msgpack::format::type::string) {
logger.error("connect failed: non-string key {}", k);
return tl::make_unexpected(msgpack::error::bad_value);
}
if (k == "user_id") {
result = v.get<std::string>().map([&user](auto uid){
user.user_id = std::move(uid);
});
}
if (!result) {
logger.error("connect failed: {} -> {}: {}", k, v,
result.error());
return tl::make_unexpected(msgpack::error::bad_value);
}
}
} else {
logger.error("Did not get message type: {}", message_type.error());
return tl::make_unexpected(msgpack::error::bad_value);
}
return user;
}
class user_connection : public std::enable_shared_from_this<user_connection> { class user_connection : public std::enable_shared_from_this<user_connection> {
public: public:
user_connection(monolithic_server& server, net::ip::tcp::socket sock) user_connection(monolithic_server& server, net::ip::tcp::socket sock)
: server_(server) : server_(server)
, socket_(std::move(sock)) {} , socket_(std::move(sock)) {}
~user_connection() { ~user_connection() { stop(); }
void stop() {
logger.debug("Connection to {} closed.", socket_.remote_endpoint()); logger.debug("Connection to {} closed.", socket_.remote_endpoint());
boost::system::error_code ec; boost::system::error_code ec;
socket_.shutdown(net::ip::tcp::socket::shutdown_both, ec); socket_.shutdown(net::ip::tcp::socket::shutdown_both, ec);
@ -237,145 +210,115 @@ public:
void start() { void start() {
logger.debug("New connection from {}", socket_.remote_endpoint()); logger.debug("New connection from {}", socket_.remote_endpoint());
co_spawn(socket_.get_executor(), [self = shared_from_this()]{ co_spawn(
return self->await_connect(); socket_.get_executor(),
}, detached); [self = shared_from_this()] { return self->await_connect(); },
detached);
} }
tl::expected<std::vector<std::byte>, msgpack::error> parse_header( awaitable<tl::expected<tl::monostate, proto::error>> read_message(
std::span<std::byte> data) noexcept { std::vector<std::byte>& buffer) noexcept {
auto reader = msgpack::token_reader(data); // Use a small buffer on the stack to read the initial header.
std::array<std::byte, 8> hdrbuff;
auto [ec, n] = co_await socket_.async_read_some(
net::buffer(hdrbuff), no_ex_coro);
auto magic = reader.read_one().map( if (ec) {
[](auto t){ return t == "prs"; }); logger.error("Reading hdr from user socket failed: {}", ec);
if (magic && *magic) { co_return tl::make_unexpected(proto::error::system_error);
logger.debug("Got magic from client");
} else {
logger.error("Failed to get magic from client: {}", magic);
return tl::unexpected(magic.error());
} }
auto sz = reader.read_one().and_then( logger.debug("Read {} bytes from client: {}", n,
[](auto t){ return t.template get<std::size_t>(); }); std::span(hdrbuff.data(), n));
if (sz && *sz) {
logger.debug("Got packet size from client: {}", *sz); auto maybe_hdr = proto::parse_header(std::span(hdrbuff.data(), n));
} else {
logger.debug("Failed to get packet size from client: {}", sz); if (!maybe_hdr) {
return tl::unexpected(magic.error()); logger.error("Unable to parse header: {}", maybe_hdr.error());
co_return tl::make_unexpected(maybe_hdr.error());
} }
// Copy the rest of the message to the buffer for parsing. // TODO(ksassenrath): Replace with specialized allocator.
// TODO(ksassenrath): Replace vector with custom buffer. buffer.resize(maybe_hdr->message_size);
std::vector<std::byte> msg;
msg.reserve(*sz);
msg.resize(reader.remaining());
std::copy(reader.current(), reader.end(), msg.begin());
return msg;
}
awaitable<tl::expected<std::monostate, boost::system::error_code>> // Copy remaining portion of message in initial read to the message
buffer_message(std::span<std::byte> buffer) noexcept { // buffer.
std::copy(std::next(hdrbuff.begin(), maybe_hdr->bytes_read),
std::next(hdrbuff.begin(), n), buffer.begin());
auto span = std::span(
buffer.begin() + n - maybe_hdr->bytes_read, buffer.end());
// Buffer remaining message.
std::size_t amt = 0; std::size_t amt = 0;
while (amt < buffer.size()) { while (amt < span.size()) {
auto subsp = buffer.subspan(amt); auto subsp = span.subspan(amt);
auto [ec, n] = co_await socket_.async_read_some( auto [ec, n] = co_await socket_.async_read_some(
net::buffer(subsp), no_ex_coro); net::buffer(subsp), no_ex_coro);
logger.debug("Read {} bytes, total is now {}", n, amt + n); logger.debug("Read {} bytes, total is now {}", n, amt + n);
if (ec || n == 0) { if (ec || n == 0) {
logger.error("Reading from user socket failed: {}", ec); logger.error("Reading msg from user socket failed: {}", ec);
co_return tl::make_unexpected(ec); co_return tl::make_unexpected(proto::error::system_error);
} }
amt += n; amt += n;
} }
co_return std::monostate{};
co_return tl::monostate{};
} }
awaitable<void> await_connect() noexcept { awaitable<void> await_connect() noexcept {
// Use a small buffer on the stack to read the initial header. std::vector<std::byte> msgbuf;
std::array<std::byte, 8> buffer;
auto [ec, n] = co_await socket_.async_read_some(
net::buffer(buffer), no_ex_coro);
if (ec) { if (auto maybe_msg = co_await read_message(msgbuf); !maybe_msg) {
logger.error("Reading from user socket failed: {}", ec); logger.debug("returning");
co_return; co_return;
} }
logger.debug("Read {} bytes from client: {}", n, auto connect_info = proto::parse<proto::connect_message>(msgbuf);
std::span(buffer.data(), n));
proto::session session; if (!connect_info) {
logger.error("Session failed: {}", connect_info.error());
auto maybe_hdr = session.parse_header(std::span(buffer.data(), n));
if (!maybe_hdr) {
logger.error("Unable to parse header: {}", maybe_hdr.error());
co_return; co_return;
} }
// TODO(ksassenrath): Replace with specialized allocator. auto session = co_await server_.create_session(
auto msg = std::vector<std::byte>(maybe_hdr->message_size); shared_from_this(), *connect_info);
if (!session) {
// Copy remaining portion of message in initial read to the message
// buffer.
std::copy(
std::next(buffer.begin(), maybe_hdr->bytes_read),
std::next(buffer.begin(), n),
msg.begin());
auto msg_span = std::span(msg.begin() + n - maybe_hdr->bytes_read, msg.end());
if (auto result = co_await buffer_message(msg_span); !result) {
logger.error("Unable to parse header: {}", result.error());
co_return; co_return;
} }
session_ = *session;
auto reader = msgpack::token_reader(msg);
std::array<msgpack::token, 32> tokens;
auto maybe_user = reader.read_some(tokens)
.and_then(handle_connect)
.map_error([](auto const& error) {
logger.error("Unable to parse msgpack tokens: {}", error);
});
if (!maybe_user) {
co_return;
} }
// Authenticate against database. enum class state { init, authenticated, active };
logger.debug("User {} established connection", maybe_user->user_id);
//session_ = server_.establish_session(std::move(*maybe_user));
}
enum class state {
init,
authenticated,
active
};
monolithic_server& server_; monolithic_server& server_;
user_session* session_{};
net::ip::tcp::socket socket_; net::ip::tcp::socket socket_;
proto::session* session_{};
}; };
monolithic_server::monolithic_server(std::string_view address, monolithic_server::monolithic_server(std::string_view address,
std::uint16_t user_port, std::uint16_t websocket_port) std::uint16_t user_port, std::uint16_t websocket_port)
: io_context_{1} : io_context_{1}
, session_strand_(io_context_)
, session_mgr_(io_context_)
, addr_(net::ip::address::from_string(std::string{address})) , addr_(net::ip::address::from_string(std::string{address}))
, user_port_{user_port} , user_port_{user_port}
, websocket_port_{websocket_port} { , websocket_port_{websocket_port} {
logger.debug("Loaded keys: {}", load_keys());
logger.debug("Creating monolithic_server(address = {}, user_port = {}, " logger.debug("Creating monolithic_server(address = {}, user_port = {}, "
"websocket_port = {})", address, user_port_, websocket_port_); "websocket_port = {})",
address, user_port_, websocket_port_);
} }
awaitable<void> monolithic_server::user_listen() { awaitable<void> monolithic_server::user_listen() {
auto exec = co_await net::this_coro::executor; auto exec = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor{exec, {addr_, user_port_}}; net::ip::tcp::acceptor acceptor{exec, {addr_, user_port_}};
while (true) { while (true) {
std::make_shared<user_connection>(*this, std::make_shared<user_connection>(
co_await acceptor.async_accept(use_awaitable))->start(); *this, co_await acceptor.async_accept(use_awaitable))
->start();
} }
} }
@ -395,14 +338,79 @@ std::error_code monolithic_server::run() noexcept {
return {}; return {};
} }
user_session* monolithic_server::establish_session(user_session session) { tl::expected<tl::monostate, std::errc> monolithic_server::load_keys() noexcept {
auto existing_session = active_user_sessions_.find(session.user_id); std::string_view filename = "/home/rihya/server_kp.keys";
if (existing_session == active_user_sessions_.end()) {
// No session exists with that user ID yet. enum class result { loaded, generated };
active_user_sessions_.emplace(session.user_id,
std::move(session.user_id)); 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); });
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<result, std::errc> {
logger.warning("Could not load server keys, generating a keypair");
hydro_kx_keygen(&kp_);
return result::generated;
};
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 {}; 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_message const& info) {
// Move to session strand.
co_await net::post(session_strand_,
net::bind_executor(session_strand_, use_awaitable));
auto session = session_mgr_.create_session(info.user_id);
logger.info("Created session: {}", session);
co_return session;
} }
std::unique_ptr<server> parselink::make_server(std::string_view address, std::unique_ptr<server> parselink::make_server(std::string_view address,

20
source/server/BUILD Normal file
View File

@ -0,0 +1,20 @@
# parselink
cc_library(
name = "server",
srcs = [
"memory_session_manager.cpp",
],
deps = [
"//include/parselink:proto",
"//include/parselink:msgpack",
"//include/parselink:server",
"//source/logging",
"@hydrogen",
"@boost//:asio",
],
visibility = [
# TODO: Fix visibility
"//visibility:public",
],
)

View File

@ -0,0 +1,84 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Simple in-memory session manager.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include <parselink/logging.h>
#include <parselink/server/memory_session_manager.h>
#include <fmt/chrono.h>
using namespace parselink;
using namespace std::chrono_literals;
namespace {
logging::logger logger("memory_session_manager");
constexpr std::size_t max_open_sessions = 1000;
constexpr auto expires_time = 1min;
} // namespace
memory_session_manager::memory_session_manager(boost::asio::io_context& ctx)
: ctx_(ctx)
, strand_(ctx) {
logger.trace("Creating: {}", this);
}
memory_session_manager::~memory_session_manager() {
logger.trace("Destroying: {}", this);
}
tl::expected<proto::session*, proto::error>
memory_session_manager::create_session(std::string_view user_id) {
auto [itr, inserted] = sessions_.try_emplace(std::string{user_id}, user_id);
if (!inserted) {
logger.debug("Session already found for {}, last activity"
" {:%Y-%m-%d %H:%M:%S}",
user_id, fmt::gmtime(itr->second.last_activity()));
if (itr->second.last_activity() + expires_time
< std::chrono::system_clock::now()) {
logger.debug("Session expired. Creating new session");
itr->second = proto::session{user_id};
}
return &itr->second;
}
lookup_by_sid_.emplace(itr->second.id(), &itr->second);
return &itr->second;
}
tl::expected<tl::monostate, proto::error>
memory_session_manager::destroy_session(proto::session* s) {
return tl::make_unexpected(proto::error::unsupported);
}
tl::expected<proto::session*, proto::error> memory_session_manager::find(
std::string_view user_id) {
auto x = sessions_.find(user_id);
if (x != sessions_.end()) {
return &x->second;
}
return tl::make_unexpected(proto::error::unsupported);
}
tl::expected<proto::session*, proto::error> memory_session_manager::find(
proto::session_id const& sid) {
auto x = lookup_by_sid_.find(sid);
if (x != lookup_by_sid_.end()) {
return x->second;
}
return tl::make_unexpected(proto::error::unsupported);
}

View File

@ -32,16 +32,18 @@ struct test_endpoint_base : public endpoint {
void write(message const& msg) override { void write(message const& msg) override {
if constexpr (Colored) { if constexpr (Colored) {
buffer_.append(fmt::format("{} {} | {}", buffer_.append(fmt::format("{} {} | {}",
themed_arg{enum_name_only{msg.lvl}}, msg.name, msg.message)); themed_arg{enum_name_only{msg.lvl}}, msg.name,
msg.message));
} else { } else {
buffer_.append(fmt::format("{} {} | {}", buffer_.append(fmt::format("{} {} | {}", enum_name_only{msg.lvl},
enum_name_only{msg.lvl}, msg.name, msg.message)); msg.name, msg.message));
} }
} }
bool colored() const noexcept override { return Colored; } bool colored() const noexcept override { return Colored; }
void clear() { buffer_.clear(); } void clear() { buffer_.clear(); }
std::string_view contents() const { return buffer_; } std::string_view contents() const { return buffer_; }
std::string buffer_; std::string buffer_;
@ -58,11 +60,15 @@ using colored_test_endpoint = test_endpoint_base<true>;
// In order to test themed (colorized) logging, we must be sure that theming // In order to test themed (colorized) logging, we must be sure that theming
// gets correctly-styled content. // gets correctly-styled content.
struct static_theme_test { int value; }; struct static_theme_test {
int value;
};
// In order to test themed (colorized) logging, we must be sure that theming // In order to test themed (colorized) logging, we must be sure that theming
// gets correctly-styled content. // gets correctly-styled content.
struct dynamic_theme_test { int value; }; struct dynamic_theme_test {
int value;
};
template <has_theme T> template <has_theme T>
auto styled(T&& v) { auto styled(T&& v) {
@ -75,7 +81,7 @@ auto styled(T&& v) {
} }
} }
} } // namespace
template <> template <>
struct fmt::formatter<static_theme_test> : public fmt::formatter<int> { struct fmt::formatter<static_theme_test> : public fmt::formatter<int> {
@ -104,7 +110,6 @@ struct parselink::logging::theme<dynamic_theme_test> {
} }
}; };
// Begin tests! // Begin tests!
using namespace boost::ut; using namespace boost::ut;
@ -226,8 +231,8 @@ suite logging = [] {
{ {
auto const& cptrref = ptr; auto const& cptrref = ptr;
logger.log<level::info>("int const pointer&: {}", cptrref); logger.log<level::info>("int const pointer&: {}", cptrref);
auto expected = fmt::format( auto expected =
"info formatting | int const pointer&: {}", fmt::format("info formatting | int const pointer&: {}",
fmt::ptr(cptrref)); fmt::ptr(cptrref));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -235,8 +240,8 @@ suite logging = [] {
{ {
logger.log<level::info>("std::unique_ptr<int>: {}", uniq_ptr); logger.log<level::info>("std::unique_ptr<int>: {}", uniq_ptr);
auto expected = fmt::format( auto expected =
"info formatting | std::unique_ptr<int>: {}", fmt::format("info formatting | std::unique_ptr<int>: {}",
fmt::ptr(uniq_ptr)); fmt::ptr(uniq_ptr));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -245,8 +250,8 @@ suite logging = [] {
{ {
auto& uniq_ptrref = uniq_ptr; auto& uniq_ptrref = uniq_ptr;
logger.log<level::info>("std::unique_ptr<int>&: {}", uniq_ptrref); logger.log<level::info>("std::unique_ptr<int>&: {}", uniq_ptrref);
auto expected = fmt::format( auto expected =
"info formatting | std::unique_ptr<int>&: {}", fmt::format("info formatting | std::unique_ptr<int>&: {}",
fmt::ptr(uniq_ptrref)); fmt::ptr(uniq_ptrref));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -254,8 +259,8 @@ suite logging = [] {
{ {
auto const& cuniq_ptrref = uniq_ptr; auto const& cuniq_ptrref = uniq_ptr;
logger.log<level::info>("std::unique_ptr<int> const&: {}", logger.log<level::info>(
cuniq_ptrref); "std::unique_ptr<int> const&: {}", cuniq_ptrref);
auto expected = fmt::format( auto expected = fmt::format(
"info formatting | std::unique_ptr<int> const&: {}", "info formatting | std::unique_ptr<int> const&: {}",
fmt::ptr(cuniq_ptrref)); fmt::ptr(cuniq_ptrref));
@ -265,8 +270,8 @@ suite logging = [] {
{ {
logger.log<level::info>("std::shared_ptr<int>: {}", shr_ptr); logger.log<level::info>("std::shared_ptr<int>: {}", shr_ptr);
auto expected = fmt::format( auto expected =
"info formatting | std::shared_ptr<int>: {}", fmt::format("info formatting | std::shared_ptr<int>: {}",
fmt::ptr(shr_ptr)); fmt::ptr(shr_ptr));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -275,8 +280,8 @@ suite logging = [] {
{ {
auto& shr_ptrref = shr_ptr; auto& shr_ptrref = shr_ptr;
logger.log<level::info>("std::shared_ptr<int>&: {}", shr_ptrref); logger.log<level::info>("std::shared_ptr<int>&: {}", shr_ptrref);
auto expected = fmt::format( auto expected =
"info formatting | std::shared_ptr<int>&: {}", fmt::format("info formatting | std::shared_ptr<int>&: {}",
fmt::ptr(shr_ptrref)); fmt::ptr(shr_ptrref));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -284,8 +289,8 @@ suite logging = [] {
{ {
auto const& cshr_ptrref = shr_ptr; auto const& cshr_ptrref = shr_ptr;
logger.log<level::info>("std::shared_ptr<int> const&: {}", logger.log<level::info>(
cshr_ptrref); "std::shared_ptr<int> const&: {}", cshr_ptrref);
auto expected = fmt::format( auto expected = fmt::format(
"info formatting | std::shared_ptr<int> const&: {}", "info formatting | std::shared_ptr<int> const&: {}",
fmt::ptr(cshr_ptrref)); fmt::ptr(cshr_ptrref));
@ -330,8 +335,8 @@ suite logging = [] {
dynamic_theme_test red{21}, green{202}; dynamic_theme_test red{21}, green{202};
auto formatted = fmt::format("{}", styled(stt)); auto formatted = fmt::format("{}", styled(stt));
auto expected = fmt::format("{}", auto expected = fmt::format(
fmt::styled(stt.value, fmt::fg(fmt::color::black))); "{}", fmt::styled(stt.value, fmt::fg(fmt::color::black)));
expect(formatted == expected); expect(formatted == expected);
formatted = fmt::format("{}", themed_arg{stt}); formatted = fmt::format("{}", themed_arg{stt});
expect(formatted == expected); expect(formatted == expected);
@ -351,8 +356,8 @@ suite logging = [] {
auto [logger, ep] = make_test_logger<colored_test_endpoint>("colored"); auto [logger, ep] = make_test_logger<colored_test_endpoint>("colored");
logger.log<level::info>(""); logger.log<level::info>("");
auto expected = fmt::format("{} colored | ", auto expected = fmt::format(
styled(enum_name_only{level::info})); "{} colored | ", styled(enum_name_only{level::info}));
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
@ -387,12 +392,11 @@ suite logging = [] {
auto* int_ptr = &int_value; auto* int_ptr = &int_value;
logger.log<level::info>("pointer {} {}", int_ptr, *int_ptr); logger.log<level::info>("pointer {} {}", int_ptr, *int_ptr);
expected = fmt::format("{} colored | pointer {} {}", expected = fmt::format("{} colored | pointer {} {}",
styled(enum_name_only{level::info}), styled(enum_name_only{level::info}), themed_arg{int_ptr},
themed_arg{int_ptr}, themed_arg{*int_ptr}); themed_arg{*int_ptr});
expect(ep->contents() == expected); expect(ep->contents() == expected);
ep->clear(); ep->clear();
}; };
}; };
int main(int, char**) { int main(int, char**) {}
}

View File

@ -1,10 +1,11 @@
cc_library( cc_library(
name = "test_deps", name = "common",
srcs = [ srcs = [
"test_main.cpp", "test_main.cpp",
"rng.h",
], ],
includes = ["include"],
hdrs = glob(["include/*.h"]),
deps = [ deps = [
"//include/parselink:msgpack", "//include/parselink:msgpack",
"@expected", "@expected",
@ -12,47 +13,53 @@ cc_library(
"@magic_enum", "@magic_enum",
"@ut", "@ut",
], ],
visibility = ["__subpackages__"],
) )
cc_test( cc_test(
name = "reader", name = "reader",
size = "small",
srcs = [ srcs = [
"test_reader_relaxed.cpp", "test_reader_relaxed.cpp",
"test_reader_strict.cpp", "test_reader_strict.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
) )
cc_test( cc_test(
name = "writer", name = "writer",
size = "small",
srcs = [ srcs = [
"test_writer.cpp", "test_writer.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
) )
cc_test( cc_test(
name = "token", name = "token",
size = "small",
srcs = [ srcs = [
"test_token.cpp", "test_token.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
) )
cc_test( cc_test(
name = "token_reader", name = "token_reader",
size = "small",
srcs = [ srcs = [
"test_token_reader.cpp", "test_token_reader.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
) )
cc_test( cc_test(
name = "token_views", name = "token_views",
size = "small",
srcs = [ srcs = [
"test_token_views.cpp", "test_token_views.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
) )
cc_binary( cc_binary(
@ -60,5 +67,13 @@ cc_binary(
srcs = [ srcs = [
"test_speed.cpp", "test_speed.cpp",
], ],
deps = ["test_deps"], deps = ["common"],
)
cc_binary(
name = "code",
srcs = [
"test_code.cpp",
],
deps = ["common"],
) )

View File

@ -0,0 +1,102 @@
#ifndef msgpack_test_utils_4573e6627d8efe78
#define msgpack_test_utils_4573e6627d8efe78
// clang-format off : Must include fmt/format before extra/formatters.
#include <fmt/format.h>
#include "parselink/msgpack/extra/formatters.h"
// clang-format on
#include <tl/expected.hpp>
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <magic_enum_format.hpp>
#include <source_location>
namespace test {
template <typename T>
inline constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(std::forward<T>(t));
}
template <typename... Bytes>
inline constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(
Bytes&&... bytes) noexcept {
return {std::byte(std::forward<Bytes>(bytes))...};
}
} // namespace test
// This formatter allows expected values to be formatted for both its expected
// and unexpected types.
template <typename T, typename E>
struct fmt::formatter<tl::expected<T, E>> {
int width = 0;
fmt::formatter<T> t_fmtr;
fmt::formatter<E> e_fmtr;
constexpr auto parse(auto& ctx) {
using char_type =
typename std::remove_reference_t<decltype(ctx)>::char_type;
auto delim = char_type{'|'};
auto close_brace = char_type{'}'};
std::array<char_type, 32> fmtbuf;
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') {
return ctx.begin();
}
bool t_parsed = false;
auto itr = ctx.begin();
auto buf = fmtbuf.begin();
while (*itr != close_brace) {
if (*itr == delim) {
if (t_parsed) {
fmt::throw_format_error("multiple delims encountered");
}
t_parsed = true;
*buf = close_brace;
auto str = basic_string_view<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> subparser(str, 0);
t_fmtr.parse(subparser);
buf = fmtbuf.begin();
} else {
*buf = *itr;
++buf;
}
++itr;
}
*buf = close_brace;
auto str = basic_string_view<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> subparser(str, 0);
if (t_parsed) {
e_fmtr.parse(subparser);
} else {
t_fmtr.parse(subparser);
}
ctx.advance_to(itr);
return itr;
}
auto format(tl::expected<T, E> const& v, auto& ctx) const {
if (v) {
// return fmt::format_to(ctx.out(), "{}", v.value());
return t_fmtr.format(v.value(), ctx);
} else {
return e_fmtr.format(v.error(), ctx);
}
}
};
#endif // msgpack_test_utils_4573e6627d8efe78

View File

@ -0,0 +1,9 @@
cc_test(
name = "packer",
size = "small",
srcs = glob([
"*.cpp",
"*.h",
]),
deps = ["//tests/msgpack:common", "@rapidcheck"],
)

View File

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, byte types
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
using namespace boost::ut;
suite pack_bytes = [] {
#if 0
// Byte ranges -> Binary
"packer::pack<std::span<std::byte>>"_test = [] {
expect(test_deduced<std::span<std::byte const>>());
};
#endif
"packer::pack<std::array<std::byte, N>>"_test = [] {
{
constexpr auto data = test::make_bytes(0x01, 0x02, 0x03, 0x04);
constexpr auto expected =
test::make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04);
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 256> data{};
constexpr std::array<std::byte, 259> expected{
std::byte(0xc5), std::byte(0x01), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 65536> data{};
constexpr std::array<std::byte, std::size(data) + 5> expected{
std::byte(0xc6), std::byte(0x00), std::byte(0x01),
std::byte(0x00), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
};
};

View File

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, ranges (for types that serialize to array / map formats)
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <fmt/ranges.h>
using namespace boost::ut;
namespace {
constexpr bool expect_equal(auto const& expected, auto const& actual) {
if (!std::ranges::equal(expected, actual)) {
fmt::print("\n\tExpected: ");
for (auto x : expected) {
fmt::print("{:02x} ", x);
}
fmt::print("\n\t Actual: ");
for (auto x : actual) {
fmt::print("{:02x} ", x);
}
fmt::print("\n");
return false;
}
return true;
}
} // namespace
suite pack_ranges = [] {
"packer::pack<std::array/std::span<int>>"_test = [] {
constexpr auto data_array = std::to_array<int>({5, 10, 15, 20});
constexpr auto expected = test::make_bytes(0x94, 0x05, 0xa, 0xf, 0x14);
{
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_array));
expect(expect_equal(std::span(expected), payload));
}
{
std::span data_span{data_array};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_span));
expect(expect_equal(expected, payload));
}
};
};

View File

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, signed ints.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
auto check_pack() {
return rc::check([](T value) {
std::array<std::byte, 16> payload;
msgpack::packer packer(payload);
if (!packer.pack(value)) return false;
if (value < 128 && value >= -32) {
// positive_fixint/negative_fixint
return packer.tell() == 1
&& payload[0] == static_cast<std::byte>(value);
} else if (within<std::int8_t>(value)) {
return payload[0] == msgpack::format::int8::marker
&& verify_packed<std::int8_t>(packer, value);
} else if (within<std::int16_t>(value)) {
return payload[0] == msgpack::format::int16::marker
&& verify_packed<std::int16_t>(packer, value);
} else if (within<std::int32_t>(value)) {
return payload[0] == msgpack::format::int32::marker
&& verify_packed<std::int32_t>(packer, value);
} else {
return payload[0] == msgpack::format::int64::marker
&& verify_packed<std::int64_t>(packer, value);
}
});
}
} // anonymous namespace
suite pack_signed_types = [] {
"packer::pack<std::int8_t>"_test = [] {
expect(check_pack<std::int8_t>());
};
"packer::pack<std::int16_t>"_test = [] {
expect(check_pack<std::int16_t>());
};
"packer::pack<std::int32_t>"_test = [] {
expect(check_pack<std::int32_t>());
};
"packer::pack<std::int64_t>"_test = [] {
expect(check_pack<std::int64_t>());
};
};

View File

@ -0,0 +1,50 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, simple types
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
using namespace boost::ut;
suite pack_simple_types = [] {
"packer::pack<nil>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(msgpack::nil{}));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc0});
};
"packer::pack<invalid>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(msgpack::invalid{}));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc1});
};
"packer::pack<bool>"_test = [] {
std::array<std::byte, 8> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(false));
expect(packer.tell() == 1);
expect(payload[0] == std::byte{0xc2});
expect(!!packer.pack(true));
expect(packer.tell() == 2);
expect(payload[1] == std::byte{0xc3});
};
};

View File

@ -0,0 +1,46 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, strings.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <std::unsigned_integral LenType, typename StrType = std::string>
auto check_string() {
return rc::check([](LenType value) {
auto str = *rc::gen::container<std::string>(
value, rc::gen::character<char>());
std::vector<std::byte> payload;
payload.resize(value + 32);
msgpack::packer packer(payload);
if (!packer.pack(str)) return false;
return true;
});
}
} // anonymous namespace
suite pack_strings = [] {
"packer::pack<std::string>"_test = [] {
expect(check_string<std::uint8_t>());
expect(check_string<std::uint16_t>());
};
};

View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, common code.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef tests_msgpack_packer_6e9a5bf8e14223bc
#define tests_msgpack_packer_6e9a5bf8e14223bc
#include "parselink/msgpack/core/packer.h"
#include "test_utils.h"
template <std::integral T>
constexpr auto within(auto value) noexcept {
return value >= std::numeric_limits<T>::min()
&& value <= std::numeric_limits<T>::max();
}
template <std::integral T>
constexpr auto verify_packed(auto const& packer, auto value) noexcept {
std::array<std::byte, (std::numeric_limits<T>::digits + 7) / 8> raw;
if (packer.tell() != 1 + raw.size()) return false;
auto packed = packer.subspan().subspan(1);
be_to_host(packed.begin(), packed.end(), raw.begin());
auto actual = std::bit_cast<T>(raw);
return actual == value;
}
#endif // tests_msgpack_packer_6e9a5bf8e14223bc

View File

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Packer tests, unsigned ints.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_packer.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
auto check_unsigned() {
return rc::check([](T value) {
std::array<std::byte, 16> payload;
msgpack::packer packer(payload);
if (!packer.pack(value)) return false;
if (value < 128) {
// positive_fixint
return packer.tell() == 1
&& payload[0] == static_cast<std::byte>(value);
} else if (within<std::uint8_t>(value)) {
return payload[0] == msgpack::format::uint8::marker
&& verify_packed<std::uint8_t>(packer, value);
} else if (within<std::uint16_t>(value)) {
return payload[0] == msgpack::format::uint16::marker
&& verify_packed<std::uint16_t>(packer, value);
} else if (within<std::uint32_t>(value)) {
return payload[0] == msgpack::format::uint32::marker
&& verify_packed<std::uint32_t>(packer, value);
} else {
return payload[0] == msgpack::format::uint64::marker
&& verify_packed<std::uint64_t>(packer, value);
}
});
}
} // anonymous namespace
suite packer_unsigned_ints = [] {
"packer::pack<std::uint8_t>"_test = [] {
expect(check_unsigned<std::uint8_t>());
};
"packer::pack<std::uint16_t>"_test = [] {
expect(check_unsigned<std::uint16_t>());
};
"packer::pack<std::uint32_t>"_test = [] {
expect(check_unsigned<std::uint32_t>());
};
"packer::pack<std::uint64_t>"_test = [] {
expect(check_unsigned<std::uint64_t>());
};
};

View File

@ -5,16 +5,23 @@
#include <random> #include <random>
struct rng { struct rng {
rng(auto s) : seed(s), generator(seed) {} rng(auto s)
rng() : rng([]{ return std::random_device{}(); }()) {} : seed(s)
, generator(seed) {}
rng()
: rng([] { return std::random_device{}(); }()) {}
template <typename T> template <typename T>
requires std::is_integral_v<T> requires std::is_integral_v<T>
T get() noexcept { T get() noexcept {
union { union {
std::array<std::mt19937::result_type, 1 + sizeof(T) / sizeof(std::mt19937::result_type)> data; std::array<std::mt19937::result_type,
1 + sizeof(T) / sizeof(std::mt19937::result_type)>
data;
T v; T v;
} u; } u;
for (auto& d : u.data) { for (auto& d : u.data) {
d = generator(); d = generator();
} }

View File

@ -0,0 +1,17 @@
#include <array>
#include <map>
#include <parselink/msgpack/core/packer.h>
int main(int argc, char** argv) {
std::map<int, std::string_view> args{};
for (int i = 0; i < argc; ++i) {
args.emplace(i, argv[i]);
}
std::array<std::byte, 500> buff;
msgpack::packer packer(buff);
packer.pack(args);
for (auto b : packer.subspan()) {
printf("%c", char(b));
}
return 0;
}

View File

@ -0,0 +1,412 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/msgpack/core/packer.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "rng.h"
#include <algorithm>
#include <boost/ut.hpp>
#include <chrono>
using namespace boost::ut;
template <>
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
}
};
template <>
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
}
};
template <>
struct fmt::formatter<msgpack::map_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::map_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::map_desc: {}}}", value.count);
}
};
template <>
struct fmt::formatter<msgpack::array_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::array_desc const& value, FormatContext& ctx) const {
return fmt::format_to(
ctx.out(), "{{msgpack::array_desc: {}}}", value.count);
}
};
namespace {
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {static_cast<std::byte>(std::forward<Bytes>(bytes))...};
}
constexpr auto equal(auto a, auto b) {
return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename T>
struct test_data {};
template <>
struct test_data<msgpack::nil> {
static constexpr msgpack::nil values[] = {{}};
static constexpr auto payload = make_bytes(0xc0);
};
template <>
struct test_data<msgpack::invalid> {
static constexpr msgpack::invalid values[] = {{}};
static constexpr auto payload = make_bytes(0xc1);
};
template <>
struct test_data<bool> {
static constexpr auto values = std::to_array<bool>({false, true});
static constexpr auto payload = make_bytes(0xc2, 0xc3);
};
template <>
struct test_data<std::uint64_t> {
static constexpr auto values = std::to_array<std::uint64_t>({
0x00, // positive fixint
0x79, // positive fixint
0x80, // uint8
0xff, // uint8
0x100, // uint16
0xffff, // uint16
0x10000, // uint32
0xffffffff, // uint32
0x100000000, // uint64
0xffffffffffffffff // uint64
});
static constexpr auto payload = make_bytes(0x00, // positive fixint
0x79, // positive fixint
0xcc, 0x80, // uint8
0xcc, 0xff, // uint8
0xcd, 0x01, 0x00, // uint16
0xcd, 0xff, 0xff, // uint16
0xce, 0x00, 0x01, 0x00, 0x00, // uint32
0xce, 0xff, 0xff, 0xff, 0xff, // uint32
0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // uint64
0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // uint64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<std::uint64_t>::max();
}
};
template <std::unsigned_integral T>
struct test_data<T> : test_data<std::uint64_t> {
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max();
}
};
template <>
struct test_data<std::int64_t> {
static constexpr auto values = std::to_array<std::int64_t>({
-1, // negative fixint
-32, // negative fixint
-33, // int8
-128, // int8
0, // int8
127, // int8
128, // int16
-129, // int16
-32768, // int16
32767, // int16
-32769, // int32
32768, // int32
-2147483648, // int32
2147483647, // int32
-2147483649, // int64
2147483648, // int64
std::numeric_limits<std::int64_t>::lowest(), // int64
std::numeric_limits<std::int64_t>::max(), // int64
});
static constexpr auto payload = make_bytes(0xff, // negative fixint
0xe0, // negative fixint
0xd0, 0xdf, // int8
0xd0, 0x80, // int8
0xd0, 0x0, // int8
0xd0, 0x7f, // int8
0xd1, 0x00, 0x80, // int16
0xd1, 0xff, 0x7f, // int16
0xd1, 0x80, 0x00, // int16
0xd1, 0x7f, 0xff, // int16
0xd2, 0xff, 0xff, 0x7f, 0xff, // int32
0xd2, 0x00, 0x00, 0x80, 0x00, // int32
0xd2, 0x80, 0x00, 0x00, 0x00, // int32
0xd2, 0x7f, 0xff, 0xff, 0xff, // int32
0xd3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, // int64
0xd3, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, // int64
0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // int64
0xd3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // int64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<std::int64_t>::max()
&& value >= std::numeric_limits<std::int64_t>::lowest();
}
};
template <>
struct test_data<msgpack::array_desc> {
static constexpr auto values = std::to_array<msgpack::array_desc>({
1, // fixarray
15, // fixarray
16, // array16
65535, // array16
65536 // array32
});
static constexpr auto payload = make_bytes(0x91, // fixarray
0x9f, // fixarray
0xdc, 0x00, 0x10, // array16
0xdc, 0xff, 0xff, // array16
0xdd, 0x00, 0x01, 0x00, 0x00 // array32
);
};
template <>
struct test_data<msgpack::map_desc> {
static constexpr auto values = std::to_array<msgpack::map_desc>({
1, // fixmap
15, // fixmap
16, // map16
65535, // map16
65536 // map32
});
static constexpr auto payload = make_bytes(0x81, // fixmap
0x8f, // fixmap
0xde, 0x00, 0x10, // map16
0xde, 0xff, 0xff, // map16
0xdf, 0x00, 0x01, 0x00, 0x00 // map32
);
};
template <std::convertible_to<std::string_view> T>
struct test_data<T> {
static constexpr auto values = std::to_array<T>({
"", // fixstr
"0", // fixstr
"0123456789abcdef0123456789abcde", // fixstr
"0123456789abcdef0123456789abcdef", // str8
});
static constexpr auto payload = make_bytes(0xa0, // fixstr
0xa1, 0x30, // fixstr
// fixstr
0xbf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
// str8
0xd9, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
0x66);
static constexpr auto valid(T value) noexcept {
return std::string_view(value).size()
<= std::numeric_limits<std::uint32_t>::max();
}
};
template <>
struct test_data<std::span<std::byte const>> {
static constexpr auto test1 = make_bytes(0x02, 0x03);
static constexpr auto values =
std::to_array<std::span<std::byte const>>({test1});
static constexpr auto payload = make_bytes(0xc4, 0x02, 0x02, 0x03);
static constexpr auto valid(auto value) noexcept {
return value.size() <= std::numeric_limits<std::uint32_t>::max();
}
};
template <std::signed_integral T>
struct test_data<T> : test_data<std::int64_t> {
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max()
&& value >= std::numeric_limits<T>::lowest();
}
};
template <typename T>
bool test_deduced() noexcept {
constexpr auto const& expected_payload = test_data<T>::payload;
std::array<std::byte, expected_payload.size()> payload;
msgpack::packer packer(payload);
for (auto const& value : test_data<T>::values) {
if constexpr (requires { test_data<T>::valid(value); }) {
if (!test_data<T>::valid(value)) break;
}
expect(!!packer.pack(T(value)));
auto expect = std::span(expected_payload.begin(), packer.tell());
auto correct = equal(packer.subspan(), expect);
if (!correct) {
fmt::print("Deduction failed for '{}'\n", T(value));
fmt::print("\tActual: {::#04x}\n", packer.subspan());
fmt::print("\tExpect: {::#04x}\n", expect);
return false;
}
}
return true;
}
} // anonymous namespace
suite packer_single_format = [] {
"packer::pack<bool>"_test = [] { expect(test_deduced<bool>()); };
"packer::pack<nil>"_test = [] { expect(test_deduced<msgpack::nil>()); };
"packer::pack<invalid>"_test = [] {
expect(test_deduced<msgpack::invalid>());
};
// Unsigned ints
"packer::pack<std::uint8_t>"_test = [] {
expect(test_deduced<std::uint8_t>());
};
"packer::pack<std::uint16_t>"_test = [] {
expect(test_deduced<std::uint16_t>());
};
"packer::pack<std::uint32_t>"_test = [] {
expect(test_deduced<std::uint32_t>());
};
"packer::pack<std::uint64_t>"_test = [] {
expect(test_deduced<std::uint64_t>());
};
// Signed ints
"packer::pack<std::int8_t>"_test = [] {
expect(test_deduced<std::int8_t>());
};
"packer::pack<std::int16_t>"_test = [] {
expect(test_deduced<std::int16_t>());
};
"packer::pack<std::int32_t>"_test = [] {
expect(test_deduced<std::int32_t>());
};
"packer::pack<std::int64_t>"_test = [] {
expect(test_deduced<std::int64_t>());
};
// Strings
"packer::pack<std::string_view>"_test = [] {
expect(test_deduced<std::string_view>());
};
#if 0
"packer::pack<char const*>"_test = [] {
expect(test_deduced<char const*>());
};
#endif
// Byte ranges -> Binary
"packer::pack<std::span<std::byte>>"_test = [] {
expect(test_deduced<std::span<std::byte const>>());
};
"packer::pack<std::array<std::byte, N>>"_test = [] {
{
constexpr auto data = make_bytes(0x01, 0x02, 0x03, 0x04);
constexpr auto expected =
make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04);
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 256> data{};
constexpr std::array<std::byte, 259> expected{
std::byte(0xc5), std::byte(0x01), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
{
constexpr std::array<std::byte, 65536> data{};
constexpr std::array<std::byte, std::size(data) + 5> expected{
std::byte(0xc6), std::byte(0x00), std::byte(0x01),
std::byte(0x00), std::byte(0x00)};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data));
expect(std::ranges::equal(payload, expected));
}
};
// array_desc - Just the header of the array.
"packer::pack<msgpack::array_desc>"_test = [] {
expect(test_deduced<msgpack::array_desc>());
};
// map_desc - Just the header of the map.
"packer::pack<msgpack::map_desc>"_test = [] {
expect(test_deduced<msgpack::map_desc>());
};
};
suite packer_ranges = [] {
"packer::pack<std::array/std::span<int>>"_test = [] {
constexpr auto data_array = std::to_array<int>({5, 10, 15, 20});
constexpr auto expected =
make_bytes(0x94, 0xd0, 0x05, 0xd0, 0xa, 0xd0, 0xf, 0xd0, 0x14);
{
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_array));
expect(std::ranges::equal(payload, expected));
}
{
std::span data_span{data_array};
std::array<std::byte, std::size(expected)> payload;
msgpack::packer packer(payload);
expect(!!packer.pack(data_span));
expect(std::ranges::equal(payload, expected));
}
};
};

View File

@ -1,7 +1,5 @@
#include "parselink/msgpack/core/reader.h" #include "parselink/msgpack/core/reader.h"
#include <boost/ut.hpp> #include <boost/ut.hpp>
#include <string> #include <string>
namespace { namespace {
@ -14,7 +12,8 @@ constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...}; return {std::byte(std::forward<Bytes>(bytes))...};
} }
template <typename T, std::size_t C = 1024> struct oversized_array { template <typename T, std::size_t C = 1024>
struct oversized_array {
std::array<T, C> data; std::array<T, C> data;
std::size_t size; std::size_t size;
}; };
@ -36,8 +35,8 @@ consteval auto generate_bytes(auto callable) {
} }
template <std::size_t A, std::size_t B> template <std::size_t A, std::size_t B>
consteval auto cat(std::array<std::byte, A> const &a, consteval auto cat(
std::array<std::byte, B> const &b) { std::array<std::byte, A> const& a, std::array<std::byte, B> const& b) {
std::array<std::byte, A + B> out; std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)), std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
std::begin(out)); std::begin(out));
@ -107,7 +106,8 @@ suite relaxed = [] {
* All bytes 0x00->0x79 are effectively literal uint8s. * All bytes 0x00->0x79 are effectively literal uint8s.
*/ */
{ {
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84, 0xcd, 0xaa, 0xcc); constexpr auto payload =
make_bytes(0x52, 0xcc, 0x84, 0xcd, 0xaa, 0xcc);
relaxed_reader reader(payload); relaxed_reader reader(payload);
{ {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -127,4 +127,3 @@ suite relaxed = [] {
} }
}; };
}; };

View File

@ -1,14 +1,13 @@
#include "parselink/msgpack/core/reader.h" #include "parselink/msgpack/core/reader.h"
#include "rng.h" #include "rng.h"
#include <boost/ut.hpp> #include <boost/ut.hpp>
#include <string> #include <string>
namespace { namespace {
constexpr static std::size_t rng_samples_count = 10; constexpr static std::size_t rng_samples_count = 10;
template <typename E> constexpr auto enum_name() noexcept { template <typename E>
constexpr auto enum_name() noexcept {
return __PRETTY_FUNCTION__; return __PRETTY_FUNCTION__;
} }
@ -20,7 +19,8 @@ constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...}; return {std::byte(std::forward<Bytes>(bytes))...};
} }
template <typename T, std::size_t C = 1024> struct oversized_array { template <typename T, std::size_t C = 1024>
struct oversized_array {
std::array<T, C> data; std::array<T, C> data;
std::size_t size; std::size_t size;
}; };
@ -42,8 +42,8 @@ consteval auto generate_bytes(auto callable) {
} }
template <std::size_t A, std::size_t B> template <std::size_t A, std::size_t B>
consteval auto cat(std::array<std::byte, A> const &a, consteval auto cat(
std::array<std::byte, B> const &b) { std::array<std::byte, A> const& a, std::array<std::byte, B> const& b) {
std::array<std::byte, A + B> out; std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)), std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
std::begin(out)); std::begin(out));
@ -107,11 +107,12 @@ suite reader = [] {
* All bytes 0x00->0x79 are effectively literal uint8s. * All bytes 0x00->0x79 are effectively literal uint8s.
*/ */
{ {
constexpr auto payload = constexpr auto payload = generate_bytes(
generate_bytes([] { return make_contiguous_range(0, 0x79); }); [] { return make_contiguous_range(0, 0x79); });
msgpack::reader reader(payload); msgpack::reader reader(payload);
static_assert( static_assert(std::is_same_v<
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::uint8_t>); typename decltype(reader.read<fmt>())::value_type,
std::uint8_t>);
for (auto byte : payload) { for (auto byte : payload) {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == std::uint8_t(byte)); expect(result == std::uint8_t(byte));
@ -121,8 +122,8 @@ suite reader = [] {
expect(result == tl::make_unexpected(error::end_of_message)); expect(result == tl::make_unexpected(error::end_of_message));
} }
{ {
constexpr auto payload = constexpr auto payload = generate_bytes(
generate_bytes([] { return make_contiguous_range(0x80, 0xff); }); [] { return make_contiguous_range(0x80, 0xff); });
// Continually narrow the span over the range, as read will not // Continually narrow the span over the range, as read will not
// advance the internal span on failure. // advance the internal span on failure.
for (auto itr = payload.begin(); itr != payload.end(); ++itr) { for (auto itr = payload.begin(); itr != payload.end(); ++itr) {
@ -144,8 +145,9 @@ suite reader = [] {
return zip(std::byte{0xcc}, make_contiguous_range(0x00, 0xff)); return zip(std::byte{0xcc}, make_contiguous_range(0x00, 0xff));
}); });
msgpack::reader reader(payload); msgpack::reader reader(payload);
static_assert( static_assert(std::is_same_v<
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::uint8_t>); typename decltype(reader.read<fmt>())::value_type,
std::uint8_t>);
for (auto i = 0; i < 0x100; ++i) { for (auto i = 0; i < 0x100; ++i) {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == std::uint8_t(i)); expect(result == std::uint8_t(i));
@ -236,7 +238,8 @@ suite reader = [] {
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
msgpack::reader reader({payload.begin(), itr}); msgpack::reader reader({payload.begin(), itr});
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::incomplete_message)); expect(result
== tl::make_unexpected(error::incomplete_message));
} }
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -266,12 +269,13 @@ suite reader = [] {
} }
{ {
constexpr auto payload = constexpr auto payload = make_bytes(
make_bytes(0xcf, 0x01, 0x02, 0x03, 0x09, 0x10, 0x20, 0x30, 0x90); 0xcf, 0x01, 0x02, 0x03, 0x09, 0x10, 0x20, 0x30, 0x90);
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
msgpack::reader reader({payload.begin(), itr}); msgpack::reader reader({payload.begin(), itr});
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::incomplete_message)); expect(result
== tl::make_unexpected(error::incomplete_message));
} }
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -289,11 +293,12 @@ suite reader = [] {
* All bytes 0x00->0x79 are effectively literal uint8s. * All bytes 0x00->0x79 are effectively literal uint8s.
*/ */
{ {
constexpr auto payload = constexpr auto payload = generate_bytes(
generate_bytes([] { return make_contiguous_range(0xe0, 0xff); }); [] { return make_contiguous_range(0xe0, 0xff); });
msgpack::reader reader(payload); msgpack::reader reader(payload);
static_assert( static_assert(std::is_same_v<
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::int8_t>); typename decltype(reader.read<fmt>())::value_type,
std::int8_t>);
for (auto byte : payload) { for (auto byte : payload) {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == std::int8_t(byte)); expect(result == std::int8_t(byte));
@ -303,8 +308,8 @@ suite reader = [] {
expect(result == tl::make_unexpected(error::end_of_message)); expect(result == tl::make_unexpected(error::end_of_message));
} }
{ {
constexpr auto payload = constexpr auto payload = generate_bytes(
generate_bytes([] { return make_contiguous_range(0x00, 0xdf); }); [] { return make_contiguous_range(0x00, 0xdf); });
// Continually narrow the span over the range, as read will not // Continually narrow the span over the range, as read will not
// advance the internal span on failure. // advance the internal span on failure.
for (auto itr = payload.begin(); itr != payload.end(); ++itr) { for (auto itr = payload.begin(); itr != payload.end(); ++itr) {
@ -326,8 +331,9 @@ suite reader = [] {
return zip(std::byte{0xd0}, make_contiguous_range(0x00, 0xff)); return zip(std::byte{0xd0}, make_contiguous_range(0x00, 0xff));
}); });
msgpack::reader reader(payload); msgpack::reader reader(payload);
static_assert( static_assert(std::is_same_v<
std::is_same_v<typename decltype(reader.read<fmt>())::value_type, std::int8_t>); typename decltype(reader.read<fmt>())::value_type,
std::int8_t>);
for (auto i = 0; i < 0x100; ++i) { for (auto i = 0; i < 0x100; ++i) {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(*result == std::int8_t(i)); expect(*result == std::int8_t(i));
@ -418,7 +424,8 @@ suite reader = [] {
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
msgpack::reader reader({payload.begin(), itr}); msgpack::reader reader({payload.begin(), itr});
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::incomplete_message)); expect(result
== tl::make_unexpected(error::incomplete_message));
} }
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -448,12 +455,13 @@ suite reader = [] {
} }
{ {
constexpr auto payload = constexpr auto payload = make_bytes(
make_bytes(0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0); 0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0);
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
msgpack::reader reader({payload.begin(), itr}); msgpack::reader reader({payload.begin(), itr});
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::incomplete_message)); expect(result
== tl::make_unexpected(error::incomplete_message));
} }
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -469,8 +477,7 @@ suite reader = [] {
using fmt = msgpack::format::fixstr; using fmt = msgpack::format::fixstr;
using error = msgpack::error; using error = msgpack::error;
constexpr std::string_view sv = "hello"; constexpr std::string_view sv = "hello";
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xa0 + sv.size()),
cat(make_bytes(0xa0 + sv.size()),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
@ -488,8 +495,7 @@ suite reader = [] {
using fmt = msgpack::format::str8; using fmt = msgpack::format::str8;
using error = msgpack::error; using error = msgpack::error;
constexpr std::string_view sv = "hello d"; constexpr std::string_view sv = "hello d";
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xd9, sv.size()),
cat(make_bytes(0xd9, sv.size()),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
@ -507,8 +513,7 @@ suite reader = [] {
using fmt = msgpack::format::str16; using fmt = msgpack::format::str16;
using error = msgpack::error; using error = msgpack::error;
constexpr std::string_view sv = "hello world"; constexpr std::string_view sv = "hello world";
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xda, 0x00, sv.size()),
cat(make_bytes(0xda, 0x00, sv.size()),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
@ -560,8 +565,8 @@ suite reader = [] {
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result.has_value()); expect(result.has_value());
expect( expect(std::equal(
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end())); (*result).begin(), (*result).end(), bv.begin(), bv.end()));
}; };
"reader::read<fmt::bin16>"_test = [] { "reader::read<fmt::bin16>"_test = [] {
@ -585,8 +590,8 @@ suite reader = [] {
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect( expect(std::equal(
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end())); (*result).begin(), (*result).end(), bv.begin(), bv.end()));
}; };
// TODO: Support this with a proper test payload // TODO: Support this with a proper test payload
@ -601,8 +606,8 @@ suite reader = [] {
return rg2; return rg2;
}); });
constexpr auto payload = constexpr auto payload =
cat(make_bytes(0xc6, bv.size() >> 24, bv.size() >> 16, bv.size() >> 8, cat(make_bytes(0xc6, bv.size() >> 24, bv.size() >> 16,
bv.size() & 0xff), bv.size() >> 8, bv.size() & 0xff),
bv); bv);
for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) { for (auto itr = payload.begin() + 1; itr != payload.end(); ++itr) {
@ -613,8 +618,8 @@ suite reader = [] {
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect( expect(std::equal(
std::equal((*result).begin(), (*result).end(), bv.begin(), bv.end())); (*result).begin(), (*result).end(), bv.begin(), bv.end()));
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -666,9 +671,8 @@ suite reader = [] {
using fmt = msgpack::format::array16; using fmt = msgpack::format::array16;
using error = msgpack::error; using error = msgpack::error;
// A MessagePack array of 5 8-bit unsigned integers. // A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = constexpr auto payload = make_bytes(0xdc, 0x00, 0x05, 0xcc, 0x85, 0xcc,
make_bytes(0xdc, 0x00, 0x05, 0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
0x82, 0xcc, 0x81);
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -687,9 +691,8 @@ suite reader = [] {
using fmt = msgpack::format::array32; using fmt = msgpack::format::array32;
using error = msgpack::error; using error = msgpack::error;
// A MessagePack array of 5 8-bit unsigned integers. // A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = constexpr auto payload = make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, 0xcc,
make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, 0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
0x83, 0xcc, 0x82, 0xcc, 0x81);
msgpack::reader reader(payload); msgpack::reader reader(payload);
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
@ -704,4 +707,3 @@ suite reader = [] {
expect(end == tl::make_unexpected(error::end_of_message)); expect(end == tl::make_unexpected(error::end_of_message));
}; };
}; };

View File

@ -1,15 +1,15 @@
#include "parselink/msgpack/token.h" #include "parselink/msgpack/token.h"
// #include "parselink/proto/message.h" // #include "parselink/proto/message.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <magic_enum.hpp>
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <fcntl.h> #include <fcntl.h>
#include <magic_enum.hpp>
#include <vector> #include <vector>
#include <fmt/format.h>
#include <fmt/ranges.h>
using namespace std::chrono_literals; using namespace std::chrono_literals;
struct any { struct any {
@ -37,7 +37,6 @@ auto read_file(char const* filename) {
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
std::array<msgpack::token, 20> buf; std::array<msgpack::token, 20> buf;
auto data = read_file(argc < 2 ? "output.bin" : argv[1]); auto data = read_file(argc < 2 ? "output.bin" : argv[1]);
@ -50,10 +49,12 @@ int main(int argc, char** argv) {
test.map([&](auto sp) { test.map([&](auto sp) {
fmt::print("Read {} tokens\n", sp.size()); fmt::print("Read {} tokens\n", sp.size());
for (auto token : sp) { for (auto token : sp) {
fmt::print("token type: {} value: ", magic_enum::enum_name(token.type())); fmt::print("token type: {} value: ",
magic_enum::enum_name(token.type()));
switch (token.type()) { switch (token.type()) {
case msgpack::format::type::unsigned_int: case msgpack::format::type::unsigned_int:
fmt::print("{}", *(token.template get<std::uint64_t>())); fmt::print(
"{}", *(token.template get<std::uint64_t>()));
break; break;
case msgpack::format::type::signed_int: case msgpack::format::type::signed_int:
fmt::print("{}", *(token.template get<std::int64_t>())); fmt::print("{}", *(token.template get<std::int64_t>()));
@ -62,20 +63,24 @@ int main(int argc, char** argv) {
fmt::print("{}", *(token.template get<bool>())); fmt::print("{}", *(token.template get<bool>()));
break; break;
case msgpack::format::type::string: case msgpack::format::type::string:
fmt::print("{}", *(token.template get<std::string_view>())); fmt::print("{}",
*(token.template get<std::string_view>()));
break; break;
case msgpack::format::type::binary: case msgpack::format::type::binary:
fmt::print("{}", *(token.template get<std::span<std::byte const>>())); fmt::print(
"{}", *(token.template get<
std::span<std::byte const>>()));
break; break;
case msgpack::format::type::map: case msgpack::format::type::map:
fmt::print("map ({} entries)", token.template get<msgpack::map_desc>()->count); fmt::print("map ({} entries)",
token.template get<msgpack::map_desc>()->count);
break; break;
case msgpack::format::type::array: case msgpack::format::type::array:
fmt::print("array ({} entries)", token.template get<msgpack::array_desc>()->count); fmt::print("array ({} entries)",
break; token.template get<msgpack::array_desc>()
case msgpack::format::type::nil: ->count);
fmt::print("(nil)");
break; break;
case msgpack::format::type::nil: fmt::print("(nil)"); break;
case msgpack::format::type::invalid: case msgpack::format::type::invalid:
fmt::print("(invalid)"); fmt::print("(invalid)");
break; break;

View File

@ -1,5 +1,4 @@
#include "parselink/msgpack/token.h" #include "parselink/msgpack/token.h"
#include <boost/ut.hpp> #include <boost/ut.hpp>
using namespace boost::ut; using namespace boost::ut;
@ -30,11 +29,9 @@ namespace {
} }
return os; return os;
} }
} } // namespace
enum class foo : std::uint8_t { enum class foo : std::uint8_t { a, b, c, d, e };
a, b, c, d, e
};
suite size_and_enum = [] { suite size_and_enum = [] {
"expected use case"_test = [] { "expected use case"_test = [] {
@ -118,11 +115,12 @@ suite assignment_and_access = [] {
std::vector<std::byte> extracted_val; std::vector<std::byte> extracted_val;
{ {
auto val = make_bytes(0x32, 0xff, 0xaa, 0xce); auto val = make_bytes(0x32, 0xff, 0xaa, 0xce);
msgpack::token obj(val); expect(obj.type() == msgpack::format::type::binary); msgpack::token obj(val);
expect(obj.type() == msgpack::format::type::binary);
auto retrieved = obj.get<std::span<std::byte const>>(); auto retrieved = obj.get<std::span<std::byte const>>();
expect(bool(retrieved)); expect(bool(retrieved));
expect(std::equal(retrieved->begin(), retrieved->end(), expect(std::equal(retrieved->begin(), retrieved->end(), val.begin(),
val.begin(), val.end())); val.end()));
expect(wrong_types<bool, int, unsigned, std::string_view>(obj)); expect(wrong_types<bool, int, unsigned, std::string_view>(obj));
auto bytes_retrieved = obj.get<std::vector<std::byte>>(); auto bytes_retrieved = obj.get<std::vector<std::byte>>();
expect(bool(bytes_retrieved)); expect(bool(bytes_retrieved));

View File

@ -1,8 +1,8 @@
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <msgpack/token.h> #include <msgpack/token.h>
#include <boost/ut.hpp>
#include <fmt/format.h> #include <fmt/format.h>
#include <magic_enum.hpp>
using namespace boost::ut; using namespace boost::ut;
@ -22,12 +22,12 @@ constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...}; return {std::byte(std::forward<Bytes>(bytes))...};
} }
template <typename T, std::size_t C = 1024 * 1024> struct oversized_array { template <typename T, std::size_t C = 1024 * 1024>
struct oversized_array {
std::array<T, C> data; std::array<T, C> data;
std::size_t size; std::size_t size;
}; };
constexpr auto to_bytes_array_oversized(auto const& container) { constexpr auto to_bytes_array_oversized(auto const& container) {
using value_type = std::decay_t<decltype(container[0])>; using value_type = std::decay_t<decltype(container[0])>;
oversized_array<value_type> arr; oversized_array<value_type> arr;
@ -105,9 +105,9 @@ bool test_incomplete_message(auto const& payload) {
msgpack::token_reader reader(std::span(payload.data(), i)); msgpack::token_reader reader(std::span(payload.data(), i));
auto token = reader.read_one(); auto token = reader.read_one();
if (token != tl::make_unexpected(msgpack::error::incomplete_message)) { if (token != tl::make_unexpected(msgpack::error::incomplete_message)) {
fmt::print("Got the wrong response reading subview[0,{}] of payload: {}\n", fmt::print("Got the wrong response reading subview[0,{}] of "
i, "payload: {}\n",
token->get<std::string_view>().value()); i, token->get<std::string_view>().value());
return false; return false;
} }
} }
@ -115,11 +115,11 @@ bool test_incomplete_message(auto const& payload) {
} }
bool test_end_of_message(auto& reader) { bool test_end_of_message(auto& reader) {
return reader.read_one() == return reader.read_one()
tl::make_unexpected(msgpack::error::end_of_message); == tl::make_unexpected(msgpack::error::end_of_message);
} }
} } // namespace
suite reader_tests = [] { suite reader_tests = [] {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -173,8 +173,8 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int); expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() == expect(token->get<std::uint8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == 0x0102); expect(token->get<std::uint16_t>() == 0x0102);
expect(token->get<std::uint32_t>() == 0x0102); expect(token->get<std::uint32_t>() == 0x0102);
expect(token->get<std::uint64_t>() == 0x0102); expect(token->get<std::uint64_t>() == 0x0102);
@ -193,10 +193,10 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int); expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() == expect(token->get<std::uint8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == expect(token->get<std::uint16_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() == 0x01020304); expect(token->get<std::uint32_t>() == 0x01020304);
expect(token->get<std::uint64_t>() == 0x01020304); expect(token->get<std::uint64_t>() == 0x01020304);
expect(token == 0x01020304u); expect(token == 0x01020304u);
@ -204,8 +204,8 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::uint64"_test = [] { "read format::uint64"_test = [] {
constexpr auto payload = make_bytes(0xcf, constexpr auto payload = make_bytes(
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); 0xcf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
// Test incomplete message. // Test incomplete message.
expect(test_incomplete_message(payload)); expect(test_incomplete_message(payload));
@ -214,12 +214,12 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int); expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() == expect(token->get<std::uint8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == expect(token->get<std::uint16_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() == expect(token->get<std::uint32_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint64_t>() == 0x0102030405060708); expect(token->get<std::uint64_t>() == 0x0102030405060708);
expect(token == 0x0102030405060708u); expect(token == 0x0102030405060708u);
// EOM // EOM
@ -277,8 +277,8 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int); expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() == expect(token->get<std::int8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() == -256); expect(token->get<std::int16_t>() == -256);
expect(token->get<std::int32_t>() == -256); expect(token->get<std::int32_t>() == -256);
expect(token->get<std::int64_t>() == -256); expect(token->get<std::int64_t>() == -256);
@ -297,10 +297,10 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int); expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() == expect(token->get<std::int8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() == expect(token->get<std::int16_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() == -65536); expect(token->get<std::int32_t>() == -65536);
expect(token->get<std::int64_t>() == -65536); expect(token->get<std::int64_t>() == -65536);
expect(token == -65536); expect(token == -65536);
@ -308,8 +308,8 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::int64"_test = [] { "read format::int64"_test = [] {
constexpr auto payload = make_bytes(0xd3, constexpr auto payload = make_bytes(
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00); 0xd3, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00);
// Test incomplete message. // Test incomplete message.
expect(test_incomplete_message(payload)); expect(test_incomplete_message(payload));
@ -318,12 +318,12 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int); expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() == expect(token->get<std::int8_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() == expect(token->get<std::int16_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() == expect(token->get<std::int32_t>()
tl::make_unexpected(msgpack::error::will_truncate)); == tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int64_t>() == -4294967296); expect(token->get<std::int64_t>() == -4294967296);
expect(token == -4294967296); expect(token == -4294967296);
// EOM // EOM
@ -335,8 +335,7 @@ suite reader_tests = [] {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
"read format::fixstr"_test = [] { "read format::fixstr"_test = [] {
constexpr std::string_view sv = "hello"; constexpr std::string_view sv = "hello";
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xa5),
cat(make_bytes(0xa5),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message. // Test incomplete message.
@ -355,8 +354,7 @@ suite reader_tests = [] {
}; };
"read format::str8"_test = [] { "read format::str8"_test = [] {
constexpr std::string_view sv = "hello world"; constexpr std::string_view sv = "hello world";
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xd9, sv.size()),
cat(make_bytes(0xd9, sv.size()),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message. // Test incomplete message.
@ -374,14 +372,12 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::str16"_test = [] { "read format::str16"_test = [] {
static constexpr auto sa = build_string([]{ static constexpr auto sa =
return repeat("0123456789abcdef", 17); build_string([] { return repeat("0123456789abcdef", 17); });
});
constexpr auto sv = std::string_view(sa.data(), sa.size()); constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint16_t(sv.size())); constexpr auto sv_size = host_to_be(std::uint16_t(sv.size()));
auto payload = auto payload = cat(make_bytes(0xda), as_bytes(sv_size),
cat(make_bytes(0xda), as_bytes(sv_size),
generate_bytes([] { return from_string_view(sv); })); generate_bytes([] { return from_string_view(sv); }));
// Test incomplete message. // Test incomplete message.
@ -399,14 +395,12 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::str32"_test = [] { "read format::str32"_test = [] {
static constexpr auto sa = build_string([]{ static constexpr auto sa =
return repeat("0123456789abcdef", 4097); build_string([] { return repeat("0123456789abcdef", 4097); });
});
constexpr auto sv = std::string_view(sa.data(), sa.size()); constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint32_t(sv.size())); constexpr auto sv_size = host_to_be(std::uint32_t(sv.size()));
constexpr auto payload = constexpr auto payload = cat(make_bytes(0xdb), as_bytes(sv_size),
cat(make_bytes(0xdb), as_bytes(sv_size),
generate_bytes([sv] { return from_string_view(sv); })); generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message. // Test incomplete message.
@ -428,10 +422,8 @@ suite reader_tests = [] {
// Binary format tests // Binary format tests
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
"read format::bin8"_test = [] { "read format::bin8"_test = [] {
constexpr auto bytes = make_bytes( constexpr auto bytes = make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10);
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10
);
constexpr auto bytes_size = host_to_be(std::uint8_t(bytes.size())); constexpr auto bytes_size = host_to_be(std::uint8_t(bytes.size()));
constexpr auto payload = constexpr auto payload =
cat(make_bytes(0xc4), as_bytes(bytes_size), bytes); cat(make_bytes(0xc4), as_bytes(bytes_size), bytes);
@ -525,8 +517,8 @@ suite reader_tests = [] {
}; };
"read format::array16"_test = [] { "read format::array16"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers. // A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = make_bytes(0xdc, 0x00, 0x05, constexpr auto payload = make_bytes(0xdc, 0x00, 0x05, 0xcc, 0x85, 0xcc,
0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81); 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
@ -543,8 +535,8 @@ suite reader_tests = [] {
}; };
"read format::array32"_test = [] { "read format::array32"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers. // A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, constexpr auto payload = make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, 0xcc,
0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81); 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
@ -562,18 +554,15 @@ suite reader_tests = [] {
"read format::fixmap"_test = [] { "read format::fixmap"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers. // A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = { static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three" "one", "two", "three"};
};
constexpr auto payload = cat( constexpr auto payload = cat(make_bytes(0x83, 0xa3),
make_bytes(0x83, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }), generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3), make_bytes(0x01, 0xa3),
generate_bytes([] { return from_string_view(strings[1]); }), generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5), make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }), generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03) make_bytes(0x03));
);
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
@ -593,18 +582,15 @@ suite reader_tests = [] {
"read format::map16"_test = [] { "read format::map16"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers. // A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = { static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three" "one", "two", "three"};
};
constexpr auto payload = cat( constexpr auto payload = cat(make_bytes(0xde, 0x00, 0x03, 0xa3),
make_bytes(0xde, 0x00, 0x03, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }), generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3), make_bytes(0x01, 0xa3),
generate_bytes([] { return from_string_view(strings[1]); }), generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5), make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }), generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03) make_bytes(0x03));
);
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();
@ -626,8 +612,7 @@ suite reader_tests = [] {
"read format::map32"_test = [] { "read format::map32"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers. // A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = { static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three" "one", "two", "three"};
};
constexpr auto payload = cat( constexpr auto payload = cat(
make_bytes(0xdf, 0x00, 0x00, 0x00, 0x03, 0xa3), make_bytes(0xdf, 0x00, 0x00, 0x00, 0x03, 0xa3),
@ -636,8 +621,7 @@ suite reader_tests = [] {
generate_bytes([] { return from_string_view(strings[1]); }), generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5), make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }), generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03) make_bytes(0x03));
);
msgpack::token_reader reader(payload); msgpack::token_reader reader(payload);
auto token = reader.read_one(); auto token = reader.read_one();

View File

@ -1,11 +1,12 @@
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <msgpack/token.h> #include <msgpack/token.h>
#include <boost/ut.hpp>
#include <fmt/format.h> #include <fmt/format.h>
#include <fmt/ranges.h> #include <fmt/ranges.h>
#include <magic_enum.hpp>
using namespace boost::ut; using namespace boost::ut;
namespace { namespace {
template <typename T> template <typename T>
constexpr bool operator==(std::span<T const> a, std::span<T const> b) noexcept { constexpr bool operator==(std::span<T const> a, std::span<T const> b) noexcept {
@ -22,12 +23,12 @@ constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...}; return {std::byte(std::forward<Bytes>(bytes))...};
} }
template <typename T, std::size_t C = 1024 * 1024> struct oversized_array { template <typename T, std::size_t C = 1024 * 1024>
struct oversized_array {
std::array<T, C> data; std::array<T, C> data;
std::size_t size; std::size_t size;
}; };
constexpr auto to_bytes_array_oversized(auto const& container) { constexpr auto to_bytes_array_oversized(auto const& container) {
using value_type = std::decay_t<decltype(container[0])>; using value_type = std::decay_t<decltype(container[0])>;
oversized_array<value_type> arr; oversized_array<value_type> arr;
@ -105,9 +106,9 @@ bool test_incomplete_message(auto const& payload) {
msgpack::token_reader reader(std::span(payload.data(), i)); msgpack::token_reader reader(std::span(payload.data(), i));
auto token = reader.read_one(); auto token = reader.read_one();
if (token != tl::make_unexpected(msgpack::error::incomplete_message)) { if (token != tl::make_unexpected(msgpack::error::incomplete_message)) {
fmt::print("Got the wrong response reading subview[0,{}] of payload: {}\n", fmt::print("Got the wrong response reading subview[0,{}] of "
i, "payload: {}\n",
token->get<std::string_view>().value()); i, token->get<std::string_view>().value());
return false; return false;
} }
} }
@ -115,10 +116,10 @@ bool test_incomplete_message(auto const& payload) {
} }
bool test_end_of_message(auto& reader) { bool test_end_of_message(auto& reader) {
return reader.read_one() == return reader.read_one()
tl::make_unexpected(msgpack::error::end_of_message); == tl::make_unexpected(msgpack::error::end_of_message);
}
} }
} // namespace
template <> template <>
struct fmt::formatter<msgpack::token> { struct fmt::formatter<msgpack::token> {
@ -126,9 +127,11 @@ struct fmt::formatter<msgpack::token> {
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
template <typename FormatContext> template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const { auto format(msgpack::token const& v, FormatContext& ctx) const {
auto out = fmt::format_to(ctx.out(), "<msgpack {} = ", magic_enum::enum_name(v.type())); auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", magic_enum::enum_name(v.type()));
switch (v.type()) { switch (v.type()) {
case msgpack::format::type::unsigned_int: case msgpack::format::type::unsigned_int:
fmt::format_to(out, "{}", (*(v.get<std::uint64_t>()))); fmt::format_to(out, "{}", (*(v.get<std::uint64_t>())));
@ -143,8 +146,8 @@ struct fmt::formatter<msgpack::token> {
out = fmt::format_to(out, "{}", (*(v.get<std::string_view>()))); out = fmt::format_to(out, "{}", (*(v.get<std::string_view>())));
break; break;
case msgpack::format::type::binary: case msgpack::format::type::binary:
out = fmt::format_to(out, "{}", out = fmt::format_to(
(*(v.get<std::span<std::byte const>>()))); out, "{}", (*(v.get<std::span<std::byte const>>())));
break; break;
case msgpack::format::type::map: case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})", out = fmt::format_to(out, "(arity: {})",
@ -160,8 +163,7 @@ struct fmt::formatter<msgpack::token> {
case msgpack::format::type::invalid: case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)"); out = fmt::format_to(out, "(invalid)");
break; break;
default: default: break;
break;
} }
return fmt::format_to(out, ">"); return fmt::format_to(out, ">");
} }
@ -171,11 +173,16 @@ suite views_tests = [] {
"read format::fixmap"_test = [] { "read format::fixmap"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers. // A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr auto strings = std::to_array<std::string_view>({ static constexpr auto strings = std::to_array<std::string_view>({
"one", "two", "three", "array", "four", "map", "five", "one",
"two",
"three",
"array",
"four",
"map",
"five",
}); });
constexpr auto payload = cat( constexpr auto payload = cat(make_bytes(0x87, 0xa3),
make_bytes(0x87, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }), generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3), make_bytes(0x01, 0xa3),

View File

@ -1,98 +0,0 @@
#ifndef msgpack_test_utils_4573e6627d8efe78
#define msgpack_test_utils_4573e6627d8efe78
#include <boost/ut.hpp>
#include <fmt/format.h>
#include <magic_enum.hpp>
template <typename T>
constexpr bool operator==(std::span<T const> a, std::span<T const> b) noexcept {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
template <typename T>
constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(std::forward<T>(t));
}
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, std::size_t C = 1024 * 1024> struct oversized_array {
std::array<T, C> data;
std::size_t size;
};
constexpr auto to_bytes_array_oversized(auto const &container) {
using value_type = std::decay_t<decltype(container[0])>;
oversized_array<value_type> arr;
std::copy(std::begin(container), std::end(container), std::begin(arr.data));
arr.size = std::distance(std::begin(container), std::end(container));
return arr;
}
consteval auto generate_bytes(auto callable) {
constexpr auto oversized = to_bytes_array_oversized(callable());
using value_type = std::decay_t<decltype(oversized.data[0])>;
std::array<value_type, oversized.size> out;
std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size),
std::begin(out));
return out;
}
consteval auto build_string(auto callable) {
constexpr auto string_array = generate_bytes(callable);
return string_array;
}
template <std::size_t ... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept {
std::array<std::byte, (Sizes + ...)> out;
std::size_t index{};
((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...);
return out;
}
constexpr auto repeat(std::span<std::byte const> sv, std::size_t count) {
std::vector<std::byte> range;
range.reserve(sv.size() * count);
for (decltype(count) i = 0; i < count; ++i) {
std::copy_n(sv.begin(), sv.size(), std::back_inserter(range));
}
return range;
}
constexpr auto repeat(std::string_view sv, std::size_t count) {
std::vector<char> range;
range.reserve(sv.size() * count);
for (decltype(count) i = 0; i < count; ++i) {
std::copy_n(sv.begin(), sv.size(), std::back_inserter(range));
}
return range;
}
constexpr auto from_string_view(std::string_view sv) {
std::vector<std::byte> range;
range.resize(sv.size());
auto itr = range.begin();
for (auto c : sv) {
*itr = std::byte(c);
++itr;
}
return range;
}
template <typename T, typename E>
std::ostream &operator<<(std::ostream &os, tl::expected<T, E> const &exp) {
if (exp.has_value()) {
os << "Value: '" << *exp << "'";
} else {
os << "Error";
}
return os;
}
#endif // msgpack_test_utils_4573e6627d8efe78

View File

@ -1,9 +1,10 @@
#include "parselink/msgpack/core/writer.h" #include "parselink/msgpack/core/writer.h"
#include <boost/ut.hpp> #include <boost/ut.hpp>
#include <string> #include <string>
#include <fmt/format.h>
#include <fmt/ranges.h>
using namespace boost::ut; using namespace boost::ut;
namespace format = msgpack::format; namespace format = msgpack::format;
@ -14,7 +15,8 @@ constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...}; return {std::byte(std::forward<Bytes>(bytes))...};
} }
template <typename T, std::size_t C = 1024> struct oversized_array { template <typename T, std::size_t C = 1024>
struct oversized_array {
std::array<T, C> data; std::array<T, C> data;
std::size_t size; std::size_t size;
}; };
@ -36,8 +38,8 @@ consteval auto generate_bytes(auto callable) {
} }
template <std::size_t A, std::size_t B> template <std::size_t A, std::size_t B>
consteval auto cat(std::array<std::byte, A> const &a, consteval auto cat(
std::array<std::byte, B> const &b) { std::array<std::byte, A> const& a, std::array<std::byte, B> const& b) {
std::array<std::byte, A + B> out; std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)), std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
std::begin(out)); std::begin(out));
@ -103,16 +105,18 @@ suite writer = [] {
std::array<std::byte, 2> payload; std::array<std::byte, 2> payload;
auto constexpr expected = make_bytes(0x32, 0x55); auto constexpr expected = make_bytes(0x32, 0x55);
msgpack::writer writer(payload); msgpack::writer writer(payload);
auto result = writer.write<fmt>(std::uint8_t{0x32}); expect(!!writer.write<fmt>(std::uint8_t{0x32}));
expect(!!result);
expect(writer.tell() == 1); expect(writer.tell() == 1);
expect(*writer.subspan().begin() == std::byte{0x32}); expect(*writer.subspan().begin() == *expected.begin());
expect(writer.write<fmt>(std::uint8_t{0x82}) == tl::make_unexpected(error::bad_value)); expect(writer.write<fmt>(std::uint8_t{0x82})
== tl::make_unexpected(error::bad_value));
writer.write(std::uint8_t{0x55}); expect(!!writer.write<fmt>(std::uint8_t{0x55}));
expect(writer.tell() == 2); expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(std::uint8_t{0x82})
== tl::make_unexpected(error::out_of_space));
expect(writer.write<fmt>(std::uint8_t{0x32})
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::uint8>"_test = [] { "writer::write<format::uint8>"_test = [] {
@ -122,12 +126,14 @@ suite writer = [] {
std::array<std::byte, 4> payload; std::array<std::byte, 4> payload;
auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82); auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82);
msgpack::writer writer(payload); msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::uint8_t{0x32})); auto result = writer.write<fmt>(std::uint8_t{0x32});
expect(!!result);
expect(writer.tell() == 2); expect(writer.tell() == 2);
expect(equal(writer.subspan(), std::span{expected.begin(), 2})); expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::uint8_t{0x82})); expect(!!writer.write<fmt>(std::uint8_t{0x82}));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(std::uint8_t{0x01})
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::uint16>"_test = [] { "writer::write<format::uint16>"_test = [] {
@ -135,14 +141,16 @@ suite writer = [] {
using error = msgpack::error; using error = msgpack::error;
std::array<std::byte, 6> payload; std::array<std::byte, 6> payload;
auto constexpr expected = make_bytes(0xcd, 0x32, 0xcc, 0xcd, 0xaa, 0xff); auto constexpr expected =
make_bytes(0xcd, 0x32, 0xcc, 0xcd, 0xaa, 0xff);
msgpack::writer writer(payload); msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::uint16_t{0x32cc})); expect(!!writer.write<fmt>(std::uint16_t{0x32cc}));
expect(writer.tell() == 3); expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3})); expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(0xaaff)); expect(!!writer.write<fmt>(0xaaff));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::uint32>"_test = [] { "writer::write<format::uint32>"_test = [] {
@ -155,7 +163,8 @@ suite writer = [] {
expect(!!writer.write<fmt>(0x01020304)); expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5); expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::uint64>"_test = [] { "writer::write<format::uint64>"_test = [] {
@ -169,7 +178,8 @@ suite writer = [] {
expect(!!writer.write<fmt>(0x0102030405060708)); expect(!!writer.write<fmt>(0x0102030405060708));
expect(writer.tell() == 9); expect(writer.tell() == 9);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::negative_fixint>"_test = [] { "writer::write<format::negative_fixint>"_test = [] {
@ -186,7 +196,8 @@ suite writer = [] {
expect(!!writer.write<fmt>(-32)); expect(!!writer.write<fmt>(-32));
expect(writer.tell() == 2); expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(-5) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(-5)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::int8>"_test = [] { "writer::write<format::int8>"_test = [] {
@ -201,7 +212,8 @@ suite writer = [] {
expect(equal(writer.subspan(), std::span{expected.begin(), 2})); expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::int8_t{-5})); expect(!!writer.write<fmt>(std::int8_t{-5}));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::int16>"_test = [] { "writer::write<format::int16>"_test = [] {
@ -209,14 +221,16 @@ suite writer = [] {
using error = msgpack::error; using error = msgpack::error;
std::array<std::byte, 6> payload; std::array<std::byte, 6> payload;
auto constexpr expected = make_bytes(0xd1, 0x32, 0xcc, 0xd1, 0xff, 0xfa); auto constexpr expected =
make_bytes(0xd1, 0x32, 0xcc, 0xd1, 0xff, 0xfa);
msgpack::writer writer(payload); msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::int16_t{0x32cc})); expect(!!writer.write<fmt>(std::int16_t{0x32cc}));
expect(writer.tell() == 3); expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3})); expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(-6)); expect(!!writer.write<fmt>(-6));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::int32>"_test = [] { "writer::write<format::int32>"_test = [] {
@ -229,7 +243,8 @@ suite writer = [] {
expect(!!writer.write<fmt>(0x01020304)); expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5); expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::int64>"_test = [] { "writer::write<format::int64>"_test = [] {
@ -243,7 +258,8 @@ suite writer = [] {
expect(!!writer.write<fmt>(std::int64_t{0x0102030405060708})); expect(!!writer.write<fmt>(std::int64_t{0x0102030405060708}));
expect(writer.tell() == 9); expect(writer.tell() == 9);
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space)); expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
}; };
"writer::write<format::fixstr>"_test = [] { "writer::write<format::fixstr>"_test = [] {
@ -259,15 +275,14 @@ suite writer = [] {
"writer::write<format::str8>"_test = [] { "writer::write<format::str8>"_test = [] {
using fmt = format::str8; using fmt = format::str8;
std::array<std::byte, 46> payload; std::array<std::byte, 46> payload;
auto constexpr expected = make_bytes(0xd9, 44, auto constexpr expected = make_bytes(0xd9, 44, 't', 'h', 'e', ' ', 'q',
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ', ' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g'); 'g');
msgpack::writer writer(payload); msgpack::writer writer(payload);
std::string_view txt = std::string_view txt = "the quick brown fox jumped over the lazy dog";
"the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt))); expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
}; };
@ -275,15 +290,14 @@ suite writer = [] {
"writer::write<format::str16>"_test = [] { "writer::write<format::str16>"_test = [] {
using fmt = format::str16; using fmt = format::str16;
std::array<std::byte, 47> payload; std::array<std::byte, 47> payload;
auto constexpr expected = make_bytes(0xda, 0, 44, auto constexpr expected = make_bytes(0xda, 0, 44, 't', 'h', 'e', ' ',
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ', 'f',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ', 'o', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ', 'r', ' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g'); 'g');
msgpack::writer writer(payload); msgpack::writer writer(payload);
std::string_view txt = std::string_view txt = "the quick brown fox jumped over the lazy dog";
"the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt))); expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
}; };
@ -291,15 +305,14 @@ suite writer = [] {
"writer::write<format::str32>"_test = [] { "writer::write<format::str32>"_test = [] {
using fmt = format::str32; using fmt = format::str32;
std::array<std::byte, 49> payload; std::array<std::byte, 49> payload;
auto constexpr expected = make_bytes(0xdb, 0, 0, 0, 44, auto constexpr expected = make_bytes(0xdb, 0, 0, 0, 44, 't', 'h', 'e',
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ', ' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ', 'f', 'o', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ', 'e', 'r', ' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g'); 'o', 'g');
msgpack::writer writer(payload); msgpack::writer writer(payload);
std::string_view txt = std::string_view txt = "the quick brown fox jumped over the lazy dog";
"the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt))); expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
}; };
@ -307,8 +320,8 @@ suite writer = [] {
"writer::write<format::bin8>"_test = [] { "writer::write<format::bin8>"_test = [] {
using fmt = format::bin8; using fmt = format::bin8;
std::array<std::byte, 12> payload; std::array<std::byte, 12> payload;
auto constexpr expected = make_bytes(0xc4, 0x07, 0x01, 0x02, 0x03, 0x04, auto constexpr expected = make_bytes(
0xf8, 0xf9, 0xfa); 0xc4, 0x07, 0x01, 0x02, 0x03, 0x04, 0xf8, 0xf9, 0xfa);
msgpack::writer writer(payload); msgpack::writer writer(payload);
std::span<std::byte const> bv(expected.begin() + 2, expected.end()); std::span<std::byte const> bv(expected.begin() + 2, expected.end());
@ -319,8 +332,8 @@ suite writer = [] {
"writer::write<format::bin16>"_test = [] { "writer::write<format::bin16>"_test = [] {
using fmt = format::bin16; using fmt = format::bin16;
std::array<std::byte, 12> payload; std::array<std::byte, 12> payload;
auto constexpr expected = make_bytes(0xc5, 0x0, 0x07, 0x01, 0x02, 0x03, auto constexpr expected = make_bytes(
0x04, 0xf8, 0xf9, 0xfa); 0xc5, 0x0, 0x07, 0x01, 0x02, 0x03, 0x04, 0xf8, 0xf9, 0xfa);
msgpack::writer writer(payload); msgpack::writer writer(payload);
std::span<std::byte const> bv(expected.begin() + 3, expected.end()); std::span<std::byte const> bv(expected.begin() + 3, expected.end());
@ -422,3 +435,179 @@ suite writer = [] {
expect(equal(writer.subspan(), expected)); expect(equal(writer.subspan(), expected));
}; };
}; };
// Deduced writer tests
namespace {
template <typename T>
struct test_data {};
template <std::signed_integral T>
struct test_data<T> {
static constexpr auto values = std::to_array<std::int64_t>({
-1, // negative fixint
-32, // negative fixint
-33, // int8
-128, // int8
0, // int8
127, // int8
128, // int16
-129, // int16
-32768, // int16
32767, // int16
-32769, // int32
32768, // int32
-2147483648, // int32
2147483647, // int32
-2147483649, // int64
2147483648, // int64
std::numeric_limits<std::int64_t>::lowest(), // int64
std::numeric_limits<std::int64_t>::max(), // int64
});
static constexpr auto payload = make_bytes(0xff, // negative fixint
0xe0, // negative fixint
0xd0, 0xdf, // int8
0xd0, 0x80, // int8
0xd0, 0x0, // int8
0xd0, 0x7f, // int8
0xd1, 0x00, 0x80, // int16
0xd1, 0xff, 0x7f, // int16
0xd1, 0x80, 0x00, // int16
0xd1, 0x7f, 0xff, // int16
0xd2, 0xff, 0xff, 0x7f, 0xff, // int32
0xd2, 0x00, 0x00, 0x80, 0x00, // int32
0xd2, 0x80, 0x00, 0x00, 0x00, // int32
0xd2, 0x7f, 0xff, 0xff, 0xff, // int32
0xd3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, // int64
0xd3, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, // int64
0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // int64
0xd3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // int64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max()
&& value >= std::numeric_limits<T>::lowest();
}
};
template <std::unsigned_integral T>
struct test_data<T> {
static constexpr auto values = std::to_array<std::uint64_t>({
0x00, // positive fixint
0x79, // positive fixint
0x80, // uint8
0xff, // uint8
0x100, // uint16
0xffff, // uint16
0x10000, // uint32
0xffffffff, // uint32
0x100000000, // uint64
0xffffffffffffffff // uint64
});
static constexpr auto payload = make_bytes(0x00, // positive fixint
0x79, // positive fixint
0xcc, 0x80, // uint8
0xcc, 0xff, // uint8
0xcd, 0x01, 0x00, // uint16
0xcd, 0xff, 0xff, // uint16
0xce, 0x00, 0x01, 0x00, 0x00, // uint32
0xce, 0xff, 0xff, 0xff, 0xff, // uint32
0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // uint64
0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // uint64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max();
}
};
template <>
struct test_data<std::string_view> {
static constexpr auto values = std::to_array<std::string_view>({
"", // fixstr
"0", // fixstr
"0123456789abcdef0123456789abcde", // fixstr
"0123456789abcdef0123456789abcdef", // str8
});
static constexpr auto payload = make_bytes(0xa0, // fixstr
0xa1, 0x30, // fixstr
// fixstr
0xbf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
// str8
0xd9, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
0x66);
static constexpr auto valid(auto value) noexcept {
return value.size() <= std::numeric_limits<std::uint32_t>::max();
}
};
template <typename T>
bool test_deduced() noexcept {
constexpr auto const& expected_payload = test_data<T>::payload;
std::array<std::byte, expected_payload.size()> payload;
msgpack::writer writer(payload);
for (auto const& value : test_data<T>::values) {
if (!test_data<T>::valid(value)) break;
expect(!!writer.write2(T(value)));
auto expect = std::span(expected_payload.begin(), writer.tell());
auto correct = equal(writer.subspan(), expect);
if (!correct) {
fmt::print("Deduction failed for '{}'\n", T(value));
fmt::print("\tActual: {::#04x}\n", writer.subspan());
fmt::print("\tExpect: {::#04x}\n", expect);
return false;
}
}
return true;
}
} // anonymous namespace
suite deduced_writer = [] {
"writer::write<std::uint8_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::uint8_t>());
};
"writer::write<std::uint16_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::uint16_t>());
};
"writer::write<std::uint32_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::uint32_t>());
};
"writer::write<std::uint64_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::uint64_t>());
};
"writer::write<std::int8_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::int8_t>());
};
"writer::write<std::int16_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::int16_t>());
};
"writer::write<std::int32_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::int32_t>());
};
"writer::write<std::int64_t> (deduced + compressed)"_test = [] {
expect(test_deduced<std::int32_t>());
};
"writer::write<std::string_view> (deduced + compressed)"_test = [] {
expect(test_deduced<std::string_view>());
};
};

View File

@ -0,0 +1,9 @@
cc_test(
name = "unpacker",
size = "small",
srcs = glob([
"*.cpp",
"*.h",
]),
deps = ["//tests/msgpack:common", "@rapidcheck"],
)

View File

@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for bytes.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
using namespace boost::ut;
suite unpack_byte_types = [] {
// TODO: Make non-const version?
"unpacker::unpack<std::span<std::byte const>>"_test = [] {
constexpr auto payload =
test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05);
constexpr auto expected =
test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x05);
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const>>();
expect(std::ranges::equal(*actual, expected));
}
{
// Test extent
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const>>();
expect(std::ranges::equal(*actual2, expected));
}
};
// TODO: Make non-const version?
"unpacker::unpack<std::array<std::byte, N>>"_test = [] {
constexpr auto payload =
test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x06);
constexpr auto expected =
test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x06);
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::array<std::byte, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::array<std::byte, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual2, expected));
}
};
};

View File

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for signed integer values
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
struct get_marker;
template <>
struct get_marker<std::int8_t> {
static constexpr std::byte marker = msgpack::format::int8::marker;
};
template <>
struct get_marker<std::int16_t> {
static constexpr std::byte marker = msgpack::format::int16::marker;
};
template <>
struct get_marker<std::int32_t> {
static constexpr std::byte marker = msgpack::format::int32::marker;
};
template <>
struct get_marker<std::int64_t> {
static constexpr std::byte marker = msgpack::format::int64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::min() > value) {
return unpacked_value
== tl::make_unexpected(msgpack::error::will_truncate);
} else {
return unpacked_value.has_value() && unpacked_value == value;
}
});
if (!result) {
fmt::print("Failed on {}\n",
std::source_location::current().function_name());
}
return result;
}
} // anonymous namespace
suite unpack_signed_types = [] {
"unpacker::unpack<std::int8_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int8_t>());
expect(check_unpack_integer<std::int16_t, std::int8_t>());
expect(check_unpack_integer<std::int32_t, std::int8_t>());
expect(check_unpack_integer<std::int64_t, std::int8_t>());
};
"unpacker::unpack<std::int16_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int16_t>());
expect(check_unpack_integer<std::int16_t, std::int16_t>());
expect(check_unpack_integer<std::int32_t, std::int16_t>());
expect(check_unpack_integer<std::int64_t, std::int16_t>());
};
"unpacker::unpack<std::int32_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int32_t>());
expect(check_unpack_integer<std::int16_t, std::int32_t>());
expect(check_unpack_integer<std::int32_t, std::int32_t>());
expect(check_unpack_integer<std::int64_t, std::int32_t>());
};
"unpacker::unpack<std::int64_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int64_t, std::int64_t>());
};
};

View File

@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <fmt/format.h>
using namespace boost::ut;
namespace {} // anonymous namespace
suite unpacker_simple_types = [] {
"unpacker::unpack<invalid>"_test = [] {
tl::expected<int, msgpack::error> x(
tl::make_unexpected(msgpack::error::end_of_message));
static constexpr auto payload = test::make_bytes(0xc1, 0xc1);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<msgpack::invalid>(unpacker));
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<nil>"_test = [] {
static constexpr auto payload = test::make_bytes(0xc0, 0xc0);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<msgpack::nil>(unpacker));
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<bool>"_test = [] {
constexpr auto payload = test::make_bytes(0xc3, 0xc2);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<bool>(unpacker));
auto value = unpacker.unpack<bool>();
expect(value && *value == true);
value = unpacker.unpack<bool>();
expect(value && *value == false);
expect(unpacker.unpack<bool>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -0,0 +1,31 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests for strings.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
using namespace boost::ut;
suite unpack_string_types = [] {
"unpacker::unpack<std::string_view>"_test = [] {
static constexpr auto payload = test::make_bytes(0xa2, 'H', 'i');
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<std::string_view>(unpacker));
expect(unpacker.unpack<std::string_view>() == "Hi");
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Unpacker tests, common code.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef tests_msgpack_unpacker_25b118dd14e2b1b4
#define tests_msgpack_unpacker_25b118dd14e2b1b4
#include "parselink/msgpack/core/unpacker.h"
#include "test_utils.h"
// Helper template to check that a type is either equal to itself, or verify
// that it won't unpack correctly.
template <typename T, typename U>
constexpr bool unpack_type_check(msgpack::unpacker unpacker) noexcept {
auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type);
auto compatible_type = std::same_as<T, U>
|| (std::is_unsigned_v<T> && std::is_unsigned_v<U>)
|| (std::is_signed_v<T> && std::is_signed_v<U>);
auto result = compatible_type || unpacker.unpack<U>() == wrong_type;
if (!result) {
fmt::print("Unpack_type_check failed: {}\n",
std::source_location::current().function_name());
}
return result;
}
// Validate the wrong types do not unpack correctly, or fail.
template <typename Expected, typename... OtherTypes>
constexpr bool try_unpacking_types(msgpack::unpacker unpacker) noexcept {
return ((unpack_type_check<Expected, OtherTypes>(unpacker)) && ...);
}
#define SUPPORTED_TYPES \
msgpack::nil, msgpack::invalid, bool, std::uint8_t, std::uint16_t, \
std::uint32_t, std::uint64_t, std::int8_t, std::int16_t, \
std::int32_t, std::int64_t, std::string_view
template <typename Expected>
constexpr bool type_check_unpacker(msgpack::unpacker unpacker) noexcept {
return try_unpacking_types<Expected, SUPPORTED_TYPES>(unpacker);
}
#endif // tests_msgpack_unpacker_25b118dd14e2b1b4

View File

@ -0,0 +1,131 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <rapidcheck.h>
using namespace boost::ut;
namespace {
constexpr auto equal(auto a, auto b) {
return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, typename U>
constexpr bool test_unpack(msgpack::unpacker unpacker) noexcept {
auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type);
return std::same_as<T, U> || unpacker.unpack<U>() == wrong_type;
}
template <typename Expected, typename... OtherTypes>
constexpr bool test_types(msgpack::unpacker unpacker) noexcept {
return ((test_unpack<Expected, OtherTypes>(unpacker)) && ...);
}
template <typename Expected>
constexpr bool expect_invalid(msgpack::unpacker unpacker) noexcept {
return test_types<Expected, msgpack::nil, msgpack::invalid, bool,
std::uint8_t, std::string_view>(unpacker);
}
template <typename T>
struct get_marker;
template <>
struct get_marker<std::uint8_t> {
static constexpr std::byte marker = msgpack::format::uint8::marker;
};
template <>
struct get_marker<std::uint16_t> {
static constexpr std::byte marker = msgpack::format::uint16::marker;
};
template <>
struct get_marker<std::uint32_t> {
static constexpr std::byte marker = msgpack::format::uint32::marker;
};
template <>
struct get_marker<std::uint64_t> {
static constexpr std::byte marker = msgpack::format::uint64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::min() > value) {
return unpacked_value
== tl::make_unexpected(msgpack::error::will_truncate);
} else {
return unpacked_value.has_value() && unpacked_value == value;
}
});
if (!result) {
fmt::print("Failed on {}\n",
std::source_location::current().function_name());
}
return result;
}
} // anonymous namespace
suite unpack_unsigned_types = [] {
"unpacker::unpack<std::uint8_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint8_t>());
expect(check_unpack_integer<std::uint16_t, std::uint8_t>());
expect(check_unpack_integer<std::uint32_t, std::uint8_t>());
expect(check_unpack_integer<std::uint64_t, std::uint8_t>());
};
"unpacker::unpack<std::uint16_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint16_t>());
expect(check_unpack_integer<std::uint16_t, std::uint16_t>());
expect(check_unpack_integer<std::uint32_t, std::uint16_t>());
expect(check_unpack_integer<std::uint64_t, std::uint16_t>());
};
"unpacker::unpack<std::uint32_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint32_t>());
expect(check_unpack_integer<std::uint16_t, std::uint32_t>());
expect(check_unpack_integer<std::uint32_t, std::uint32_t>());
expect(check_unpack_integer<std::uint64_t, std::uint32_t>());
};
"unpacker::unpack<std::uint64_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint64_t>());
expect(check_unpack_integer<std::uint16_t, std::uint64_t>());
expect(check_unpack_integer<std::uint32_t, std::uint64_t>());
expect(check_unpack_integer<std::uint64_t, std::uint64_t>());
};
};

22
tests/proto/BUILD Normal file
View File

@ -0,0 +1,22 @@
cc_library(
name = "test_deps",
srcs = [
"test_main.cpp",
],
deps = [
"//source/proto",
"@expected",
"@fmt",
"@magic_enum",
"@ut",
],
)
cc_test(
name = "session_id",
srcs = [
"test_session_id.cpp",
],
deps = ["test_deps"],
)

View File

@ -0,0 +1,2 @@
int main(int, char**) {}

View File

@ -0,0 +1,60 @@
#include <boost/ut.hpp>
#include <parselink/proto/session_id.h>
#include <set>
#include <fmt/format.h>
namespace {
using namespace boost::ut;
using namespace parselink::proto;
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
constexpr std::array<std::byte, 32> null_id_bytes = {};
constexpr std::size_t expected_null_hash = 0xd98558;
} // namespace
suite session_id_tests = [] {
"test explicitly generated session ids"_test = [] {
constexpr session_id sid1(null_id_bytes);
constexpr session_id sid2(null_id_bytes);
expect(sid1 == sid2);
expect(sid1 == null_id_bytes);
constexpr auto basic_id = make_bytes(0x0, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f);
constexpr session_id sid3(basic_id);
constexpr session_id sid4(basic_id);
expect(sid3 == sid4);
expect(sid3 == basic_id);
};
"test hashing"_test = [] {
session_id sid(null_id_bytes);
expect(std::hash<decltype(sid)>{}(sid) == expected_null_hash);
};
"test random generated"_test = [] {
session_id sid1;
session_id sid2;
expect(sid1 != null_id_bytes);
expect(sid2 != null_id_bytes);
expect(sid1 != sid2);
};
"test random generated entropy"_test = [] {
constexpr static std::size_t iter_count = 100000;
std::set<session_id> sessions;
while (sessions.size() < iter_count) {
session_id s;
expect(!sessions.contains(s));
sessions.insert(std::move(s));
}
};
};