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
85 changed files with 8560 additions and 2862 deletions

47
.clang-format Normal file
View File

@ -0,0 +1,47 @@
BasedOnStyle: LLVM
---
Language: Cpp
AlignAfterOpenBracket: DontAlign
AlignOperands: AlignAfterOperator
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AccessModifierOffset: -4
AlwaysBreakTemplateDeclarations: Yes
BraceWrapping:
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
#BracedInitializerIndentWidth: 4
BreakBeforeBinaryOperators: NonAssignment
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
#IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"parselink/'
Priority: 1
SortPriority: 1
CaseSensitive: false
- Regex: '^<(fmt|tl|magic_enum|ut)/'
Priority: 2
SortPriority: 2
CaseSensitive: false
- Regex: '.*'
Priority: 3
SortPriority: 3
IncludeIsMainRegex: '(_test)?$'
IndentAccessModifiers: false
IndentCaseLabels: true
IndentGotoLabels: false
IndentWidth: 4
InsertNewlineAtEOF: true
#KeepEmptyLinesAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Left
QualifierAlignment: Right
#QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'volatile']
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>
@ -14,69 +14,57 @@
#include <thread> #include <thread>
#include <vector> #include <vector>
namespace beast = boost::beast; // from <boost/beast.hpp> namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp> namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp> 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";
if(iequals(ext, ".html")) return "text/html"; if (iequals(ext, ".html")) return "text/html";
if(iequals(ext, ".php")) return "text/html"; if (iequals(ext, ".php")) return "text/html";
if(iequals(ext, ".css")) return "text/css"; if (iequals(ext, ".css")) return "text/css";
if(iequals(ext, ".txt")) return "text/plain"; if (iequals(ext, ".txt")) return "text/plain";
if(iequals(ext, ".js")) return "application/javascript"; if (iequals(ext, ".js")) return "application/javascript";
if(iequals(ext, ".json")) return "application/json"; if (iequals(ext, ".json")) return "application/json";
if(iequals(ext, ".xml")) return "application/xml"; if (iequals(ext, ".xml")) return "application/xml";
if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; if (iequals(ext, ".swf")) return "application/x-shockwave-flash";
if(iequals(ext, ".flv")) return "video/x-flv"; if (iequals(ext, ".flv")) return "video/x-flv";
if(iequals(ext, ".png")) return "image/png"; if (iequals(ext, ".png")) return "image/png";
if(iequals(ext, ".jpe")) return "image/jpeg"; if (iequals(ext, ".jpe")) return "image/jpeg";
if(iequals(ext, ".jpeg")) return "image/jpeg"; if (iequals(ext, ".jpeg")) return "image/jpeg";
if(iequals(ext, ".jpg")) return "image/jpeg"; if (iequals(ext, ".jpg")) return "image/jpeg";
if(iequals(ext, ".gif")) return "image/gif"; if (iequals(ext, ".gif")) return "image/gif";
if(iequals(ext, ".bmp")) return "image/bmp"; if (iequals(ext, ".bmp")) return "image/bmp";
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; if (iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
if(iequals(ext, ".tiff")) return "image/tiff"; if (iequals(ext, ".tiff")) return "image/tiff";
if(iequals(ext, ".tif")) return "image/tiff"; if (iequals(ext, ".tif")) return "image/tiff";
if(iequals(ext, ".svg")) return "image/svg+xml"; if (iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml"; if (iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text"; return "application/text";
} }
// 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;
@ -153,19 +133,17 @@ handle_request(
body.open(path.c_str(), beast::file_mode::scan, ec); body.open(path.c_str(), beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist // Handle the case where the file doesn't exist
if(ec == beast::errc::no_such_file_or_directory) if (ec == beast::errc::no_such_file_or_directory)
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,10 +153,9 @@ 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);
res.set(http::field::content_type, mime_type(path)); res.set(http::field::content_type, mime_type(path));
res.content_length(size); res.content_length(size);
@ -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_ = {};
@ -239,56 +205,38 @@ 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_, beast::bind_front_handler(
std::move(msg), &session::on_write, shared_from_this(), keep_alive));
beast::bind_front_handler(
&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, std::shared_ptr<std::string const> const& doc_root)
tcp::endpoint endpoint, : ioc_(ioc)
std::shared_ptr<std::string const> const& doc_root) , acceptor_(net::make_strand(ioc))
: ioc_(ioc) , doc_root_(doc_root) {
, acceptor_(net::make_strand(ioc))
, 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, shared_from_this()));
&listener::on_accept,
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,11 +15,19 @@ 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"]),
includes = ["."], includes = ["."],
deps = ["//include:path"], deps = ["@expected", "//include:path"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

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,27 +65,28 @@ 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)
: logger(name, {std::forward<Endpoints>(eps)...}) {} : logger(name, {std::forward<Endpoints>(eps)...}) {}
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>
requires (Level > static_threshold) requires(Level > static_threshold)
[[gnu::flatten]] void log(fmt::format_string<Args...>, Args&&...) const {} [[gnu::flatten]] void log(fmt::format_string<Args...>, Args&&...) const {}
#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,8 +102,7 @@ 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 {
auto buff = endpoint->buffer(); auto buff = endpoint->buffer();
@ -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,18 +20,17 @@
#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>
: 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::formatter<std::string_view>::format(v.v, ctx); return fmt::formatter<std::string_view>::format(v.v, ctx);
} }
@ -45,14 +44,13 @@ struct fmt::formatter<parselink::logging::error_str>
// //
// Logging `color` will yield "Color::Green" // Logging `color` will yield "Color::Green"
template <typename E> template <typename E>
requires std::is_enum_v<E> requires std::is_enum_v<E>
struct fmt::formatter<E> : fmt::formatter<std::string_view> { 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);
} }
@ -66,11 +64,12 @@ struct fmt::formatter<E> : fmt::formatter<std::string_view> {
// //
// Logging `enum_name_only{color}` will yield "Green" // Logging `enum_name_only{color}` will yield "Green"
template <typename E> template <typename E>
requires std::is_enum_v<E> 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(
magic_enum::enum_name(v.v), ctx); magic_enum::enum_name(v.v), ctx);
@ -79,7 +78,7 @@ struct fmt::formatter<parselink::logging::enum_name_only<E>>
template <> template <>
struct fmt::formatter<std::error_code> : fmt::formatter<std::string_view> { struct fmt::formatter<std::error_code> : fmt::formatter<std::string_view> {
template<typename FormatContext> template <typename FormatContext>
auto format(auto const& v, FormatContext& ctx) const { auto format(auto const& v, FormatContext& ctx) const {
return fmt::formatter<std::string_view>::format(v.message(), ctx); return fmt::formatter<std::string_view>::format(v.message(), ctx);
} }
@ -89,7 +88,7 @@ struct fmt::formatter<std::error_code> : fmt::formatter<std::string_view> {
// string. // string.
template <> template <>
struct fmt::formatter<std::errc> : fmt::formatter<std::error_code> { struct fmt::formatter<std::errc> : fmt::formatter<std::error_code> {
template<typename FormatContext> template <typename FormatContext>
auto format(std::errc const& v, FormatContext& ctx) const { auto format(std::errc const& v, FormatContext& ctx) const {
return fmt::formatter<std::error_code>::format( return fmt::formatter<std::error_code>::format(
std::make_error_code(v), ctx); std::make_error_code(v), ctx);
@ -104,7 +103,7 @@ struct fmt::formatter<std::byte> {
return ctx.begin(); return ctx.begin();
} }
template<typename FormatContext> template <typename FormatContext>
auto format(std::byte const v, FormatContext& ctx) const { auto format(std::byte const v, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "0x{:0x}", std::uint8_t(v)); return fmt::format_to(ctx.out(), "0x{:0x}", std::uint8_t(v));
} }
@ -113,13 +112,12 @@ struct fmt::formatter<std::byte> {
// Support printing raw/smart pointers without needing to wrap them in fmt::ptr // Support printing raw/smart pointers without needing to wrap them in fmt::ptr
template <parselink::logging::detail::printable_pointer T> template <parselink::logging::detail::printable_pointer T>
struct fmt::formatter<T> : fmt::formatter<void const*> { struct fmt::formatter<T> : fmt::formatter<void const*> {
template<typename FormatContext> template <typename FormatContext>
auto format(T const& v, FormatContext& ctx) const { auto format(T const& v, FormatContext& ctx) const {
return fmt::formatter<void const*>::format(fmt::ptr(v), ctx); return fmt::formatter<void const*>::format(fmt::ptr(v), ctx);
} }
}; };
// 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>
@ -127,7 +125,7 @@ struct fmt::formatter<tl::expected<T, Err>> {
return ctx.begin(); return ctx.begin();
} }
template<typename FormatContext> template <typename FormatContext>
auto format(tl::expected<T, Err> const& v, FormatContext& ctx) const { auto format(tl::expected<T, Err> const& v, FormatContext& ctx) const {
if (v) { if (v) {
return fmt::format_to(ctx.out(), "{}", return fmt::format_to(ctx.out(), "{}",
@ -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

@ -25,14 +25,14 @@ namespace parselink {
namespace logging { namespace logging {
enum class level { enum class level {
silent, // "Virtual" level used to suppress all logging output. silent, // "Virtual" level used to suppress all logging output.
critical, // Indicates a fatal error occurred. Crash likely. critical, // Indicates a fatal error occurred. Crash likely.
error, // Indicates a non-fatal error occurred. error, // Indicates a non-fatal error occurred.
warning, // Indicates potentially incorrect/unintentional behavior. warning, // Indicates potentially incorrect/unintentional behavior.
info, // Indicates general information. info, // Indicates general information.
verbose, // Noisier/potentially unimportant information. verbose, // Noisier/potentially unimportant information.
debug, // Information intended for debugging purposes only. debug, // Information intended for debugging purposes only.
trace // Tracer-like levels of verbosity may impact performance. trace // Tracer-like levels of verbosity may impact performance.
}; };
} // namespace logging } // namespace logging

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;
}; };
@ -72,7 +81,7 @@ struct static_theme {
}; };
template <std::integral T> template <std::integral T>
requires (!std::same_as<T, bool>) requires(!std::same_as<T, bool>)
struct theme<T> : static_theme<fmt::color::light_sky_blue> {}; struct theme<T> : static_theme<fmt::color::light_sky_blue> {};
template <> template <>
@ -85,7 +94,7 @@ template <detail::printable_pointer T>
struct theme<T> : static_theme<fmt::color::pale_violet_red> {}; struct theme<T> : static_theme<fmt::color::pale_violet_red> {};
template <typename E> template <typename E>
requires std::is_enum_v<E> requires std::is_enum_v<E>
struct theme<E> : static_theme<fmt::color::gray> {}; struct theme<E> : static_theme<fmt::color::gray> {};
// Errors // Errors
@ -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,37 +116,40 @@ 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,
fmt::color::fire_brick, fmt::color::fire_brick,
fmt::color::golden_rod, fmt::color::golden_rod,
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>
concept has_theme = has_static_theme<T> || has_dynamic_theme<T>; concept has_theme = has_static_theme<T> || has_dynamic_theme<T>;
template <typename T> template <typename T>
requires (has_theme<T>) requires(has_theme<T>)
struct theme<std::span<T>> : theme<T> {}; struct theme<std::span<T>> : theme<T> {};
template <typename T> template <typename T>
@ -156,12 +167,12 @@ 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 {
@ -33,7 +33,7 @@ namespace logging {
// logger.log<...>("value: {}", v); // logs "value: Foo::Bar" // logger.log<...>("value: {}", v); // logs "value: Foo::Bar"
// logger.log<...>("value: {}", enum_name_only{v}); // logs "value: Bar" // logger.log<...>("value: {}", enum_name_only{v}); // logs "value: Bar"
template <typename E> template <typename E>
requires std::is_enum_v<E> requires std::is_enum_v<E>
struct enum_name_only { struct enum_name_only {
E v; E v;
}; };
@ -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,323 +31,362 @@ 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;
}; };
namespace format { namespace format {
// Classification of the format type. Used by the token API to distinguish // Classification of the format type. Used by the token API to distinguish
// different formats. // different formats.
enum class type : std::uint8_t { enum class type : std::uint8_t {
invalid, invalid,
unsigned_int, unsigned_int,
signed_int, signed_int,
string, string,
binary, binary,
nil, nil,
boolean, boolean,
array, array,
map, map,
array_view, array_view,
map_view, map_view,
}; };
// Flags that may control the behavior of readers/writers. // Flags that may control the behavior of readers/writers.
enum flag : std::uint8_t { enum flag : std::uint8_t {
apply_mask = 1, apply_mask = 1,
fixed_size = 2, fixed_size = 2,
}; };
// MessagePack formats break down into one of the following schemes: // MessagePack formats break down into one of the following schemes:
// Marker byte + Fixed-length value // Marker byte + Fixed-length value
// Marker byte + Fixed-length payload length + payload // Marker byte + Fixed-length payload length + payload
// //
// 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 {
constexpr static flag flags = {}; constexpr static flag flags = {};
}; };
// Traits describing a particular format. // Traits describing a particular format.
struct traits { struct traits {
std::uint8_t flags{}; std::uint8_t flags{};
std::uint8_t size{}; std::uint8_t size{};
std::byte mask{}; std::byte mask{};
type token_type{}; type token_type{};
constexpr auto operator<=>(traits const&) const noexcept = default; constexpr auto operator<=>(traits const&) const noexcept = default;
}; };
// "Sentinel" traits instance indicating no trait found // "Sentinel" traits instance indicating no trait found
constexpr static traits no_traits {}; constexpr static traits no_traits{};
struct fixtype_format : base_format {}; struct fixtype_format : base_format {};
template <class> template <class>
struct resolve_token_type { struct resolve_token_type {
constexpr static type value = type::invalid; constexpr static type value = type::invalid;
}; };
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 <>
struct resolve_token_type<std::string_view> { struct resolve_token_type<std::string_view> {
constexpr static type value = type::string; constexpr static type value = type::string;
}; };
template <> template <>
struct resolve_token_type<std::span<std::byte const>> { struct resolve_token_type<std::span<std::byte const>> {
constexpr static type value = type::binary; constexpr static type value = type::binary;
}; };
template <> template <>
struct resolve_token_type<bool> { struct resolve_token_type<bool> {
constexpr static type value = type::boolean; constexpr static type value = type::boolean;
}; };
template <> template <>
struct resolve_token_type<nil> { struct resolve_token_type<nil> {
constexpr static type value = type::nil; constexpr static type value = type::nil;
}; };
template <> template <>
struct resolve_token_type<invalid> { struct resolve_token_type<invalid> {
constexpr static type value = type::invalid; constexpr static type value = type::invalid;
}; };
template <> template <>
struct resolve_token_type<array_desc> { struct resolve_token_type<array_desc> {
constexpr static type value = type::array; constexpr static type value = type::array;
}; };
template <> template <>
struct resolve_token_type<map_desc> { struct resolve_token_type<map_desc> {
constexpr static type value = type::map; constexpr static type value = type::map;
}; };
template <typename T> template <typename T>
constexpr static auto resolve_type_v = resolve_token_type<T>::value; constexpr static auto resolve_type_v = resolve_token_type<T>::value;
// The library's representation of certain data types does not always // The library's representation of certain data types does not always
// match the underlying data serialized in the message. For example, // match the underlying data serialized in the message. For example,
// arrays and maps are encoded as a marker byte + fixed length value, but // arrays and maps are encoded as a marker byte + fixed length value, but
// msgpack will present them wrapped in a structure for stronger type // msgpack will present them wrapped in a structure for stronger type
// semantics. // semantics.
template <std::uint8_t Marker, typename First, typename Value = First> template <std::uint8_t Marker, typename First, typename Value = First>
struct format : base_format { struct format : base_format {
using first_type = First; using first_type = First;
using value_type = Value; // Can be overridden using value_type = Value; // Can be overridden
constexpr static std::byte marker{Marker}; constexpr static std::byte marker{Marker};
constexpr static payload payload_type{payload::fixed}; constexpr static payload payload_type{payload::fixed};
constexpr static std::uint8_t flags{}; constexpr static std::uint8_t flags{};
}; };
template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t> template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t>
struct fixtype : fixtype_format { struct fixtype : fixtype_format {
using first_type = First; using first_type = First;
using value_type = first_type; // Can be overridden using value_type = first_type; // Can be overridden
constexpr static std::byte marker{Marker}; constexpr static std::byte marker{Marker};
constexpr static std::byte mask{Mask}; constexpr static std::byte mask{Mask};
constexpr static payload payload_type{payload::fixed}; constexpr static payload payload_type{payload::fixed};
constexpr static auto flags{flag::apply_mask}; constexpr static auto flags{flag::apply_mask};
}; };
/* /*
* Integral types * Integral types
*/ */
// Positive/negative fixint represent the literal value specified, do not // Positive/negative fixint represent the literal value specified, do not
// need to mask it off. // need to mask it off.
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> {
constexpr static flag flags{};
};
struct uint8 : format<0xcc, std::uint8_t> {}; struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
struct uint16 : format<0xcd, std::uint16_t> {}; constexpr static flag flags{};
struct uint32 : format<0xce, std::uint32_t> {}; };
struct uint64 : format<0xcf, std::uint64_t> {};
struct int8 : format<0xd0, std::int8_t> {}; struct uint8 : format<0xcc, std::uint8_t> {};
struct int16 : format<0xd1, std::int16_t> {};
struct int32 : format<0xd2, std::int32_t> {};
struct int64 : format<0xd3, std::int64_t> {};
/* struct uint16 : format<0xcd, std::uint16_t> {};
* Other primitive types
*/
struct nil : fixtype<0xc0, 0x00> {
using value_type = msgpack::nil;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct invalid : fixtype<0xc1, 0x00> { struct uint32 : format<0xce, std::uint32_t> {};
using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct boolean : fixtype<0xc2, 0x01> { struct uint64 : format<0xcf, std::uint64_t> {};
using value_type = bool;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
/* struct int8 : format<0xd0, std::int8_t> {};
* Maps
*/
template <typename Fmt> struct map_format : Fmt { struct int16 : format<0xd1, std::int16_t> {};
using value_type = map_desc;
};
struct fixmap : map_format<fixtype<0x80, 0x0f>> {}; struct int32 : format<0xd2, std::int32_t> {};
struct map16 : map_format<format<0xde, std::uint16_t>> {};
struct map32 : map_format<format<0xdf, std::uint32_t>> {};
/* struct int64 : format<0xd3, std::int64_t> {};
* Arrays
*/
template <typename Fmt> struct array_format : Fmt { /*
using value_type = array_desc; * Other primitive types
}; */
struct nil : fixtype<0xc0, 0x00> {
using value_type = msgpack::nil;
constexpr static flag flags{flag::fixed_size};
};
struct fixarray : array_format<fixtype<0x90, 0x0f>> {}; struct invalid : fixtype<0xc1, 0x00> {
struct array16 : array_format<format<0xdc, std::uint16_t>> {}; using value_type = msgpack::invalid;
struct array32 : array_format<format<0xdd, std::uint32_t>> {}; constexpr static flag flags{flag::fixed_size};
};
/* struct boolean : fixtype<0xc2, 0x01> {
* Strings using value_type = bool;
*/ constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
template <typename Fmt> struct string_format : Fmt { /*
using value_type = std::string_view; * Maps
constexpr static auto payload_type = payload::variable; */
};
template <typename Fmt>
struct map_format : Fmt {
using value_type = map_desc;
};
struct fixstr : string_format<fixtype<0xa0, 0x1f>> {}; struct fixmap : map_format<fixtype<0x80, 0x0f>> {};
struct str8 : string_format<format<0xd9, std::uint8_t>> {};
struct str16 : string_format<format<0xda, std::uint16_t>> {};
struct str32 : string_format<format<0xdb, std::uint32_t>> {};
/* struct map16 : map_format<format<0xde, std::uint16_t>> {};
* Binary arrays
*/
template <typename Fmt> struct bin_format : Fmt { struct map32 : map_format<format<0xdf, std::uint32_t>> {};
using value_type = std::span<std::byte const>;
constexpr static auto payload_type = payload::variable;
};
struct bin8 : bin_format<format<0xc4, std::uint8_t>> {}; /*
struct bin16 : bin_format<format<0xc5, std::uint16_t>> {}; * Arrays
struct bin32 : bin_format<format<0xc6, std::uint32_t>> {}; */
/* template <typename Fmt>
* Extension types, not yet supported. struct array_format : Fmt {
*/ using value_type = array_desc;
};
template <typename Fmt> struct unimplemented_format : Fmt {}; struct fixarray : array_format<fixtype<0x90, 0x0f>> {};
struct fixext1 : unimplemented_format<fixtype<0xd4, 0x00>> {}; struct array16 : array_format<format<0xdc, std::uint16_t>> {};
struct fixext2 : unimplemented_format<fixtype<0xd5, 0x00>> {};
struct fixext4 : unimplemented_format<fixtype<0xd6, 0x00>> {};
struct fixext8 : unimplemented_format<fixtype<0xd7, 0x00>> {};
struct fixext16 : unimplemented_format<fixtype<0xd8, 0x00>> {};
struct ext8 : unimplemented_format<format<0xc7, std::uint8_t>> {};
struct ext16 : unimplemented_format<format<0xc8, std::uint16_t>> {};
struct ext32 : unimplemented_format<format<0xc9, std::uint32_t>> {};
template <typename T> struct array32 : array_format<format<0xdd, std::uint32_t>> {};
struct unimplemented : std::false_type {};
template <typename Fmt> /*
struct unimplemented<unimplemented_format<Fmt>> : std::true_type {}; * Strings
*/
template <typename Fmt>
struct string_format : Fmt {
using value_type = std::string_view;
constexpr static auto payload_type = payload::variable;
};
struct fixstr : string_format<fixtype<0xa0, 0x1f>> {};
struct str8 : string_format<format<0xd9, std::uint8_t>> {};
struct str16 : string_format<format<0xda, std::uint16_t>> {};
struct str32 : string_format<format<0xdb, std::uint32_t>> {};
/*
* Binary arrays
*/
template <typename Fmt>
struct bin_format : Fmt {
using value_type = std::span<std::byte const>;
constexpr static auto payload_type = payload::variable;
};
struct bin8 : bin_format<format<0xc4, std::uint8_t>> {};
struct bin16 : bin_format<format<0xc5, std::uint16_t>> {};
struct bin32 : bin_format<format<0xc6, std::uint32_t>> {};
/*
* Extension types, not yet supported.
*/
template <typename Fmt>
struct unimplemented_format : Fmt {};
struct fixext1 : unimplemented_format<fixtype<0xd4, 0x00>> {};
struct fixext2 : unimplemented_format<fixtype<0xd5, 0x00>> {};
struct fixext4 : unimplemented_format<fixtype<0xd6, 0x00>> {};
struct fixext8 : unimplemented_format<fixtype<0xd7, 0x00>> {};
struct fixext16 : unimplemented_format<fixtype<0xd8, 0x00>> {};
struct ext8 : unimplemented_format<format<0xc7, std::uint8_t>> {};
struct ext16 : unimplemented_format<format<0xc8, std::uint16_t>> {};
struct ext32 : unimplemented_format<format<0xc9, std::uint32_t>> {};
template <typename T>
struct unimplemented : std::false_type {};
template <typename Fmt>
struct unimplemented<unimplemented_format<Fmt>> : std::true_type {};
namespace detail { 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;
template <typename... Ts, template <typename> typename Predicate> template <typename... Ts, template <typename> typename Predicate>
struct filter_type_list<type_list<>, type_list<Ts...>, Predicate> { struct filter_type_list<type_list<>, type_list<Ts...>, Predicate> {
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,
struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> { template <typename> typename Predicate>
using type = typename std::conditional_t< struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> {
Predicate<Head>::value, using type = typename std::conditional_t<Predicate<Head>::value,
filter_type_list<type_list<Tail...>, type_list<Ts..., Head>, Predicate>, filter_type_list<type_list<Tail...>, type_list<Ts..., Head>,
filter_type_list<type_list<Tail...>, type_list<Ts...>, Predicate>>::type; Predicate>,
}; 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>
using filter = filter_type_list<List, type_list<>, Predicate>; using filter = filter_type_list<List, type_list<>, Predicate>;
template <typename List, template <typename> typename Predicate> template <typename List, template <typename> typename Predicate>
using filter_t = typename filter<List, Predicate>::type; using filter_t = typename filter<List, Predicate>::type;
} // namespace detail } // namespace detail
} // namespace format } // namespace format
@ -358,35 +397,33 @@ template <typename T>
concept is_fixtype = std::is_base_of_v<format::fixtype_format, T>; concept is_fixtype = std::is_base_of_v<format::fixtype_format, T>;
namespace format { namespace format {
// This template instantiates a format::traits object for a given format. // This template instantiates a format::traits object for a given format.
// This should only occur once per format type, and should happen at // This should only occur once per format type, and should happen at
// compile time. // compile time.
// //
// This isn't the prettiest way to go about it. // This isn't the prettiest way to go about it.
template <format_type Format> template <format_type Format>
inline traits const& get_traits() noexcept { inline traits const& get_traits() noexcept {
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; }
} if constexpr (is_fixtype<Format>) {
if constexpr (is_fixtype<Format>) { inst.mask = Format::mask;
inst.mask = Format::mask; }
}
return inst;
}();
return inst; return inst;
}; }();
return inst;
};
} // namespace format } // namespace format
template <format_type... Formats> template <format_type... Formats>

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,268 +35,265 @@ 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
namespace detail { namespace detail {
// Generic mechanism for reading a number of bytes out of an interator. // Generic mechanism for reading a number of bytes out of an interator.
template <std::size_t N, typename Itr> template <std::size_t N, typename Itr>
constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept { constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept {
std::array<std::byte, N> out; std::array<std::byte, N> out;
auto const end = inp + N; auto const end = inp + N;
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;
}
// Read an integral type out of the iterator.
template <std::integral T, typename Itr>
constexpr inline decltype(auto) read_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)));
}
}
// 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);
}
};
template <typename T>
using expected = tl::expected<T, error>;
template <format_type F, typename Iter>
constexpr inline bool accept(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;
}
}
// Read a value from a MessagePack message. Assumes that itr != end to
// start with.
template <format_type F, typename Iter>
requires(!format::unimplemented<F>::value)
constexpr inline auto read(Iter& itr, Iter const end) noexcept
-> expected<typename F::value_type> {
// Copy off the iterator. Update it at the end _if_ everything goes
// smoothly.
auto cur = itr;
if (!accept<F>(cur)) 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 = typename std::iterator_traits<Iter>::difference_type;
if (std::distance(cur, end) < diff_type{sizeof(first_type)}) {
return tl::make_unexpected(error::incomplete_message);
} }
// Read an integral type out of the iterator. auto f = read_integral<first_type>(cur);
template <std::integral T, typename Itr> if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
constexpr inline decltype(auto) read_integral(Itr& inp) noexcept { f &= decltype(f)(F::mask);
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)));
}
} }
if constexpr (F::payload_type == format::payload::fixed) {
// This helper structure provides a customization point for converting an // We've read all we need to read. Update itr and return the value.
// iterator to some other type (e.g. a pointer) in the event that the itr = cur;
// final format does not have a suitable constructor. It's only currently return value_type(f);
// used for string_view, but may be overridden on platforms that provide } else {
// their own types. // We're reading a variable length payload. `f` is the length of the
template <typename T, typename Iter> // payload. Ensure that the span has enough data and construct the
struct iterator_adapter { // value_type accordingly.
static constexpr auto convert(Iter itr) { return itr; } if (std::distance(cur, end) < diff_type{f}) {
};
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); }
};
template <typename T>
using expected = tl::expected<T, error>;
template <format_type F, typename Iter>
constexpr inline bool accept(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;
}
}
// Read a value from a MessagePack message. Assumes that itr != end to
// start with.
template <format_type F, typename Iter>
requires (!format::unimplemented<F>::value)
constexpr inline auto read(Iter& itr, Iter const end) noexcept ->
expected<typename F::value_type> {
// Copy off the iterator. Update it at the end _if_ everything goes
// smoothly.
auto cur = itr;
if (!accept<F>(cur)) 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 = typename std::iterator_traits<Iter>::difference_type;
if (std::distance(cur, end) < diff_type{sizeof(first_type)}) {
return tl::make_unexpected(error::incomplete_message); return tl::make_unexpected(error::incomplete_message);
} }
auto f = read_integral<first_type>(cur); itr = cur + f;
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) { using adapt = iterator_adapter<value_type, Iter>;
f &= decltype(f)(F::mask); return value_type(adapt::convert(cur), f);
} }
}
if constexpr (F::payload_type == format::payload::fixed) { // "Relaxed" reader policies allow for certain conversions to take place
// We've read all we need to read. Update itr and return the value. // at runtime. For instance, any smaller type of the same category is
itr = cur; // allowed.
return value_type(f); //
} else { // TODO: Possible improvement to readability; specify a single format list
// We're reading a variable length payload. `f` is the length of the // containing all formats, and filter them down for each instantiated
// payload. Ensure that the span has enough data and construct the // format. Note that this would probably have compile-time performance
// value_type accordingly. // implications.
if (std::distance(cur, end) < diff_type{f}) {
return tl::make_unexpected(error::incomplete_message);
}
itr = cur + f; template <format_type>
using adapt = iterator_adapter<value_type, Iter>; struct relaxed_conversions {};
return value_type(adapt::convert(cur), f);
} template <>
struct relaxed_conversions<format::uint8> {
using type_list = format_list<format::uint8, format::positive_fixint>;
};
template <>
struct relaxed_conversions<format::uint16> {
using type_list =
format_list<format::uint16, format::uint8, format::positive_fixint>;
};
template <>
struct relaxed_conversions<format::uint32> {
using type_list = format_list<format::uint32, format::uint16, format::uint8,
format::positive_fixint>;
};
template <>
struct relaxed_conversions<format::uint64> {
using type_list = format_list<format::uint64, format::uint32,
format::uint16, format::uint8, format::positive_fixint>;
};
template <>
struct relaxed_conversions<format::int8> {
using type_list = format_list<format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int16> {
using type_list =
format_list<format::int16, format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int32> {
using type_list = format_list<format::int32, format::int16, format::int8,
format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int64> {
using type_list = format_list<format::int64, format::int32, format::int16,
format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::str8> {
using type_list = format_list<format::str8, format::fixstr>;
};
template <>
struct relaxed_conversions<format::str16> {
using type_list = format_list<format::str16, format::str8, format::fixstr>;
};
template <>
struct relaxed_conversions<format::map16> {
using type_list = format_list<format::map16, format::fixmap>;
};
template <>
struct relaxed_conversions<format::map32> {
using type_list = format_list<format::map32, format::map16, format::fixmap>;
};
template <typename F>
concept has_conversions = format_type<F> && requires {
typename relaxed_conversions<F>::type_list;
};
template <typename policy>
struct reader_policy_traits {};
template <>
struct reader_policy_traits<reader_policy::strict> {
template <format_type F, typename Iter>
constexpr static bool accept(Iter itr) noexcept {
return detail::accept<F>(itr);
} }
// "Relaxed" reader policies allow for certain conversions to take place template <format_type F, typename Iter>
// at runtime. For instance, any smaller type of the same category is constexpr static decltype(auto) read(Iter& itr, Iter const end) noexcept {
// allowed. return detail::read<F>(itr, end);
//
// TODO: Possible improvement to readability; specify a single format list
// containing all formats, and filter them down for each instantiated
// format. Note that this would probably have compile-time performance
// implications.
template <format_type>
struct relaxed_conversions {};
template <>
struct relaxed_conversions<format::uint8> {
using type_list = format_list<format::uint8, format::positive_fixint>;
}; };
};
template <> template <format_type fmt, typename fmtlist, typename Iter, std::size_t I = 0>
struct relaxed_conversions<format::uint16> { constexpr static auto relaxed_read_impl(Iter& itr, Iter const end) noexcept
using type_list = format_list<format::uint16, format::uint8, -> expected<typename fmt::value_type> {
format::positive_fixint>; if constexpr (I < fmtlist::size) {
}; using this_format = format_list_at<I, fmtlist>;
auto v = detail::read<this_format>(itr, end);
template <> if (v.has_value()) {
struct relaxed_conversions<format::uint32> { return v.value();
using type_list = format_list<format::uint32, format::uint16, } else if (v.error() == error::wrong_type) {
format::uint8, format::positive_fixint>; return relaxed_read_impl<fmt, fmtlist, Iter, I + 1>(itr, end);
}; } else {
return v;
template <>
struct relaxed_conversions<format::uint64> {
using type_list = format_list<format::uint64, format::uint32,
format::uint16, format::uint8, format::positive_fixint>;
};
template <>
struct relaxed_conversions<format::int8> {
using type_list = format_list<format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int16> {
using type_list = format_list<format::int16, format::int8,
format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int32> {
using type_list = format_list<format::int32, format::int16,
format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::int64> {
using type_list = format_list<format::int64, format::int32,
format::int16, format::int8, format::negative_fixint>;
};
template <>
struct relaxed_conversions<format::str8> {
using type_list = format_list<format::str8, format::fixstr>;
};
template <>
struct relaxed_conversions<format::str16> {
using type_list = format_list<format::str16, format::str8,
format::fixstr>;
};
template <>
struct relaxed_conversions<format::map16> {
using type_list = format_list<format::map16, format::fixmap>;
};
template <>
struct relaxed_conversions<format::map32> {
using type_list = format_list<format::map32, format::map16,
format::fixmap>;
};
template <typename F>
concept has_conversions = format_type<F> && requires {
typename relaxed_conversions<F>::type_list;
};
template <typename policy>
struct reader_policy_traits {};
template <>
struct reader_policy_traits<reader_policy::strict> {
template <format_type F, typename Iter>
constexpr static bool accept(Iter itr) noexcept {
return detail::accept<F>(itr);
} }
} else {
return tl::make_unexpected(error::wrong_type);
}
}
template <format_type F, typename Iter> template <>
constexpr static decltype(auto) read(Iter& itr, Iter const end) struct reader_policy_traits<reader_policy::relaxed> {
noexcept { template <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const& end) noexcept {
if constexpr (has_conversions<F>) {
using format_list = typename relaxed_conversions<F>::type_list;
return relaxed_read_impl<F, format_list, Iter, 0>(itr, end);
} else {
// Fall back on strict policy
return detail::read<F>(itr, end); return detail::read<F>(itr, end);
};
};
template <format_type fmt, typename fmtlist, typename Iter,
std::size_t I = 0>
constexpr static auto relaxed_read_impl(Iter& itr, Iter const end)
noexcept -> expected<typename fmt::value_type> {
if constexpr (I < fmtlist::size) {
using this_format = format_list_at<I, fmtlist>;
auto v = detail::read<this_format>(itr, end);
if (v.has_value()) {
return v.value();
} else if (v.error() == error::wrong_type) {
return relaxed_read_impl<fmt, fmtlist, Iter, I + 1>(itr, end);
} else {
return v;
}
} else {
return tl::make_unexpected(error::wrong_type);
} }
} }
};
template <>
struct reader_policy_traits<reader_policy::relaxed> {
template <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const& end)
noexcept {
if constexpr (has_conversions<F>) {
using format_list = typename relaxed_conversions<F>::type_list;
return relaxed_read_impl<F, format_list, Iter, 0>(itr, end);
} else {
// Fall back on strict policy
return detail::read<F>(itr, end);
}
}
};
} // namespace detail } // 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 {
@ -37,36 +37,162 @@ enum class writer_error {
}; };
// Helper template for writing a non-integral datatype to an output. // Helper template for writing a non-integral datatype to an output.
//template <typename T> // template <typename T>
//struct write_adapter { // struct write_adapter {
//template <typename Itr> // template <typename Itr>
//static constexpr tl::expected<Itr, error> write(T const& t); // static constexpr tl::expected<Itr, error> write(T const& t);
//}; //};
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,144 +224,145 @@ 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 {
template <typename T> template <typename T>
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);
if (!is_fixtype<F>) { if (!is_fixtype<F>) {
++size; // For format ++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 if constexpr (F::payload_type == format::payload::variable) {
// a fixed-length value. Additionally, this "first type" may be inlined size += write_adapter<typename F::value_type>::size(val);
// into the marker byte if it's a fix type. }
template <format_type F> return size;
constexpr inline expected<typename F::first_type> pack_first( }
typename F::value_type value) noexcept {
using value_type = typename F::value_type; // The "first type" is either the size of the variable length payload or
if constexpr (F::payload_type == format::payload::variable) { // a fixed-length value. Additionally, this "first type" may be inlined
return typename F::first_type(write_adapter<value_type>::size(value)); // 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 { } else {
if constexpr (requires { write_adapter<value_type>::value; }) { return typename F::first_type(value);
return typename F::first_type(write_adapter<value_type>::value(value)); }
} else { }
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>
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);
} }
} }
template <format_type F, typename Itr> *out++ = marker;
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; if constexpr (!is_fixtype<F>) {
out = write_adapter<typename decltype(result)::value_type>::write(
auto result = pack_first<F>(value); *result, out);
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> if constexpr (F::payload_type == format::payload::variable) {
struct format_hint; out = write_adapter<typename F::value_type>::write(value, out);
}
template <> return out;
struct format_hint<std::uint8_t> { }
using type = format::positive_fixint;
};
template <> template <typename T>
struct format_hint<std::uint16_t> { struct format_hint;
using type = format::uint16;
}; 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 } // 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;
@ -65,7 +64,7 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
== format::negative_fixint::marker) { == format::negative_fixint::marker) {
return format::get_traits<format::negative_fixint>(); return format::get_traits<format::negative_fixint>();
} else if ((id & ~format::positive_fixint::mask) } else if ((id & ~format::positive_fixint::mask)
== format::positive_fixint::marker) { == format::positive_fixint::marker) {
return format::get_traits<format::positive_fixint>(); return format::get_traits<format::positive_fixint>();
} else if ((id & ~format::fixstr::mask) == format::fixstr::marker) { } else if ((id & ~format::fixstr::mask) == format::fixstr::marker) {
return format::get_traits<format::fixstr>(); return format::get_traits<format::fixstr>();
@ -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
@ -181,7 +161,7 @@ public:
} }
// This is either the value of the format, or the size of the format. // This is either the value of the format, or the size of the format.
auto first_bytes = [&]{ auto first_bytes = [&] {
if (traits.size) { if (traits.size) {
return detail::read(traits.size, curr); return detail::read(traits.size, curr);
} else { } else {
@ -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);
@ -261,9 +237,9 @@ public:
error err = error::end_of_message; error err = error::end_of_message;
while (tok != token_buffer.end()) { while (tok != token_buffer.end()) {
auto result = read_one().map([&tok](auto&& t){ auto result = read_one().map([&tok](auto&& t) {
*tok = t; *tok = t;
++tok; ++tok;
}); });
if (!result) { if (!result) {
err = result.error(); err = result.error();
@ -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,9 +161,10 @@ 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_) {}
template <std::integral T> template <std::integral T>
explicit token_base(T value) noexcept { explicit token_base(T value) noexcept {
@ -215,11 +221,12 @@ public:
template <typename T> template <typename T>
constexpr tl::expected<T, error> get() const noexcept; constexpr tl::expected<T, error> get() const noexcept;
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,94 +273,82 @@ 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);
} }
return std::string{value_.str, size_and_type_.get_size()}; return std::string{value_.str, size_and_type_.get_size()};
} }
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);
} }
return std::string_view{value_.str, size_and_type_.get_size()}; return std::string_view{value_.str, size_and_type_.get_size()};
} }
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);
} }
return std::span<std::byte const>(value_.bp, size_and_type_.get_size()); return std::span<std::byte const>(value_.bp, size_and_type_.get_size());
} }
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);
} }
return nil{}; return nil{};
} }
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);
} }
return invalid{}; return invalid{};
} }
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);
} }
return array_desc{size_and_type_.get_size()}; return array_desc{size_and_type_.get_size()};
} }
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,119 +24,114 @@
#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:
class sentinel;
class iterator {
friend class sentinel;
using base_iterator = std::ranges::iterator_t<V>;
using base_sentinel = std::ranges::sentinel_t<V>;
using base_value_type = std::ranges::range_value_t<V>;
using base_reference = std::ranges::range_reference_t<V>;
base_iterator next(base_iterator current, std::size_t n = 1) {
while (n && current != std::ranges::end(*base_)) {
if (auto m = current->template get<map_desc>(); m) {
n += m->count * 2;
} else if (auto m = current->template get<array_desc>(); m) {
n += m->count;
}
++current;
--n;
}
return current;
}
public: public:
class sentinel; using value_type = std::pair<base_value_type, base_value_type>;
using reference = std::pair<base_reference, base_reference>;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
class iterator { iterator() = default;
friend class sentinel;
using base_iterator = std::ranges::iterator_t<V>; iterator(V const& base)
using base_sentinel = std::ranges::sentinel_t<V>; : base_{&base}
using base_value_type = std::ranges::range_value_t<V>; , k_{std::ranges::begin(base)} {
using base_reference = std::ranges::range_reference_t<V>; // Ensure that k_ points to a map_desc. If not, then we
// effectively treat this as the end.
base_iterator next(base_iterator current, std::size_t n = 1) { if (k_->type() == msgpack::format::type::map) {
while (n && current != std::ranges::end(*base_)) { remaining_ = k_->template get<msgpack::map_desc>()->count + 1;
if (auto m = current->template get<map_desc>(); m) { // Advance to the first entry in the map.
n += m->count * 2; ++k_;
} else if (auto m = current->template get<array_desc>(); m) {
n += m->count;
}
++current;
--n;
}
return current;
}
public:
using value_type = std::pair<base_value_type, base_value_type>;
using reference = std::pair<base_reference, base_reference>;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
iterator() = default;
iterator(V const& base)
: base_{&base}
, k_{std::ranges::begin(base)} {
// Ensure that k_ points to a map_desc. If not, then we
// effectively treat this as the end.
if (k_->type() == msgpack::format::type::map) {
remaining_ = k_->template get<msgpack::map_desc>()->count + 1;
// Advance to the first entry in the map.
++k_;
v_ = next(k_);
}
}
[[nodiscard]] reference operator*() const {
return { *k_, *v_ };
}
iterator& operator++() {
k_ = next(v_);
v_ = next(k_); v_ = next(k_);
--remaining_;
return *this;
} }
[[nodiscard]] iterator operator++(int) {
auto tmp = *this;
++(*this);
return tmp;
}
[[nodiscard]] bool operator==(iterator const& rhs) const {
return k_ == rhs.remaining_ &&
base_ == rhs.base_;
}
private:
V const* base_{};
base_iterator k_{};
base_iterator v_{};
std::size_t remaining_{};
};
class sentinel {
public:
[[nodiscard]] bool operator==(sentinel const&) const {
return true;
}
[[nodiscard]] bool operator==(iterator const& rhs) const {
return rhs.remaining_ == 0
|| rhs.k_ == std::ranges::end(*rhs.base_);
}
};
constexpr map_view() noexcept = default;
constexpr map_view(V base) : base_{std::move(base)} {}
[[nodiscard]] constexpr iterator begin() const {
return { base_ };
} }
[[nodiscard]] constexpr sentinel end() const {
return {}; [[nodiscard]] reference operator*() const { return {*k_, *v_}; }
iterator& operator++() {
k_ = next(v_);
v_ = next(k_);
--remaining_;
return *this;
} }
[[nodiscard]] iterator operator++(int) {
auto tmp = *this;
++(*this);
return tmp;
}
[[nodiscard]] bool operator==(iterator const& rhs) const {
return k_ == rhs.remaining_ && base_ == rhs.base_;
}
private: private:
V base_; V const* base_{};
base_iterator k_{};
base_iterator v_{};
std::size_t remaining_{};
}; };
template <class Range> class sentinel {
map_view(Range&&) -> map_view<std::views::all_t<Range>>; public:
[[nodiscard]] bool operator==(sentinel const&) const { return true; }
[[nodiscard]] bool operator==(iterator const& rhs) const {
return rhs.remaining_ == 0
|| rhs.k_ == std::ranges::end(*rhs.base_);
}
};
constexpr map_view() noexcept = default;
constexpr map_view(V base)
: base_{std::move(base)} {}
[[nodiscard]] constexpr iterator begin() const { return {base_}; }
[[nodiscard]] constexpr sentinel end() const { return {}; }
private:
V base_;
};
template <class Range>
map_view(Range&&) -> map_view<std::views::all_t<Range>>;
} // 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,8 +33,8 @@ 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;
case 1: return endianness::big; case 1: return endianness::big;
@ -66,7 +62,7 @@ constexpr void byte_swap(Iter begin, Iter end, OutIter dest) {
* byte_swap above should optimize to bswap or some equivalent assembly. * byte_swap above should optimize to bswap or some equivalent assembly.
*/ */
template <endianness From, endianness To, std::size_t N> template <endianness From, endianness To, std::size_t N>
requires (From != endianness::other && To != endianness::other) requires(From != endianness::other && To != endianness::other)
constexpr auto maybe_swap(std::array<std::byte, N> val) noexcept { constexpr auto maybe_swap(std::array<std::byte, N> val) noexcept {
if constexpr (From == To) { if constexpr (From == To) {
return val; return val;
@ -78,7 +74,7 @@ constexpr auto maybe_swap(std::array<std::byte, N> val) noexcept {
} }
template <endianness From, endianness To, typename Iter, typename OutIter> template <endianness From, endianness To, typename Iter, typename OutIter>
requires (From != endianness::other && To != endianness::other) requires(From != endianness::other && To != endianness::other)
constexpr void maybe_swap_iter(Iter begin, Iter end, OutIter dest) noexcept { constexpr void maybe_swap_iter(Iter begin, Iter end, OutIter dest) noexcept {
if constexpr (From == To) { if constexpr (From == To) {
std::copy(begin, end, dest); std::copy(begin, end, dest);
@ -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;
} }
@ -120,14 +123,14 @@ constexpr auto value_cast(Array&& data) noexcept {
* Byte swap implementation for arbitrary endiannesses. * Byte swap implementation for arbitrary endiannesses.
*/ */
template <endianness From, endianness To, typename T> template <endianness From, endianness To, typename T>
requires std::is_trivial_v<std::remove_reference_t<T>> requires std::is_trivial_v<std::remove_reference_t<T>>
constexpr T byte_swap(T val) noexcept { constexpr T byte_swap(T val) noexcept {
using array_type = std::array<std::byte, sizeof(T)>; using array_type = std::array<std::byte, sizeof(T)>;
return std::bit_cast<T>( return std::bit_cast<T>(
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 {};
std::uint32_t code; // An error code
std::string_view what; // A string template <typename T>
concept message_type = std::is_base_of_v<base_message, T>;
struct error_message : base_message {
std::uint32_t code; // An error code
std::string_view what; // A string
}; };
// C->S: Request to (re)connect. // 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

@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
// Session management for the "user" protocol.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef 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 <chrono>
#include <cstdint>
#include <functional>
#include <span>
#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 proto {
// Structure containing header information parsed from a buffer.
struct header_info {
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_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 {
public:
using close_handle = std::function<void(std::string_view)>;
session(std::string_view user_id) noexcept;
~session();
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(
std::span<std::byte const> buffer) noexcept;
tl::expected<connect_info, error> parse_connect(
std::span<msgpack::token> tokens) noexcept;
} // namespace proto
} // namespace parselink
#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>
@ -37,210 +37,205 @@
namespace argparse { namespace argparse {
namespace custom { namespace custom {
template <typename T> template <typename T>
struct argument_parser {}; struct argument_parser {};
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 {};
template <> template <>
struct argument_parser<bool> { struct argument_parser<bool> {
static bool* parse(std::string_view value) noexcept { static bool* parse(std::string_view value) noexcept {
if (value == "1" || value == "true") { if (value == "1" || value == "true") {
return new bool{true}; return new bool{true};
} else if (value == "0" || value == "false") { } else if (value == "0" || value == "false") {
return new bool{false}; return new bool{false};
}
return nullptr;
}
};
inline constexpr std::initializer_list<
std::tuple<char const*, std::chrono::nanoseconds>> unit_map = {
{"ns", std::chrono::nanoseconds{1}},
{"us", std::chrono::microseconds{1}},
{"ms", std::chrono::milliseconds{1}},
{"s", std::chrono::seconds{1}},
{"m", std::chrono::minutes{1}},
{"h", std::chrono::hours{1}},
};
template <typename rep, typename period>
struct argument_parser<std::chrono::duration<rep, period>> {
using duration = std::chrono::duration<rep, period>;
static duration* parse(std::string_view value) noexcept {
rep result;
auto err = std::from_chars(value.begin(), value.end(), result);
if (err.ec == std::errc{}) {
auto this_unit = std::string_view{err.ptr, value.end()};
for (auto const& [unit, dura] : unit_map) {
if (std::string_view{unit} == this_unit) {
auto v =
std::chrono::duration_cast<duration>(result * dura);
return new duration{v};
}
}
}
return nullptr;
}
};
template <typename T>
requires requires (T& t) {
std::from_chars(nullptr, nullptr, t);
}
struct argument_parser<T> {
static T* parse(std::string_view value) noexcept {
T result;
auto err = std::from_chars(value.begin(), value.end(), result);
return err.ec == std::errc{} ? new T{result} : nullptr;
}
};
template <>
struct argument_parser<std::string> {
static std::string* parse(std::string_view value) noexcept {
return new std::string{value};
}
};
template <typename T>
concept has_parser = requires {
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) }
-> std::same_as<T*>;
};
static_assert(has_parser<int>);
namespace detail {
// This wrapper acts similar to std::any, but also provides a method to
// parse a string_view for the value as well. Parsers can be implemented
// by creating an argparse::custom::argument_parser template specialization
// for a given type.
struct any_arg {
constexpr any_arg() = default;
template <typename T>
any_arg(T&& value) : iface(&dispatcher<std::decay_t<T>>::table) {
dispatcher<std::decay_t<T>>::create(*this, std::forward<T>(value));
}
any_arg(any_arg const& other) : iface(other.iface) {
if (other.iface) {
data = other.iface->copy(other.data);
}
}
any_arg(any_arg&& other) : data(std::move(other.data)), iface(other.iface) {
other.iface = nullptr;
}
any_arg& operator=(any_arg const& other) {
if (has_value()) {
iface->destroy(data);
}
iface = other.iface;
if (other.iface) {
data = other.iface->copy(other.data);
}
return *this;
}
~any_arg() {
if (has_value()) {
iface->destroy(data);
}
}
bool parse(std::string_view sv) {
return iface->parse(*this, sv);
}
bool has_value() const noexcept {
return static_cast<bool>(iface);
}
template <typename T>
bool holds() const noexcept {
return iface == &dispatcher<std::decay_t<T>>::table;
}
template <typename T>
friend T const* arg_cast(any_arg const*) noexcept;
void* data = nullptr;
struct interface {
void (*destroy)(void*);
void *(*copy)(void*);
bool (*parse)(any_arg&, std::string_view);
};
interface const* iface = nullptr;
template <typename T>
struct dispatcher {
template <typename... Args>
static void create(any_arg& self, Args&&... args) {
self.data = new T{std::forward<Args>(args)...};
}
static void* copy(void *ptr) {
return new T{*static_cast<T*>(ptr)};
}
static bool parse(any_arg& self, std::string_view sv) {
if constexpr (custom::has_parser<T>) {
self.data = custom::argument_parser<T>::parse(sv);
return static_cast<bool>(self.data);
} else if constexpr (has_parser<T>) {
self.data = argument_parser<T>::parse(sv);
return static_cast<bool>(self.data);
} else {
return false;
}
}
static void destroy(void* ptr) {
delete static_cast<T*>(ptr);
}
static T const* cast(void* ptr) {
return static_cast<T const*>(ptr);
}
static constexpr struct interface table {
&dispatcher::destroy, &dispatcher::copy, &dispatcher::parse };
};
};
template <typename T>
T const* arg_cast(any_arg const* ar) noexcept {
if (ar->holds<T>()) {
return any_arg::dispatcher<std::decay_t<T>>::cast(ar->data);
} }
return nullptr; return nullptr;
} }
};
inline constexpr std::initializer_list<
std::tuple<char const*, std::chrono::nanoseconds>>
unit_map = {
{"ns", std::chrono::nanoseconds{1}},
{"us", std::chrono::microseconds{1}},
{"ms", std::chrono::milliseconds{1}},
{"s", std::chrono::seconds{1}},
{"m", std::chrono::minutes{1}},
{"h", std::chrono::hours{1}},
};
template <typename rep, typename period>
struct argument_parser<std::chrono::duration<rep, period>> {
using duration = std::chrono::duration<rep, period>;
static duration* parse(std::string_view value) noexcept {
rep result;
auto err = std::from_chars(value.begin(), value.end(), result);
if (err.ec == std::errc{}) {
auto this_unit = std::string_view{err.ptr, value.end()};
for (auto const& [unit, dura] : unit_map) {
if (std::string_view{unit} == this_unit) {
auto v =
std::chrono::duration_cast<duration>(result * dura);
return new duration{v};
}
}
}
return nullptr;
}
};
template <typename T>
requires requires(T& t) { std::from_chars(nullptr, nullptr, t); }
struct argument_parser<T> {
static T* parse(std::string_view value) noexcept {
T result;
auto err = std::from_chars(value.begin(), value.end(), result);
return err.ec == std::errc{} ? new T{result} : nullptr;
}
};
template <>
struct argument_parser<std::string> {
static std::string* parse(std::string_view value) noexcept {
return new std::string{value};
}
};
template <typename T>
concept has_parser = requires {
{
argument_parser<std::decay_t<T>>::parse(std::string_view{})
} -> std::same_as<T*>;
};
static_assert(has_parser<int>);
namespace detail {
// This wrapper acts similar to std::any, but also provides a method to
// parse a string_view for the value as well. Parsers can be implemented
// by creating an argparse::custom::argument_parser template specialization
// for a given type.
struct any_arg {
constexpr any_arg() = default;
template <typename T>
any_arg(T&& value)
: iface(&dispatcher<std::decay_t<T>>::table) {
dispatcher<std::decay_t<T>>::create(*this, std::forward<T>(value));
}
any_arg(any_arg const& other)
: iface(other.iface) {
if (other.iface) {
data = other.iface->copy(other.data);
}
}
any_arg(any_arg&& other)
: data(std::move(other.data))
, iface(other.iface) {
other.iface = nullptr;
}
any_arg& operator=(any_arg const& other) {
if (has_value()) {
iface->destroy(data);
}
iface = other.iface;
if (other.iface) {
data = other.iface->copy(other.data);
}
return *this;
}
~any_arg() {
if (has_value()) {
iface->destroy(data);
}
}
bool parse(std::string_view sv) { return iface->parse(*this, sv); }
bool has_value() const noexcept { return static_cast<bool>(iface); }
template <typename T>
bool holds() const noexcept {
return iface == &dispatcher<std::decay_t<T>>::table;
}
template <typename T>
friend T const* arg_cast(any_arg const*) noexcept;
void* data = nullptr;
struct interface {
void (*destroy)(void*);
void* (*copy)(void*);
bool (*parse)(any_arg&, std::string_view);
};
interface const* iface = nullptr;
template <typename T>
struct dispatcher {
template <typename... Args>
static void create(any_arg& self, Args&&... args) {
self.data = new T{std::forward<Args>(args)...};
}
static void* copy(void* ptr) { return new T{*static_cast<T*>(ptr)}; }
static bool parse(any_arg& self, std::string_view sv) {
if constexpr (custom::has_parser<T>) {
self.data = custom::argument_parser<T>::parse(sv);
return static_cast<bool>(self.data);
} else if constexpr (has_parser<T>) {
self.data = argument_parser<T>::parse(sv);
return static_cast<bool>(self.data);
} else {
return false;
}
}
static void destroy(void* ptr) { delete static_cast<T*>(ptr); }
static T const* cast(void* ptr) { return static_cast<T const*>(ptr); }
static constexpr struct interface table {
&dispatcher::destroy, &dispatcher::copy, &dispatcher::parse
};
};
};
template <typename T>
T const* arg_cast(any_arg const* ar) noexcept {
if (ar->holds<T>()) {
return any_arg::dispatcher<std::decay_t<T>>::cast(ar->data);
}
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

@ -15,9 +15,10 @@ cc_binary(
deps = [ deps = [
"headers", "headers",
"//include/parselink:msgpack", "//include/parselink:msgpack",
"//include/parselink:proto",
"//include/parselink:utility", "//include/parselink:utility",
"//source/logging", "//source/logging",
"//source/proto",
"//source/server",
"@boost//:beast", "@boost//:beast",
], ],
) )

View File

@ -26,7 +26,7 @@ namespace {
struct console_endpoint : public endpoint { struct console_endpoint : public endpoint {
static constexpr std::string_view format_string = static constexpr std::string_view format_string =
"{:%Y-%m-%d %H:%M:%S}.{:03} [{:<8}] {:>20} | {}\n"; "{:%Y-%m-%d %H:%M:%S}.{:03} [{:<8}] {:>20} | {}\n";
bool colored() const noexcept override { return true; } bool colored() const noexcept override { return true; }
@ -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,19 +1,19 @@
#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");
} }
using level = parselink::logging::level; using level = parselink::logging::level;
int run(std::span<std::string_view> arg_list) { int run(std::span<std::string_view> arg_list) {
argparse::command_line_parser parser({ argparse::command_line_parser parser({
{"address", {std::string{"0.0.0.0"}}}, {"address", {std::string{"0.0.0.0"}}},
{"user_port", {std::uint16_t{9001}}}, {"user_port", {std::uint16_t{9001}}},
{"websocket_port", {std::uint16_t{10501}}}, {"websocket_port", {std::uint16_t{10501}}},
{"verbose", {false}}, {"verbose", {false}},
}); });
auto args = parser.parse(arg_list); auto args = parser.parse(arg_list);
@ -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;

20
source/proto/BUILD Normal file
View File

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

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

198
source/proto/session.cpp Normal file
View File

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Server implementation. Currently, a monolithic server which:
// * Communicates with users via TCP (msgpack).
// * Runs the websocket server for overlays to read.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/proto/session.h"
#include "parselink/logging.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::proto;
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, ">");
}
};
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 {
auto reader = msgpack::token_reader(buffer);
auto magic = reader.read_one();
if (!magic || *magic != "prs") {
logger.error("Failed to parse magic");
return tl::unexpected(magic ? error::bad_data : error::incomplete);
}
auto size = reader.read_one().and_then(
[](auto t) { return t.template get<std::uint32_t>(); });
if (!size || !*size) {
logger.error("Failed to get valid message size");
return tl::unexpected(magic ? error::bad_data : error::incomplete);
}
if (*size > max_size) {
logger.error("Message size {} exceeds max {}", *size, max_size);
return tl::unexpected(error::too_large);
}
std::uint32_t amt = std::distance(buffer.begin(), reader.current());
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,46 +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/proto/message.h" #include "parselink/msgpack/core/unpacker.h"
#include "parselink/proto/parser.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;
enum class error {
system,
msgpack,
};
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// TODO(ksassenrath): These are logging formatters for various boost/asio types. // TODO(ksassenrath): These are logging formatters for various boost/asio types.
@ -71,34 +69,46 @@ struct parselink::logging::theme<boost::system::error_code>
template <> template <>
struct fmt::formatter<boost::system::error_code> struct fmt::formatter<boost::system::error_code>
: 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::formatter<std::string_view>::format(v.message(), ctx); return fmt::formatter<std::string_view>::format(v.message(), ctx);
} }
}; };
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, "{}",
@ -118,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, ">");
} }
@ -127,8 +136,8 @@ struct fmt::formatter<msgpack::token> {
template <typename T> template <typename T>
concept endpoint = requires(T const& t) { concept endpoint = requires(T const& t) {
{t.address()}; { t.address() };
{t.port()}; { t.port() };
}; };
template <endpoint T> template <endpoint T>
@ -136,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());
} }
}; };
@ -150,16 +158,15 @@ struct fmt::formatter<T>
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
namespace { 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:
@ -168,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);
@ -243,138 +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);
auto magic = reader.read_one().map(
[](auto t){ return t == "prs"; });
if (magic && *magic) {
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(
[](auto t){ return t.template get<std::size_t>(); });
if (sz && *sz) {
logger.debug("Got packet size from client: {}", *sz);
} else {
logger.debug("Failed to get packet size from client: {}", sz);
return tl::unexpected(magic.error());
}
// Copy the rest of the message to the buffer for parsing.
// TODO(ksassenrath): Replace vector with custom buffer.
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>>
buffer_message(std::vector<std::byte>& buffer) noexcept {
auto amt = buffer.size();
auto total = buffer.capacity();
buffer.resize(total);
while (amt < total) {
auto subf = std::span(buffer.begin() + amt, buffer.end());
auto [ec, n] = co_await socket_.async_read_some(
net::buffer(subf), no_ex_coro);
logger.debug("Read {} bytes, total is now {}", n, amt + n);
if (ec || n == 0) {
logger.error("Reading from user socket failed: {}", ec);
co_return tl::make_unexpected(ec);
}
amt += n;
}
co_return std::monostate{};
}
awaitable<tl::expected<std::vector<std::byte>, boost::system::error_code>>
await_message() noexcept {
// Use a small buffer on the stack to read the initial header. // Use a small buffer on the stack to read the initial header.
std::array<std::byte, 8> buffer; std::array<std::byte, 8> hdrbuff;
auto [ec, n] = co_await socket_.async_read_some( auto [ec, n] = co_await socket_.async_read_some(
net::buffer(buffer), no_ex_coro); net::buffer(hdrbuff), no_ex_coro);
if (ec) { if (ec) {
logger.error("Reading from user socket failed: {}", ec); logger.error("Reading hdr from user socket failed: {}", ec);
co_return tl::make_unexpected(ec); co_return tl::make_unexpected(proto::error::system_error);
} }
logger.debug("Read {} bytes from client: {}", n, logger.debug("Read {} bytes from client: {}", n,
std::span(buffer.data(), n)); std::span(hdrbuff.data(), n));
auto hdr = parse_header(std::span(buffer.data(), n)); auto maybe_hdr = proto::parse_header(std::span(hdrbuff.data(), n));
if (!hdr) {
logger.error("Unable to parse header: {}", hdr.error()); if (!maybe_hdr) {
co_return tl::make_unexpected(boost::system::error_code: ); logger.error("Unable to parse header: {}", maybe_hdr.error());
co_return; co_return tl::make_unexpected(maybe_hdr.error());
} }
auto msg = std::move(*hdr); // TODO(ksassenrath): Replace with specialized allocator.
buffer.resize(maybe_hdr->message_size);
if (auto result = co_await buffer_message(msg); !result) { // Copy remaining portion of message in initial read to the message
logger.error("Unable to parse header: {}", result.error()); // buffer.
co_return; 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;
while (amt < span.size()) {
auto subsp = span.subspan(amt);
auto [ec, n] = co_await socket_.async_read_some(
net::buffer(subsp), no_ex_coro);
logger.debug("Read {} bytes, total is now {}", n, amt + n);
if (ec || n == 0) {
logger.error("Reading msg from user socket failed: {}", ec);
co_return tl::make_unexpected(proto::error::system_error);
}
amt += n;
} }
co_return tl::monostate{};
} }
awaitable<bool> await_connect() noexcept { awaitable<void> await_connect() noexcept {
auto reader = msgpack::token_reader(msg); std::vector<std::byte> msgbuf;
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) { if (auto maybe_msg = co_await read_message(msgbuf); !maybe_msg) {
logger.debug("returning");
co_return; co_return;
} }
// Authenticate against database. auto connect_info = proto::parse<proto::connect_message>(msgbuf);
logger.debug("User {} established connection", maybe_user->user_id);
session_ = server_.establish_session(std::move(*maybe_user)); if (!connect_info) {
logger.error("Session failed: {}", connect_info.error());
co_return;
}
auto session = co_await server_.create_session(
shared_from_this(), *connect_info);
if (!session) {
co_return;
}
session_ = *session;
} }
enum class state { enum class state { init, authenticated, active };
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();
} }
} }
@ -382,7 +326,7 @@ std::error_code monolithic_server::run() noexcept {
logger.info("Starting server."); logger.info("Starting server.");
net::signal_set signals(io_context_, SIGINT, SIGTERM); net::signal_set signals(io_context_, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ signals.async_wait([&](auto, auto) {
logger.info("Received signal... Shutting down."); logger.info("Received signal... Shutting down.");
io_context_.stop(); io_context_.stop();
}); });
@ -394,13 +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 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,15 +60,19 @@ 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) {
if constexpr(has_static_theme<T>) { if constexpr (has_static_theme<T>) {
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style); return fmt::styled(v, theme<std::remove_cvref_t<T>>::style);
} else if constexpr (has_dynamic_theme<T>) { } else if constexpr (has_dynamic_theme<T>) {
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style(v)); return fmt::styled(v, theme<std::remove_cvref_t<T>>::style(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,18 +231,18 @@ 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();
} }
{ {
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,17 +250,17 @@ 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();
} }
{ {
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,9 +270,9 @@ 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,17 +280,17 @@ 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();
} }
{ {
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();
} }
@ -23,7 +30,7 @@ struct rng {
} }
template <typename T, std::size_t N> template <typename T, std::size_t N>
requires std::is_integral_v<T> requires std::is_integral_v<T>
auto get() noexcept { auto get() noexcept {
std::array<T, N> values; std::array<T, N> values;
for (auto& value : values) { for (auto& value : values) {

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 {
@ -10,55 +8,56 @@ using namespace boost::ut;
namespace format = msgpack::format; namespace format = msgpack::format;
template <typename... Bytes> template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) { 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>
std::array<T, C> data; struct oversized_array {
std::size_t size; std::array<T, C> data;
std::size_t size;
}; };
constexpr auto to_bytes_array_oversized(auto const &container) { constexpr auto to_bytes_array_oversized(auto const& container) {
oversized_array<std::byte> arr; oversized_array<std::byte> arr;
std::copy(std::begin(container), std::end(container), std::begin(arr.data)); std::copy(std::begin(container), std::end(container), std::begin(arr.data));
arr.size = std::distance(std::begin(container), std::end(container)); arr.size = std::distance(std::begin(container), std::end(container));
return arr; return arr;
} }
consteval auto generate_bytes(auto callable) { consteval auto generate_bytes(auto callable) {
constexpr auto oversized = to_bytes_array_oversized(callable()); constexpr auto oversized = to_bytes_array_oversized(callable());
std::array<std::byte, oversized.size> out; std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data), std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size), std::next(std::begin(oversized.data), oversized.size),
std::begin(out)); std::begin(out));
return out; return out;
} }
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));
std::copy(std::begin(b), std::next(std::begin(b), std::size(b)), std::copy(std::begin(b), std::next(std::begin(b), std::size(b)),
std::next(std::begin(out), std::size(a))); std::next(std::begin(out), std::size(a)));
return out; return out;
} }
template <typename T, typename Itr> template <typename T, typename Itr>
constexpr auto zip(T val, Itr begin, Itr end) { constexpr auto zip(T val, Itr begin, Itr end) {
std::vector<T> output; std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) { for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val); output.emplace_back(val);
output.emplace_back(*itr); output.emplace_back(*itr);
} }
return output; return output;
} }
template <typename T, typename Iterable> template <typename T, typename Iterable>
constexpr auto zip(T val, Iterable const &itr) { constexpr auto zip(T val, Iterable const& itr) {
return zip(val, std::begin(itr), std::end(itr)); return zip(val, std::begin(itr), std::end(itr));
} }
using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>; using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>;
@ -66,65 +65,65 @@ using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>;
} // namespace } // namespace
suite relaxed = [] { suite relaxed = [] {
"empty span"_test = [] { "empty span"_test = [] {
using error = msgpack::error; using error = msgpack::error;
std::span<std::byte> bytes; std::span<std::byte> bytes;
relaxed_reader reader(bytes); relaxed_reader reader(bytes);
auto v = reader.read<msgpack::format::uint8>(); auto v = reader.read<msgpack::format::uint8>();
expect(v.error() == error::end_of_message); expect(v.error() == error::end_of_message);
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Unsigned integers // Unsigned integers
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
"reader<reader_policy::relaxed>::read<format::uint8>"_test = [] { "reader<reader_policy::relaxed>::read<format::uint8>"_test = [] {
using fmt = format::uint8; using fmt = format::uint8;
using error = msgpack::error; using error = msgpack::error;
/** /**
* All bytes 0x00->0x79 are effectively literal uint8s. * All bytes 0x00->0x79 are effectively literal uint8s.
*/ */
{ {
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84); constexpr auto payload = make_bytes(0x52, 0xcc, 0x84);
relaxed_reader reader(payload); relaxed_reader reader(payload);
{ {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x52)); expect(result == std::uint8_t(0x52));
} }
{ {
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x84)); expect(result == std::uint8_t(0x84));
} }
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::end_of_message)); expect(result == tl::make_unexpected(error::end_of_message));
} }
}; };
"reader<reader_policy::relaxed>::read<formats::uint16>"_test = [] { "reader<reader_policy::relaxed>::read<formats::uint16>"_test = [] {
using fmt = msgpack::format::uint16; using fmt = msgpack::format::uint16;
using error = msgpack::error; using error = msgpack::error;
/** /**
* 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 =
relaxed_reader reader(payload); make_bytes(0x52, 0xcc, 0x84, 0xcd, 0xaa, 0xcc);
{ relaxed_reader reader(payload);
auto result = reader.read<fmt>(); {
expect(result == std::uint16_t(0x52)); auto result = reader.read<fmt>();
} expect(result == std::uint16_t(0x52));
{ }
auto result = reader.read<fmt>(); {
expect(result == std::uint16_t(0x84)); auto result = reader.read<fmt>();
} expect(result == std::uint16_t(0x84));
{ }
auto result = reader.read<fmt>(); {
expect(result == std::uint16_t(0xaacc)); auto result = reader.read<fmt>();
} expect(result == std::uint16_t(0xaacc));
}
auto result = reader.read<fmt>(); auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::end_of_message)); expect(result == tl::make_unexpected(error::end_of_message));
} }
}; };
}; };

File diff suppressed because it is too large Load Diff

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]);
@ -47,42 +46,48 @@ int main(int argc, char** argv) {
auto before = std::chrono::steady_clock::now(); auto before = std::chrono::steady_clock::now();
auto test = reader.read_some(buf); auto test = reader.read_some(buf);
auto after = std::chrono::steady_clock::now(); auto after = std::chrono::steady_clock::now();
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: ",
switch (token.type()) { magic_enum::enum_name(token.type()));
case msgpack::format::type::unsigned_int: switch (token.type()) {
fmt::print("{}", *(token.template get<std::uint64_t>())); case msgpack::format::type::unsigned_int:
break; fmt::print(
case msgpack::format::type::signed_int: "{}", *(token.template get<std::uint64_t>()));
fmt::print("{}", *(token.template get<std::int64_t>())); break;
break; case msgpack::format::type::signed_int:
case msgpack::format::type::boolean: fmt::print("{}", *(token.template get<std::int64_t>()));
fmt::print("{}", *(token.template get<bool>())); break;
break; case msgpack::format::type::boolean:
case msgpack::format::type::string: fmt::print("{}", *(token.template get<bool>()));
fmt::print("{}", *(token.template get<std::string_view>())); break;
break; case msgpack::format::type::string:
case msgpack::format::type::binary: fmt::print("{}",
fmt::print("{}", *(token.template get<std::span<std::byte const>>())); *(token.template get<std::string_view>()));
break; break;
case msgpack::format::type::map: case msgpack::format::type::binary:
fmt::print("map ({} entries)", token.template get<msgpack::map_desc>()->count); fmt::print(
break; "{}", *(token.template get<
case msgpack::format::type::array: std::span<std::byte const>>()));
fmt::print("array ({} entries)", token.template get<msgpack::array_desc>()->count); break;
break; case msgpack::format::type::map:
case msgpack::format::type::nil: fmt::print("map ({} entries)",
fmt::print("(nil)"); token.template get<msgpack::map_desc>()->count);
break; break;
case msgpack::format::type::invalid: case msgpack::format::type::array:
fmt::print("(invalid)"); fmt::print("array ({} entries)",
break; token.template get<msgpack::array_desc>()
->count);
break;
case msgpack::format::type::nil: fmt::print("(nil)"); break;
case msgpack::format::type::invalid:
fmt::print("(invalid)");
break;
}
fmt::print("\n");
} }
fmt::print("\n"); }).map_error([&](auto err) {
}
}).map_error([&](auto err){
fmt::print("Failed to read tokens: {}\n", (int)err); fmt::print("Failed to read tokens: {}\n", (int)err);
}); });
fmt::print("Took {} us\n", (after - before) / 1us); fmt::print("Took {} us\n", (after - before) / 1us);

View File

@ -1,40 +1,37 @@
#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;
namespace { namespace {
template <typename... Bytes> template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) { 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 First, typename... Others> template <typename First, typename... Others>
constexpr bool wrong_types(auto const& obj) { constexpr bool wrong_types(auto const& obj) {
auto err = tl::make_unexpected(msgpack::error::wrong_type); auto err = tl::make_unexpected(msgpack::error::wrong_type);
if (obj.template get<First>() != err) return false; if (obj.template get<First>() != err) return false;
if constexpr (sizeof...(Others)) { if constexpr (sizeof...(Others)) {
return wrong_types<Others...>(obj); return wrong_types<Others...>(obj);
} else { } else {
return true; return true;
}
}
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;
} }
} }
enum class foo : std::uint8_t { template <typename T, typename E>
a, b, c, d, 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;
}
} // namespace
enum class foo : std::uint8_t { a, b, c, d, e };
suite size_and_enum = [] { suite size_and_enum = [] {
"expected use case"_test = [] { "expected use case"_test = [] {
@ -118,18 +115,19 @@ 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));
extracted_val = std::move(*bytes_retrieved); extracted_val = std::move(*bytes_retrieved);
} }
expect(std::equal(expected_val.begin(), expected_val.end(), expect(std::equal(expected_val.begin(), expected_val.end(),
extracted_val.begin(), extracted_val.end())); extracted_val.begin(), extracted_val.end()));
}; };
}; };

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;
@ -18,17 +18,17 @@ constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
} }
template <typename... Bytes> template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) { 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;
std::copy(std::begin(container), std::end(container), std::begin(arr.data)); std::copy(std::begin(container), std::end(container), std::begin(arr.data));
@ -51,8 +51,8 @@ consteval auto build_string(auto callable) {
return string_array; return string_array;
} }
template <std::size_t ... Sizes> template <std::size_t... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept { constexpr auto cat(std::array<std::byte, Sizes> const&... a) noexcept {
std::array<std::byte, (Sizes + ...)> out; std::array<std::byte, (Sizes + ...)> out;
std::size_t index{}; std::size_t index{};
((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...); ((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...);
@ -89,14 +89,14 @@ constexpr auto from_string_view(std::string_view sv) {
} }
template <typename T, typename E> template <typename T, typename E>
std::ostream &operator<<(std::ostream &os, tl::expected<T, E> const &exp) { std::ostream& operator<<(std::ostream& os, tl::expected<T, E> const& exp) {
if (exp.has_value()) { if (exp.has_value()) {
os << "Value: '" << *exp << "'"; os << "Value: '" << *exp << "'";
} else { } else {
os << "Error"; os << "Error";
}
return os;
} }
return os;
}
bool test_incomplete_message(auto const& payload) { bool test_incomplete_message(auto const& payload) {
// Test incomplete message. // Test incomplete message.
@ -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,13 +422,11 @@ 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);
// Test incomplete message. // Test incomplete message.
expect(test_incomplete_message(payload)); expect(test_incomplete_message(payload));
@ -452,7 +444,7 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::bin16"_test = [] { "read format::bin16"_test = [] {
static constexpr auto bytes = generate_bytes([]{ static constexpr auto bytes = generate_bytes([] {
return repeat( return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08), make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
16); 16);
@ -477,7 +469,7 @@ suite reader_tests = [] {
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"read format::bin32"_test = [] { "read format::bin32"_test = [] {
static constexpr auto bytes = generate_bytes([]{ static constexpr auto bytes = generate_bytes([] {
return repeat( return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08), make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
8193); 8193);
@ -485,7 +477,7 @@ suite reader_tests = [] {
constexpr auto bytes_size = host_to_be(std::uint32_t(bytes.size())); constexpr auto bytes_size = host_to_be(std::uint32_t(bytes.size()));
constexpr auto payload = constexpr auto payload =
cat(make_bytes(0xc6), as_bytes(bytes_size), bytes); cat(make_bytes(0xc6), as_bytes(bytes_size), bytes);
// Test incomplete message. // Test incomplete message.
expect(test_incomplete_message(payload)); expect(test_incomplete_message(payload));
@ -508,7 +500,7 @@ suite reader_tests = [] {
"read format::fixarray"_test = [] { "read format::fixarray"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers. // A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = make_bytes(0x95, 0xcc, 0x85, 0xcc, 0x84, 0xcc, constexpr auto payload = make_bytes(0x95, 0xcc, 0x85, 0xcc, 0x84, 0xcc,
0x83, 0xcc, 0x82, 0xcc, 0x81); 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();
@ -517,16 +509,16 @@ suite reader_tests = [] {
expect(array && array->count == 5); expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) { for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one(); auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i); expect(v && v->get<std::uint8_t>() == 0x85 - i);
} }
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"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();
@ -535,16 +527,16 @@ suite reader_tests = [] {
expect(array && array->count == 5); expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) { for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one(); auto v = reader.read_one();
expect(v && v == 0x85u - i); expect(v && v == 0x85u - i);
} }
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
}; };
"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();
@ -553,8 +545,8 @@ suite reader_tests = [] {
expect(array && array->count == 5); expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) { for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one(); auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i); expect(v && v->get<std::uint8_t>() == 0x85 - i);
} }
expect(test_end_of_message(reader)); expect(test_end_of_message(reader));
@ -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 {
@ -18,17 +19,17 @@ constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
} }
template <typename... Bytes> template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) { 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;
std::copy(std::begin(container), std::end(container), std::begin(arr.data)); std::copy(std::begin(container), std::end(container), std::begin(arr.data));
@ -51,8 +52,8 @@ consteval auto build_string(auto callable) {
return string_array; return string_array;
} }
template <std::size_t ... Sizes> template <std::size_t... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept { constexpr auto cat(std::array<std::byte, Sizes> const&... a) noexcept {
std::array<std::byte, (Sizes + ...)> out; std::array<std::byte, (Sizes + ...)> out;
std::size_t index{}; std::size_t index{};
((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...); ((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...);
@ -89,14 +90,14 @@ constexpr auto from_string_view(std::string_view sv) {
} }
template <typename T, typename E> template <typename T, typename E>
std::ostream &operator<<(std::ostream &os, tl::expected<T, E> const &exp) { std::ostream& operator<<(std::ostream& os, tl::expected<T, E> const& exp) {
if (exp.has_value()) { if (exp.has_value()) {
os << "Value: '" << *exp << "'"; os << "Value: '" << *exp << "'";
} else { } else {
os << "Error"; os << "Error";
}
return os;
} }
return os;
}
bool test_incomplete_message(auto const& payload) { bool test_incomplete_message(auto const& payload) {
// Test incomplete message. // Test incomplete message.
@ -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),
@ -201,7 +208,7 @@ suite views_tests = [] {
make_bytes(0xa4), make_bytes(0xa4),
generate_bytes([] { return from_string_view(strings[6]); }) generate_bytes([] { return from_string_view(strings[6]); })
//make_bytes(0x05) // make_bytes(0x05)
); );
std::array<msgpack::token, 32> tokens; std::array<msgpack::token, 32> tokens;

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,85 +1,87 @@
#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;
namespace { namespace {
template <typename... Bytes> template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) { 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>
std::array<T, C> data; struct oversized_array {
std::size_t size; std::array<T, C> data;
std::size_t size;
}; };
constexpr auto to_bytes_array_oversized(auto const &container) { constexpr auto to_bytes_array_oversized(auto const& container) {
oversized_array<std::byte> arr; oversized_array<std::byte> arr;
std::copy(std::begin(container), std::end(container), std::begin(arr.data)); std::copy(std::begin(container), std::end(container), std::begin(arr.data));
arr.size = std::distance(std::begin(container), std::end(container)); arr.size = std::distance(std::begin(container), std::end(container));
return arr; return arr;
} }
consteval auto generate_bytes(auto callable) { consteval auto generate_bytes(auto callable) {
constexpr auto oversized = to_bytes_array_oversized(callable()); constexpr auto oversized = to_bytes_array_oversized(callable());
std::array<std::byte, oversized.size> out; std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data), std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size), std::next(std::begin(oversized.data), oversized.size),
std::begin(out)); std::begin(out));
return out; return out;
} }
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));
std::copy(std::begin(b), std::next(std::begin(b), std::size(b)), std::copy(std::begin(b), std::next(std::begin(b), std::size(b)),
std::next(std::begin(out), std::size(a))); std::next(std::begin(out), std::size(a)));
return out; return out;
} }
template <typename T, typename Itr> template <typename T, typename Itr>
constexpr auto zip(T val, Itr begin, Itr end) { constexpr auto zip(T val, Itr begin, Itr end) {
std::vector<T> output; std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) { for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val); output.emplace_back(val);
output.emplace_back(*itr); output.emplace_back(*itr);
} }
return output; return output;
} }
template <typename T, typename Iterable> template <typename T, typename Iterable>
constexpr auto zip(T val, Iterable const &itr) { constexpr auto zip(T val, Iterable const& itr) {
return zip(val, std::begin(itr), std::end(itr)); return zip(val, std::begin(itr), std::end(itr));
} }
constexpr auto from_string_view(std::string_view sv) { constexpr auto from_string_view(std::string_view sv) {
std::vector<std::byte> range; std::vector<std::byte> range;
range.resize(sv.size()); range.resize(sv.size());
auto itr = range.begin(); auto itr = range.begin();
for (auto c : sv) { for (auto c : sv) {
*itr = std::byte(c); *itr = std::byte(c);
++itr; ++itr;
} }
return range; return range;
} }
constexpr auto make_contiguous_range(std::uint8_t start, std::uint8_t end) { constexpr auto make_contiguous_range(std::uint8_t start, std::uint8_t end) {
auto count = std::size_t(end) - std::size_t(start) + 1; auto count = std::size_t(end) - std::size_t(start) + 1;
std::vector<std::byte> range; std::vector<std::byte> range;
range.resize(count); range.resize(count);
for (auto i = std::size_t(start); i <= std::size_t(end); ++i) { for (auto i = std::size_t(start); i <= std::size_t(end); ++i) {
range[i - std::size_t(start)] = std::byte(i); range[i - std::size_t(start)] = std::byte(i);
} }
return range; return range;
} }
constexpr auto equal(auto a, auto b) { constexpr auto equal(auto a, auto b) {
@ -89,162 +91,176 @@ constexpr auto equal(auto a, auto b) {
} // namespace } // namespace
suite writer = [] { suite writer = [] {
"writer empty span"_test = [] { "writer empty span"_test = [] {
std::array<std::byte, 16> payload; std::array<std::byte, 16> payload;
msgpack::writer writer(payload); msgpack::writer writer(payload);
expect(writer.tell() == 0); expect(writer.tell() == 0);
}; };
"writer::write<format::positive_fixint>"_test = [] { "writer::write<format::positive_fixint>"_test = [] {
using fmt = format::positive_fixint; using fmt = format::positive_fixint;
using error = msgpack::error; using error = msgpack::error;
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() == *expected.begin());
expect(*writer.subspan().begin() == std::byte{0x32}); expect(writer.write<fmt>(std::uint8_t{0x82})
expect(writer.write<fmt>(std::uint8_t{0x82}) == tl::make_unexpected(error::bad_value)); == tl::make_unexpected(error::bad_value));
expect(!!writer.write<fmt>(std::uint8_t{0x55}));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected));
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(std::uint8_t{0x55}); "writer::write<format::uint8>"_test = [] {
expect(writer.tell() == 2); using fmt = format::uint8;
expect(equal(writer.subspan(), expected)); using error = msgpack::error;
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::uint8>"_test = [] { std::array<std::byte, 4> payload;
using fmt = format::uint8; auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82);
using error = msgpack::error; msgpack::writer writer(payload);
auto result = writer.write<fmt>(std::uint8_t{0x32});
expect(!!result);
expect(writer.tell() == 2);
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::uint8_t{0x82}));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(std::uint8_t{0x01})
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 4> payload; "writer::write<format::uint16>"_test = [] {
auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82); using fmt = format::uint16;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(std::uint8_t{0x32}));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::uint8_t{0x82}));
expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::uint16>"_test = [] { std::array<std::byte, 6> payload;
using fmt = format::uint16; auto constexpr expected =
using error = msgpack::error; make_bytes(0xcd, 0x32, 0xcc, 0xcd, 0xaa, 0xff);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::uint16_t{0x32cc}));
expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(0xaaff));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 6> payload; "writer::write<format::uint32>"_test = [] {
auto constexpr expected = make_bytes(0xcd, 0x32, 0xcc, 0xcd, 0xaa, 0xff); using fmt = format::uint32;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(std::uint16_t{0x32cc}));
expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(0xaaff));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::uint32>"_test = [] { std::array<std::byte, 5> payload;
using fmt = format::uint32; auto constexpr expected = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04);
using error = msgpack::error; msgpack::writer writer(payload);
expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 5> payload; "writer::write<format::uint64>"_test = [] {
auto constexpr expected = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04); using fmt = format::uint64;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::uint64>"_test = [] { std::array<std::byte, 9> payload;
using fmt = format::uint64; auto constexpr expected = make_bytes(
using error = msgpack::error; 0xcf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(0x0102030405060708));
expect(writer.tell() == 9);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 9> payload; "writer::write<format::negative_fixint>"_test = [] {
auto constexpr expected = make_bytes( using fmt = format::negative_fixint;
0xcf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); using error = msgpack::error;
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(0x0102030405060708));
expect(writer.tell() == 9);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::negative_fixint>"_test = [] { std::array<std::byte, 2> payload;
using fmt = format::negative_fixint; auto constexpr expected = make_bytes(0xff, 0xe0);
using error = msgpack::error; msgpack::writer writer(payload);
expect(!!writer.write<fmt>(-1));
expect(writer.tell() == 1);
expect(*writer.subspan().begin() == std::byte{0xff});
expect(writer.write<fmt>(-33) == tl::make_unexpected(error::bad_value));
expect(!!writer.write<fmt>(-32));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(-5)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 2> payload; "writer::write<format::int8>"_test = [] {
auto constexpr expected = make_bytes(0xff, 0xe0); using fmt = format::int8;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(-1));
expect(writer.tell() == 1);
expect(*writer.subspan().begin() == std::byte{0xff});
expect(writer.write<fmt>(-33) == tl::make_unexpected(error::bad_value));
expect(!!writer.write<fmt>(-32));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(-5) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::int8>"_test = [] { std::array<std::byte, 4> payload;
using fmt = format::int8; auto constexpr expected = make_bytes(0xd0, 0x32, 0xd0, 0xfb);
using error = msgpack::error; msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::int8_t{0x32}));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::int8_t{-5}));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 4> payload; "writer::write<format::int16>"_test = [] {
auto constexpr expected = make_bytes(0xd0, 0x32, 0xd0, 0xfb); using fmt = format::int16;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(std::int8_t{0x32}));
expect(writer.tell() == 2);
expect(equal(writer.subspan(), std::span{expected.begin(), 2}));
expect(!!writer.write<fmt>(std::int8_t{-5}));
expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::int16>"_test = [] { std::array<std::byte, 6> payload;
using fmt = format::int16; auto constexpr expected =
using error = msgpack::error; make_bytes(0xd1, 0x32, 0xcc, 0xd1, 0xff, 0xfa);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::int16_t{0x32cc}));
expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(-6));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 6> payload; "writer::write<format::int32>"_test = [] {
auto constexpr expected = make_bytes(0xd1, 0x32, 0xcc, 0xd1, 0xff, 0xfa); using fmt = format::int32;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(std::int16_t{0x32cc}));
expect(writer.tell() == 3);
expect(equal(writer.subspan(), std::span{expected.begin(), 3}));
expect(!!writer.write<fmt>(-6));
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::int32>"_test = [] { std::array<std::byte, 5> payload;
using fmt = format::int32; auto constexpr expected = make_bytes(0xd2, 0x01, 0x02, 0x03, 0x04);
using error = msgpack::error; msgpack::writer writer(payload);
expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01)
== tl::make_unexpected(error::out_of_space));
};
std::array<std::byte, 5> payload; "writer::write<format::int64>"_test = [] {
auto constexpr expected = make_bytes(0xd2, 0x01, 0x02, 0x03, 0x04); using fmt = format::int64;
msgpack::writer writer(payload); using error = msgpack::error;
expect(!!writer.write<fmt>(0x01020304));
expect(writer.tell() == 5);
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::int64>"_test = [] { std::array<std::byte, 9> payload;
using fmt = format::int64; auto constexpr expected = make_bytes(
using error = msgpack::error; 0xd3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
msgpack::writer writer(payload);
std::array<std::byte, 9> payload; expect(!!writer.write<fmt>(std::int64_t{0x0102030405060708}));
auto constexpr expected = make_bytes( expect(writer.tell() == 9);
0xd3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); expect(equal(writer.subspan(), expected));
msgpack::writer writer(payload); expect(writer.write<fmt>(0x01)
expect(!!writer.write<fmt>(std::int64_t{0x0102030405060708})); == tl::make_unexpected(error::out_of_space));
expect(writer.tell() == 9); };
expect(equal(writer.subspan(), expected));
expect(writer.write<fmt>(0x01) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::fixstr>"_test = [] { "writer::write<format::fixstr>"_test = [] {
using fmt = format::fixstr; using fmt = format::fixstr;
@ -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));
}
};
};