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")
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
@ -24,7 +25,7 @@ boost_deps()
#-------------------------------------------------------------------------------
# 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 = \
"https://github.com/Neargye/magic_enum/archive/refs/tags/v"
magic_enum_sha256 = \
@ -32,7 +33,7 @@ magic_enum_sha256 = \
http_archive(
name = "magic_enum",
sha256 = magic_enum_sha256,
# sha256 = magic_enum_sha256,
url = magic_enum_base_url + magic_enum_version + ".zip",
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?
#-------------------------------------------------------------------------------
ut_version = "1.1.9"
@ -100,6 +124,22 @@ cc_library(
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.
#-------------------------------------------------------------------------------

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

View File

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

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -14,21 +14,20 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef logging_982a89e400976f59
#define logging_982a89e400976f59
#include "logging/formatters.h"
#include "logging/theme.h"
#include "logging/traits.h"
#include <chrono>
#include <memory>
#include <span>
#include <string_view>
#include <vector>
#include "logging/formatters.h"
#include "logging/theme.h"
#include "logging/traits.h"
namespace parselink {
namespace logging {
@ -51,7 +50,9 @@ struct message {
struct endpoint {
virtual ~endpoint() = default;
virtual std::span<char> buffer() { return {}; }
virtual void write(message const& msg) = 0;
virtual bool colored() const noexcept = 0;
level threshold{default_threshold};
@ -64,27 +65,28 @@ public:
// This constructor allows for an arbitrary number of logging endpoints
// to be passed in.
logger(std::string_view name,
std::vector<std::shared_ptr<endpoint>> eps);
logger(std::string_view name, std::vector<std::shared_ptr<endpoint>> eps);
template <typename... Endpoints>
explicit logger(std::string_view name, Endpoints&&... eps)
: logger(name, {std::forward<Endpoints>(eps)...}) {}
template<level Level, typename... Args>
requires (Level <= static_threshold)
template <level Level, typename... Args>
requires(Level <= static_threshold)
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>
requires (Level > static_threshold)
template <level Level, typename... Args>
requires(Level > static_threshold)
[[gnu::flatten]] void log(fmt::format_string<Args...>, Args&&...) const {}
#define LOG_API(lvl) \
template <typename... Args> \
[[gnu::always_inline]] void lvl(fmt::format_string<Args...>&& format, Args&&... args) const { \
log<level::lvl>(std::forward<decltype(format)>(format), std::forward<Args>(args)...); \
#define LOG_API(lvl) \
template <typename... Args> \
[[gnu::always_inline]] void lvl( \
fmt::format_string<Args...>&& format, Args&&... args) const { \
log<level::lvl>(std::forward<decltype(format)>(format), \
std::forward<Args>(args)...); \
}
LOG_API(critical);
@ -100,8 +102,7 @@ public:
void set_threshold(level new_threshold) noexcept;
private:
template<typename... Args>
template <typename... Args>
void write_endpoint(std::shared_ptr<endpoint> const& endpoint, message msg,
fmt::string_view format, Args&&... args) const {
auto buff = endpoint->buffer();
@ -114,16 +115,15 @@ private:
endpoint->write(msg);
} else {
// Fill the static buffer.
fmt::vformat_to(buff.begin(), format,
fmt::make_format_args(args...));
fmt::vformat_to(
buff.begin(), format, fmt::make_format_args(args...));
msg.message = std::string_view{buff.data(), buff.size()};
endpoint->write(msg);
}
}
template<typename... Args>
void try_write(level level, fmt::string_view format,
Args&&... args) const {
template <typename... Args>
void do_write(level level, fmt::string_view format, Args&&... args) const {
message msg{level, std::chrono::system_clock::now(), name_};
for (auto const& ep : endpoints_) {
if (level > ep->threshold) continue;

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -15,23 +15,22 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef logging_formatters_d22a64b1645a8134
#define logging_formatters_d22a64b1645a8134
#include "traits.h"
#include "theme.h"
#include "traits.h"
#include <magic_enum.hpp>
#include <tl/expected.hpp>
#include <magic_enum.hpp>
// Simple wrapper for error strings to be formatted like any other string_view.
template <>
struct fmt::formatter<parselink::logging::error_str>
: fmt::formatter<std::string_view> {
template<typename FormatContext>
template <typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const {
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"
template <typename E>
requires std::is_enum_v<E>
requires std::is_enum_v<E>
struct fmt::formatter<E> : fmt::formatter<std::string_view> {
template<typename FormatContext>
template <typename FormatContext>
auto format(E const v, FormatContext& ctx) const {
auto str = [v]{
return fmt::format("{}::{}",
magic_enum::enum_type_name<E>(),
magic_enum::enum_name(v));
auto str = [v] {
return fmt::format("{}::{}", magic_enum::enum_type_name<E>(),
magic_enum::enum_name(v));
}();
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"
template <typename E>
requires std::is_enum_v<E>
requires std::is_enum_v<E>
struct fmt::formatter<parselink::logging::enum_name_only<E>>
: fmt::formatter<std::string_view> {
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 {
return fmt::formatter<std::string_view>::format(
magic_enum::enum_name(v.v), ctx);
@ -79,7 +78,7 @@ struct fmt::formatter<parselink::logging::enum_name_only<E>>
template <>
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 {
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.
template <>
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 {
return fmt::formatter<std::error_code>::format(
std::make_error_code(v), ctx);
@ -104,7 +103,7 @@ struct fmt::formatter<std::byte> {
return ctx.begin();
}
template<typename FormatContext>
template <typename FormatContext>
auto format(std::byte const v, FormatContext& ctx) const {
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
template <parselink::logging::detail::printable_pointer T>
struct fmt::formatter<T> : fmt::formatter<void const*> {
template<typename FormatContext>
template <typename FormatContext>
auto format(T const& v, FormatContext& ctx) const {
return fmt::formatter<void const*>::format(fmt::ptr(v), ctx);
}
};
// TODO(ksassenrath): Re-enable when expected has been integrated
template <typename T, typename Err>
struct fmt::formatter<tl::expected<T, Err>> {
template <typename ParseContext>
@ -127,7 +125,7 @@ struct fmt::formatter<tl::expected<T, Err>> {
return ctx.begin();
}
template<typename FormatContext>
template <typename FormatContext>
auto format(tl::expected<T, Err> const& v, FormatContext& ctx) const {
if (v) {
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.
template <typename T>
struct fmt::formatter<parselink::logging::format_arg<T>> :
fmt::formatter<typename parselink::logging::format_arg<T>::type> {
struct fmt::formatter<parselink::logging::format_arg<T>>
: fmt::formatter<typename parselink::logging::format_arg<T>::type> {
using format_arg_type = parselink::logging::format_arg<T>;
using resolved_type = typename format_arg_type::type;

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -16,7 +16,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef level_9f090ff308e53a57
#define level_9f090ff308e53a57
@ -25,14 +25,14 @@ namespace parselink {
namespace logging {
enum class level {
silent, // "Virtual" level used to suppress all logging output.
critical, // Indicates a fatal error occurred. Crash likely.
error, // Indicates a non-fatal error occurred.
warning, // Indicates potentially incorrect/unintentional behavior.
info, // Indicates general information.
verbose, // Noisier/potentially unimportant information.
debug, // Information intended for debugging purposes only.
trace // Tracer-like levels of verbosity may impact performance.
silent, // "Virtual" level used to suppress all logging output.
critical, // Indicates a fatal error occurred. Crash likely.
error, // Indicates a non-fatal error occurred.
warning, // Indicates potentially incorrect/unintentional behavior.
info, // Indicates general information.
verbose, // Noisier/potentially unimportant information.
debug, // Information intended for debugging purposes only.
trace // Tracer-like levels of verbosity may impact performance.
};
} // namespace logging

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -24,42 +24,51 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef log_theme_8e9601cc066b20bf
#define log_theme_8e9601cc066b20bf
#include "level.h"
#include "traits.h"
#include <tl/expected.hpp>
#include <fmt/color.h>
#include <span>
#include <system_error>
#include <type_traits>
#include <fmt/chrono.h>
#include <fmt/color.h>
#include <tl/expected.hpp>
namespace parselink {
namespace logging {
template <typename T>
struct format_arg{
struct format_arg {
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;
};
template <std::convertible_to<std::string_view> T>
struct format_arg<T> {
using type = std::string_view;
constexpr format_arg(T&& t) noexcept : v(t) {}
constexpr format_arg(T&& t) noexcept
: v(t) {}
type v;
};
template <detail::smart_pointer T>
struct format_arg<T> {
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;
};
@ -72,7 +81,7 @@ struct static_theme {
};
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> {};
template <>
@ -85,7 +94,7 @@ template <detail::printable_pointer T>
struct theme<T> : static_theme<fmt::color::pale_violet_red> {};
template <typename E>
requires std::is_enum_v<E>
requires std::is_enum_v<E>
struct theme<E> : static_theme<fmt::color::gray> {};
// Errors
@ -98,7 +107,6 @@ struct theme<std::errc> : static_theme<fmt::color::fire_brick> {};
template <>
struct theme<error_str> : static_theme<fmt::color::fire_brick> {};
template <>
struct theme<bool> {
static constexpr auto style(bool l) noexcept {
@ -108,37 +116,40 @@ struct theme<bool> {
template <>
struct theme<enum_name_only<logging::level>> {
// clang-format off
constexpr static fmt::color colors[] = {
fmt::color::black,
fmt::color::red,
fmt::color::fire_brick,
fmt::color::golden_rod,
fmt::color::light_sky_blue,
fmt::color::lime_green,
fmt::color::pink,
fmt::color::slate_gray
};
fmt::color::black,
fmt::color::red,
fmt::color::fire_brick,
fmt::color::golden_rod,
fmt::color::light_sky_blue,
fmt::color::lime_green,
fmt::color::pink,
fmt::color::slate_gray };
// clang-format on
static constexpr auto style(auto l) noexcept {
return fmt::fg(*std::next(std::begin(colors), size_t(l.v)));
}
};
template <typename T>
concept has_static_theme =
std::convertible_to<fmt::text_style,
decltype(theme<std::remove_cvref_t<T>>::style)>;
concept has_static_theme = std::convertible_to<fmt::text_style,
decltype(theme<std::remove_cvref_t<T>>::style)>;
template <typename T>
concept has_dynamic_theme = requires (T const& t) {
{ theme<std::remove_cvref_t<T>>::style(t) }
-> std::convertible_to<fmt::text_style>;
concept has_dynamic_theme = requires(T const& t) {
{
theme<std::remove_cvref_t<T>>::style(t)
} -> std::convertible_to<fmt::text_style>;
};
template <typename T>
concept has_theme = has_static_theme<T> || has_dynamic_theme<T>;
template <typename T>
requires (has_theme<T>)
requires(has_theme<T>)
struct theme<std::span<T>> : theme<T> {};
template <typename T>
@ -156,12 +167,12 @@ constexpr auto get_theme(T const& value) {
return theme<T>::style(value);
}
[[gnu::always_inline]] constexpr auto styled(fmt::text_style const& style,
auto out) {
[[gnu::always_inline]] constexpr auto styled(
fmt::text_style const& style, auto out) {
if (style.has_foreground()) {
auto foreground =
fmt::detail::make_foreground_color<char>(style.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
auto foreground = fmt::detail::make_foreground_color<char>(
style.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
}
return out;
}

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -14,13 +14,13 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef logging_traits_34e410874c0179c6
#define logging_traits_34e410874c0179c6
#include <type_traits>
#include <memory>
#include <type_traits>
namespace parselink {
namespace logging {
@ -33,7 +33,7 @@ namespace logging {
// logger.log<...>("value: {}", v); // logs "value: Foo::Bar"
// logger.log<...>("value: {}", enum_name_only{v}); // logs "value: Bar"
template <typename E>
requires std::is_enum_v<E>
requires std::is_enum_v<E>
struct enum_name_only {
E v;
};
@ -45,7 +45,9 @@ enum_name_only(E) -> enum_name_only<E>;
// a normal string.
struct error_str {
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;
};

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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -14,7 +14,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_error_3fae420b0427164e
#define msgpack_error_3fae420b0427164e
@ -29,6 +29,7 @@ enum class error {
wrong_type, // The format is incompatible with requested type.
bad_value, // Value does not fit within constraints.
will_truncate, // Integer return type is smaller than stored value.
wrong_length, // Variable-length format does not match desired length.
};
} // namespace msgpack

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -14,7 +14,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_format_849b5c5238d8212
#define msgpack_format_849b5c5238d8212
@ -27,327 +27,366 @@
namespace msgpack {
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Supporting types/tags for formats
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
struct nil {
nil() = default;
// This constructor is used by the reader implementation.
nil(auto) {};
constexpr nil() noexcept = default;
constexpr bool operator==(nil const& other) const noexcept = default;
};
struct invalid {
constexpr invalid() noexcept = default;
constexpr bool operator==(invalid const& other) const noexcept = default;
};
struct invalid {};
using boolean = bool;
struct array_desc {
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
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 {
return count == other.count;
}
std::size_t count = 0;
};
struct map_desc {
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
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;
};
namespace format {
// Classification of the format type. Used by the token API to distinguish
// different formats.
enum class type : std::uint8_t {
invalid,
unsigned_int,
signed_int,
string,
binary,
nil,
boolean,
array,
map,
array_view,
map_view,
};
// Classification of the format type. Used by the token API to distinguish
// different formats.
enum class type : std::uint8_t {
invalid,
unsigned_int,
signed_int,
string,
binary,
nil,
boolean,
array,
map,
array_view,
map_view,
};
// Flags that may control the behavior of readers/writers.
enum flag : std::uint8_t {
apply_mask = 1,
fixed_size = 2,
};
// Flags that may control the behavior of readers/writers.
enum flag : std::uint8_t {
apply_mask = 1,
fixed_size = 2,
};
// MessagePack formats break down into one of the following schemes:
// Marker byte + Fixed-length value
// Marker byte + Fixed-length payload length + payload
//
// In addition, there are "fix" types that embed the literal value or the
// payload length within the marker byte.
enum payload : std::uint8_t {
fixed,
variable
};
// MessagePack formats break down into one of the following schemes:
// Marker byte + Fixed-length value
// Marker byte + Fixed-length payload length + payload
//
// In addition, there are "fix" types that embed the literal value or the
// payload length within the marker byte.
enum payload : std::uint8_t { fixed, variable };
// Base structure for all MessagePack formats to inherit from.
struct base_format {
constexpr static flag flags = {};
};
// Base structure for all MessagePack formats to inherit from.
struct base_format {
constexpr static flag flags = {};
};
// Traits describing a particular format.
struct traits {
std::uint8_t flags{};
std::uint8_t size{};
std::byte mask{};
type token_type{};
constexpr auto operator<=>(traits const&) const noexcept = default;
};
// Traits describing a particular format.
struct traits {
std::uint8_t flags{};
std::uint8_t size{};
std::byte mask{};
type token_type{};
constexpr auto operator<=>(traits const&) const noexcept = default;
};
// "Sentinel" traits instance indicating no trait found
constexpr static traits no_traits {};
// "Sentinel" traits instance indicating no trait found
constexpr static traits no_traits{};
struct fixtype_format : base_format {};
struct fixtype_format : base_format {};
template <class>
struct resolve_token_type {
constexpr static type value = type::invalid;
};
template <class>
struct resolve_token_type {
constexpr static type value = type::invalid;
};
template <std::integral T>
struct resolve_token_type<T> {
constexpr static type value = std::is_signed_v<T> ?
type::signed_int : type::unsigned_int;
};
template <std::integral T>
struct resolve_token_type<T> {
constexpr static type value =
std::is_signed_v<T> ? type::signed_int : type::unsigned_int;
};
template <>
struct resolve_token_type<std::string_view> {
constexpr static type value = type::string;
};
template <>
struct resolve_token_type<std::string_view> {
constexpr static type value = type::string;
};
template <>
struct resolve_token_type<std::span<std::byte const>> {
constexpr static type value = type::binary;
};
template <>
struct resolve_token_type<std::span<std::byte const>> {
constexpr static type value = type::binary;
};
template <>
struct resolve_token_type<bool> {
constexpr static type value = type::boolean;
};
template <>
struct resolve_token_type<bool> {
constexpr static type value = type::boolean;
};
template <>
struct resolve_token_type<nil> {
constexpr static type value = type::nil;
};
template <>
struct resolve_token_type<nil> {
constexpr static type value = type::nil;
};
template <>
struct resolve_token_type<invalid> {
constexpr static type value = type::invalid;
};
template <>
struct resolve_token_type<invalid> {
constexpr static type value = type::invalid;
};
template <>
struct resolve_token_type<array_desc> {
constexpr static type value = type::array;
};
template <>
struct resolve_token_type<array_desc> {
constexpr static type value = type::array;
};
template <>
struct resolve_token_type<map_desc> {
constexpr static type value = type::map;
};
template <>
struct resolve_token_type<map_desc> {
constexpr static type value = type::map;
};
template <typename T>
constexpr static auto resolve_type_v = resolve_token_type<T>::value;
template <typename T>
constexpr static auto resolve_type_v = resolve_token_type<T>::value;
// The library's representation of certain data types does not always
// match the underlying data serialized in the message. For example,
// arrays and maps are encoded as a marker byte + fixed length value, but
// msgpack will present them wrapped in a structure for stronger type
// semantics.
// The library's representation of certain data types does not always
// match the underlying data serialized in the message. For example,
// arrays and maps are encoded as a marker byte + fixed length value, but
// msgpack will present them wrapped in a structure for stronger type
// semantics.
template <std::uint8_t Marker, typename First, typename Value = First>
struct format : base_format {
using first_type = First;
using value_type = Value; // Can be overridden
constexpr static std::byte marker{Marker};
constexpr static payload payload_type{payload::fixed};
constexpr static std::uint8_t flags{};
};
template <std::uint8_t Marker, typename First, typename Value = First>
struct format : base_format {
using first_type = First;
using value_type = Value; // Can be overridden
constexpr static std::byte marker{Marker};
constexpr static payload payload_type{payload::fixed};
constexpr static std::uint8_t flags{};
};
template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t>
struct fixtype : fixtype_format {
using first_type = First;
using value_type = first_type; // Can be overridden
constexpr static std::byte marker{Marker};
constexpr static std::byte mask{Mask};
constexpr static payload payload_type{payload::fixed};
constexpr static auto flags{flag::apply_mask};
};
template <std::uint8_t Marker, std::uint8_t Mask, typename First = std::uint8_t>
struct fixtype : fixtype_format {
using first_type = First;
using value_type = first_type; // Can be overridden
constexpr static std::byte marker{Marker};
constexpr static std::byte mask{Mask};
constexpr static payload payload_type{payload::fixed};
constexpr static auto flags{flag::apply_mask};
};
/*
* Integral types
*/
/*
* Integral types
*/
// Positive/negative fixint represent the literal value specified, do not
// need to mask it off.
struct positive_fixint : fixtype<0x00, 0x7f> {
constexpr static flag flags{};
};
struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
constexpr static flag flags{};
};
// Positive/negative fixint represent the literal value specified, do not
// need to mask it off.
struct positive_fixint : fixtype<0x00, 0x7f> {
constexpr static flag flags{};
};
struct uint8 : format<0xcc, std::uint8_t> {};
struct uint16 : format<0xcd, std::uint16_t> {};
struct uint32 : format<0xce, std::uint32_t> {};
struct uint64 : format<0xcf, std::uint64_t> {};
struct negative_fixint : fixtype<0xe0, 0x1f, std::int8_t> {
constexpr static flag flags{};
};
struct int8 : format<0xd0, std::int8_t> {};
struct int16 : format<0xd1, std::int16_t> {};
struct int32 : format<0xd2, std::int32_t> {};
struct int64 : format<0xd3, std::int64_t> {};
struct uint8 : format<0xcc, std::uint8_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 uint16 : format<0xcd, std::uint16_t> {};
struct invalid : fixtype<0xc1, 0x00> {
using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct uint32 : format<0xce, std::uint32_t> {};
struct boolean : fixtype<0xc2, 0x01> {
using value_type = bool;
constexpr static flag flags{flag::fixed_size | flag::apply_mask};
};
struct uint64 : format<0xcf, std::uint64_t> {};
/*
* Maps
*/
struct int8 : format<0xd0, std::int8_t> {};
template <typename Fmt> struct map_format : Fmt {
using value_type = map_desc;
};
struct int16 : format<0xd1, std::int16_t> {};
struct fixmap : map_format<fixtype<0x80, 0x0f>> {};
struct map16 : map_format<format<0xde, std::uint16_t>> {};
struct map32 : map_format<format<0xdf, std::uint32_t>> {};
struct int32 : format<0xd2, std::int32_t> {};
/*
* Arrays
*/
struct int64 : format<0xd3, std::int64_t> {};
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 array16 : array_format<format<0xdc, std::uint16_t>> {};
struct array32 : array_format<format<0xdd, std::uint32_t>> {};
struct invalid : fixtype<0xc1, 0x00> {
using value_type = msgpack::invalid;
constexpr static flag flags{flag::fixed_size};
};
/*
* Strings
*/
struct boolean : fixtype<0xc2, 0x01> {
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;
constexpr static auto payload_type = payload::variable;
};
/*
* Maps
*/
template <typename Fmt>
struct map_format : Fmt {
using value_type = map_desc;
};
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>> {};
struct fixmap : map_format<fixtype<0x80, 0x0f>> {};
/*
* Binary arrays
*/
struct map16 : map_format<format<0xde, std::uint16_t>> {};
template <typename Fmt> struct bin_format : Fmt {
using value_type = std::span<std::byte const>;
constexpr static auto payload_type = payload::variable;
};
struct map32 : map_format<format<0xdf, std::uint32_t>> {};
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>> {};
/*
* Arrays
*/
/*
* Extension types, not yet supported.
*/
template <typename Fmt>
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 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>> {};
struct array16 : array_format<format<0xdc, std::uint16_t>> {};
template <typename T>
struct unimplemented : std::false_type {};
struct array32 : array_format<format<0xdd, std::uint32_t>> {};
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 {
// Simple typelist for looking up compatible types.
// TODO: Use traits to generate the compatibility list automatically?
template <std::size_t I, typename T>
struct type_list_entry { using type = T; };
// Simple typelist for looking up compatible types.
// TODO: Use traits to generate the compatibility list automatically?
template <std::size_t I, typename T>
struct type_list_entry {
using type = T;
};
template <typename...>
struct type_list_impl;
template <typename...>
struct type_list_impl;
template <std::size_t... Is, typename... Ts>
struct type_list_impl<std::index_sequence<Is...>, Ts...> :
type_list_entry<Is, Ts>... {
static constexpr auto size = sizeof...(Ts);
};
template <std::size_t... Is, typename... Ts>
struct type_list_impl<std::index_sequence<Is...>, Ts...>
: type_list_entry<Is, 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>
typelist_lookup_tag<T> typelist_lookup(type_list_entry<I, T> const&);
template <std::size_t I, typename T>
typelist_lookup_tag<T> typelist_lookup(type_list_entry<I, T> const&);
template <std::size_t I, typename List>
using type_list_at = typename decltype(
typelist_lookup<I>(std::declval<List>()))::type;
template <std::size_t I, typename List>
using type_list_at =
typename decltype(typelist_lookup<I>(std::declval<List>()))::type;
template <typename... Ts>
using type_list = detail::type_list_impl<
std::make_index_sequence<sizeof...(Ts)>, Ts...>;
template <typename... Ts>
using type_list =
detail::type_list_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;
template <typename, typename, template <typename> typename>
struct filter_type_list;
template <typename, typename, template <typename> typename>
struct filter_type_list;
template <typename... Ts, template <typename> typename Predicate>
struct filter_type_list<type_list<>, type_list<Ts...>, Predicate> {
using type = type_list<Ts...>;
};
template <typename... Ts, template <typename> typename Predicate>
struct filter_type_list<type_list<>, type_list<Ts...>, Predicate> {
using type = type_list<Ts...>;
};
template <typename Head, typename... Tail, typename... Ts, template <typename> typename Predicate>
struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> {
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...>, Predicate>>::type;
};
template <typename Head, typename... Tail, typename... Ts,
template <typename> typename Predicate>
struct filter_type_list<type_list<Head, Tail...>, type_list<Ts...>, Predicate> {
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...>,
Predicate>>::type;
};
template <typename List, template <typename> typename Predicate>
using filter = filter_type_list<List, type_list<>, Predicate>;
template <typename List, template <typename> typename Predicate>
using filter = filter_type_list<List, type_list<>, Predicate>;
template <typename List, template <typename> typename Predicate>
using filter_t = typename filter<List, Predicate>::type;
template <typename List, template <typename> typename Predicate>
using filter_t = typename filter<List, Predicate>::type;
} // namespace detail
} // namespace format
@ -358,35 +397,33 @@ template <typename T>
concept is_fixtype = std::is_base_of_v<format::fixtype_format, T>;
namespace format {
// This template instantiates a format::traits object for a given format.
// This should only occur once per format type, and should happen at
// compile time.
//
// This isn't the prettiest way to go about it.
template <format_type Format>
inline traits const& get_traits() noexcept {
constexpr static auto inst = []{
// Fixtypes define the size within the identifier byte, so the
// trait size must be zero.
auto size = is_fixtype<Format> ?
0 : sizeof(typename Format::first_type);
// This template instantiates a format::traits object for a given format.
// This should only occur once per format type, and should happen at
// compile time.
//
// This isn't the prettiest way to go about it.
template <format_type Format>
inline traits const& get_traits() noexcept {
constexpr static auto inst = [] {
// Fixtypes define the size within the identifier byte, so the
// trait size must be zero.
auto size =
is_fixtype<Format> ? 0 : sizeof(typename Format::first_type);
traits inst{
.flags = Format::flags,
traits inst{.flags = Format::flags,
.size = std::uint8_t(size),
.token_type = resolve_type_v<typename Format::value_type>
};
// Add in the fixed_size flag for integral types.
if constexpr (std::is_integral_v<typename Format::value_type>) {
inst.flags |= flag::fixed_size;
}
if constexpr (is_fixtype<Format>) {
inst.mask = Format::mask;
}
return inst;
}();
.token_type = resolve_type_v<typename Format::value_type>};
// Add in the fixed_size flag for integral types.
if constexpr (std::is_integral_v<typename Format::value_type>) {
inst.flags |= flag::fixed_size;
}
if constexpr (is_fixtype<Format>) {
inst.mask = Format::mask;
}
return inst;
};
}();
return inst;
};
} // namespace format
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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -14,13 +14,13 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_core_reader_6c2f66f02585565
#define msgpack_core_reader_6c2f66f02585565
#include "../util/endianness.h"
#include "error.h"
#include "format.h"
#include "../util/endianness.h"
#include <tl/expected.hpp>
@ -35,268 +35,265 @@ namespace msgpack {
*/
namespace reader_policy {
struct relaxed {}; // Similar formats are acceptable.
struct strict {}; // Formats must exactly match the caller's request.
} // namespace reader_policy
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;
// 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;
}
// 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.
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)));
}
auto f = read_integral<first_type>(cur);
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
f &= decltype(f)(F::mask);
}
// 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)}) {
if constexpr (F::payload_type == format::payload::fixed) {
// We've read all we need to read. Update itr and return the value.
itr = cur;
return value_type(f);
} 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 (std::distance(cur, end) < diff_type{f}) {
return tl::make_unexpected(error::incomplete_message);
}
auto f = read_integral<first_type>(cur);
if constexpr (is_fixtype<F> && (F::flags & format::flag::apply_mask)) {
f &= decltype(f)(F::mask);
}
itr = cur + f;
using adapt = iterator_adapter<value_type, Iter>;
return value_type(adapt::convert(cur), f);
}
}
if constexpr (F::payload_type == format::payload::fixed) {
// We've read all we need to read. Update itr and return the value.
itr = cur;
return value_type(f);
} 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 (std::distance(cur, end) < diff_type{f}) {
return tl::make_unexpected(error::incomplete_message);
}
// "Relaxed" reader policies allow for certain conversions to take place
// at runtime. For instance, any smaller type of the same category is
// allowed.
//
// 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.
itr = cur + f;
using adapt = iterator_adapter<value_type, Iter>;
return value_type(adapt::convert(cur), f);
}
template <format_type>
struct relaxed_conversions {};
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
// at runtime. For instance, any smaller type of the same category is
// allowed.
//
// 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 <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const end) noexcept {
return detail::read<F>(itr, end);
};
};
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);
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 <format_type F, typename Iter>
constexpr static decltype(auto) read(Iter& itr, Iter const end)
noexcept {
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);
};
};
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
template <typename policy = reader_policy::relaxed>
class reader {
public:
template <typename T>
using expected = detail::expected<T>;
constexpr reader(std::span<std::byte const> src) :
data(src), curr(std::begin(data)), end(std::end(data)) {}
constexpr reader(std::span<std::byte const> src)
: data(src)
, curr(std::begin(data))
, end(std::end(data)) {}
template <format_type F>
constexpr expected<typename F::value_type> read() noexcept {
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 {
return curr;
}
constexpr auto pos() const noexcept { return curr; }
constexpr auto subview() const noexcept {
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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -14,18 +14,18 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_core_writer_ce48a51aa6ed0858
#define msgpack_core_writer_ce48a51aa6ed0858
#include <tl/expected.hpp>
#include "../util/endianness.h"
#include "error.h"
#include "format.h"
#include "../util/endianness.h"
#include <limits>
#include <type_traits>
#include <tl/expected.hpp>
namespace msgpack {
enum class writer_error {
@ -37,36 +37,162 @@ enum class writer_error {
};
// 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 <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 {
constexpr inline decltype(auto) write_bytes(
std::array<std::byte, N>&& data, Itr out) noexcept {
for (auto b : data) {
*out++ = b;
}
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>
struct write_adapter {};
template <std::integral T>
struct write_adapter<T> {
static constexpr auto size(T) noexcept {
return sizeof(T);
}
static constexpr auto size(T 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);
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 <>
struct write_adapter<std::string_view> {
static constexpr auto size(std::string_view str) noexcept {
@ -75,9 +201,10 @@ struct write_adapter<std::string_view> {
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::byte const* beg =
reinterpret_cast<std::byte const*>(&*str.begin());
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>
static constexpr auto write(std::span<std::byte const> bytes,
Itr out) noexcept {
static constexpr auto write(
std::span<std::byte const> bytes, Itr out) noexcept {
std::copy(bytes.begin(), bytes.end(), out);
return out += bytes.size();
}
@ -97,144 +224,145 @@ struct write_adapter<std::span<std::byte const>> {
template <>
struct write_adapter<map_desc> {
static constexpr auto value(map_desc desc) noexcept {
return desc.count;
}
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;
}
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;
}
static constexpr auto value(invalid) noexcept { return 0; }
};
template <>
struct write_adapter<nil> {
static constexpr auto value(nil) noexcept {
return 0;
}
static constexpr auto value(nil) noexcept { return 0; }
};
namespace detail {
template <typename T>
using expected = tl::expected<T, error>;
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);
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;
if (!is_fixtype<F>) {
++size; // For format
}
// 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.
if constexpr (F::payload_type == format::payload::variable) {
size += write_adapter<typename F::value_type>::size(val);
}
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));
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 {
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);
}
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>
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);
}
*out++ = marker;
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;
if constexpr (!is_fixtype<F>) {
out = write_adapter<typename decltype(result)::value_type>::write(
*result, out);
}
template <typename T>
struct format_hint;
if constexpr (F::payload_type == format::payload::variable) {
out = write_adapter<typename F::value_type>::write(value, out);
}
template <>
struct format_hint<std::uint8_t> {
using type = format::positive_fixint;
};
return out;
}
template <>
struct format_hint<std::uint16_t> {
using type = format::uint16;
};
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)) {}
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 {
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());
@ -244,15 +372,23 @@ public:
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));
// Deduced-type write, automatically chooses smallest representative format
template <detail::v2_adapted T>
constexpr expected<tl::monostate> write2(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 {
return curr;
}
constexpr auto pos() const noexcept { return curr; }
constexpr auto tell() const noexcept {
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
#define msgpack_object_f1f3a9e5c8be6a11
#include "token/type.h"
#include "token/reader.h"
#include "token/type.h"
#include "token/views.h"
#endif // msgpack_object_f1f3a9e5c8be6a11

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -17,14 +17,14 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_token_reader_8daff350a0b1a519
#define msgpack_token_reader_8daff350a0b1a519
#include "type.h"
#include "../core/error.h"
#include "../util/endianness.h"
#include "../core/format.h"
#include "../util/endianness.h"
#include "type.h"
#include <tl/expected.hpp>
@ -44,8 +44,7 @@ constexpr std::int64_t sign_extend(std::size_t size, auto bytes) noexcept {
return {};
}
constexpr decltype(auto) read(std::size_t size, auto& inp)
noexcept {
constexpr decltype(auto) read(std::size_t size, auto& inp) noexcept {
std::array<std::byte, token::word_size> value{};
be_to_host(inp, inp + size, std::begin(value));
inp += size;
@ -65,7 +64,7 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
== format::negative_fixint::marker) {
return format::get_traits<format::negative_fixint>();
} else if ((id & ~format::positive_fixint::mask)
== format::positive_fixint::marker) {
== format::positive_fixint::marker) {
return format::get_traits<format::positive_fixint>();
} else if ((id & ~format::fixstr::mask) == format::fixstr::marker) {
return format::get_traits<format::fixstr>();
@ -76,8 +75,7 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
}
switch (id) {
case format::uint8::marker:
return format::get_traits<format::uint8>();
case format::uint8::marker: return format::get_traits<format::uint8>();
case format::uint16::marker:
return format::get_traits<format::uint16>();
case format::uint32::marker:
@ -85,47 +83,33 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
case format::uint64::marker:
return format::get_traits<format::uint64>();
case format::int8::marker:
return format::get_traits<format::int8>();
case format::int16::marker:
return format::get_traits<format::int16>();
case format::int32::marker:
return format::get_traits<format::int32>();
case format::int64::marker:
return format::get_traits<format::int64>();
case format::int8::marker: return format::get_traits<format::int8>();
case format::int16::marker: return format::get_traits<format::int16>();
case format::int32::marker: return format::get_traits<format::int32>();
case format::int64::marker: return format::get_traits<format::int64>();
case format::str8::marker:
return format::get_traits<format::str8>();
case format::str16::marker:
return format::get_traits<format::str16>();
case format::str32::marker:
return format::get_traits<format::str32>();
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::str8::marker: return format::get_traits<format::str8>();
case format::str16::marker: return format::get_traits<format::str16>();
case format::str32::marker: return format::get_traits<format::str32>();
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:
return format::get_traits<format::array16>();
case format::array32::marker:
return format::get_traits<format::array32>();
case format::map16::marker:
return format::get_traits<format::map16>();
case format::map32::marker:
return format::get_traits<format::map32>();
case format::map16::marker: return format::get_traits<format::map16>();
case format::map32::marker: return format::get_traits<format::map32>();
case format::nil::marker:
return format::get_traits<format::nil>();
case format::nil::marker: return format::get_traits<format::nil>();
case format::invalid::marker:
return format::get_traits<format::invalid>();
case format::boolean::marker:
case format::boolean::marker | std::byte{1}:
return format::get_traits<format::boolean>();
default:
break;
default: break;
}
return format::no_traits;
@ -135,26 +119,22 @@ constexpr inline format::traits const& traits_lookup(std::byte id) {
class token_reader {
public:
constexpr token_reader(std::span<std::byte const> src) noexcept :
data_(src), curr_{} {}
constexpr token_reader(std::span<std::byte const> src) noexcept
: data_(src)
, curr_{} {}
constexpr auto current() const noexcept {
return std::next(data_.begin(), curr_);
}
constexpr auto end() const noexcept {
return data_.end();
}
constexpr auto end() const noexcept { return data_.end(); }
constexpr auto remaining(auto itr) noexcept {
using dist_type = decltype(std::distance(itr, data_.end()));
return std::max(dist_type(0), std::distance(itr, data_.end()));
}
constexpr auto remaining() noexcept {
return remaining(current());
}
constexpr auto remaining() noexcept { return remaining(current()); }
// 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
@ -181,7 +161,7 @@ public:
}
// This is either the value of the format, or the size of the format.
auto first_bytes = [&]{
auto first_bytes = [&] {
if (traits.size) {
return detail::read(traits.size, curr);
} else {
@ -206,14 +186,11 @@ public:
case format::type::boolean:
tok = token{bool(first_bytes[0] & traits.mask)};
break;
case format::type::invalid:
tok = token{invalid{}};
break;
case format::type::nil:
tok = token{nil{}};
break;
case format::type::invalid: tok = token{invalid{}}; break;
case format::type::nil: tok = token{nil{}}; break;
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;
case format::type::signed_int: {
auto value = detail::sign_extend(traits.size, first_bytes);
@ -242,8 +219,7 @@ public:
tok = token{map_desc(var_size)};
break;
}
default:
return tl::make_unexpected(error::not_implemented);
default: return tl::make_unexpected(error::not_implemented);
}
curr_ = std::distance(data_.begin(), curr);
@ -261,9 +237,9 @@ public:
error err = error::end_of_message;
while (tok != token_buffer.end()) {
auto result = read_one().map([&tok](auto&& t){
*tok = t;
++tok;
auto result = read_one().map([&tok](auto&& t) {
*tok = t;
++tok;
});
if (!result) {
err = result.error();
@ -280,8 +256,7 @@ public:
}
template <typename T>
constexpr tl::expected<T, error> read() {
}
constexpr tl::expected<T, error> read() {}
private:
std::span<std::byte const> data_;

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -20,23 +20,22 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_token_type_f43c22522692063f
#define msgpack_token_type_f43c22522692063f
#include "../core/error.h"
#include "../core/format.h"
#include <string_view>
#include <tl/expected.hpp>
#include <limits>
#include <memory>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
#include <tl/expected.hpp>
namespace msgpack {
// This API is _currently_ optimizing on the fact that most desktop/server
@ -54,7 +53,8 @@ namespace msgpack {
// Of course, this means custom code!
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 {
public:
using size_type = Size;
@ -67,6 +67,7 @@ public:
// Constructors
constexpr size_and_enum() noexcept = default;
constexpr size_and_enum(size_type size, enum_type enum_value) noexcept {
set_both(size, enum_value);
}
@ -75,30 +76,34 @@ public:
constexpr auto get_size() const noexcept {
return static_cast<size_type>((value & size_mask) >> size_shift);
}
constexpr auto get_enum() const noexcept {
return static_cast<enum_type>((value & enum_mask));
}
constexpr auto rep() const noexcept { return value; }
// Mutators
constexpr auto set_size(size_type size) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) |
(value & enum_mask);
value = (static_cast<uintptr_t>(size) << size_shift)
| (value & enum_mask);
}
constexpr auto set_enum(enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(enum_value) & enum_mask) |
(value & size_mask);
value = (static_cast<uintptr_t>(enum_value) & enum_mask)
| (value & size_mask);
}
constexpr auto set_both(size_type size, enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) |
(static_cast<uintptr_t>(enum_value) & enum_mask);
value = (static_cast<uintptr_t>(size) << size_shift)
| (static_cast<uintptr_t>(enum_value) & enum_mask);
}
// Explicit conversion
constexpr explicit operator size_type() const noexcept {
return get_size();
}
constexpr explicit operator enum_type() const noexcept {
return get_enum();
}
@ -156,9 +161,10 @@ class token_base<8> {
public:
constexpr static std::size_t word_size = 8;
token_base() noexcept = default;
token_base(token_base const& other) noexcept
: value_(other.value_)
, size_and_type_(other.size_and_type_) {}
: value_(other.value_)
, size_and_type_(other.size_and_type_) {}
template <std::integral T>
explicit token_base(T value) noexcept {
@ -215,11 +221,12 @@ public:
template <typename T>
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 auto expected_type = std::is_same_v<T, bool> ?
format::type::boolean : std::is_signed_v<T> ?
format::type::signed_int : format::type::unsigned_int;
constexpr auto expected_type =
std::is_same_v<T, bool> ? format::type::boolean
: std::is_signed_v<T> ? format::type::signed_int
: format::type::unsigned_int;
if (type() != expected_type) {
return tl::make_unexpected(error::wrong_type);
@ -227,8 +234,8 @@ public:
if constexpr (expected_type == format::type::boolean) {
return T(value_.b);
} else if constexpr (expected_type == format::type::signed_int) {
if (std::numeric_limits<T>::max() < value_.i ||
std::numeric_limits<T>::lowest() > value_.i) {
if (std::numeric_limits<T>::max() < value_.i
|| std::numeric_limits<T>::lowest() > value_.i) {
return tl::make_unexpected(error::will_truncate);
}
return T(value_.i);
@ -266,94 +273,82 @@ private:
// TODO: What to store here for array/map types? Possibly a pointer
// to the end of the array/map, if/when known?
} value_;
size_and_enum<std::uint32_t, format::type> size_and_type_{};
};
template<>
inline tl::expected<std::string, error> token_base<8>::get()
const noexcept
{
template <>
inline tl::expected<std::string, error> token_base<8>::get() const noexcept {
if (type() != format::type::string) {
return tl::make_unexpected(error::wrong_type);
}
return std::string{value_.str, size_and_type_.get_size()};
}
template<>
constexpr tl::expected<std::string_view, error> token_base<8>::get()
const noexcept
{
template <>
constexpr tl::expected<std::string_view, error>
token_base<8>::get() const noexcept {
if (type() != format::type::string) {
return tl::make_unexpected(error::wrong_type);
}
return std::string_view{value_.str, size_and_type_.get_size()};
}
template <std::size_t N>
constexpr bool token_base<8>::operator==(char const (&t)[N]) const noexcept {
auto result = get<std::string_view>().map([&t](auto v) {
return v == std::string_view{t}; });
return result && *result;
}
template <std::size_t N>
constexpr bool token_base<8>::operator==(char const (&t)[N]) const noexcept {
auto result = get<std::string_view>().map(
[&t](auto v) { return v == std::string_view{t}; });
return result && *result;
}
template<>
inline tl::expected<std::vector<std::byte>, error> token_base<8>::get()
const noexcept
{
template <>
inline tl::expected<std::vector<std::byte>, error>
token_base<8>::get() const noexcept {
tl::expected<std::vector<std::byte>, error> result;
if (type() != format::type::binary) {
result = tl::make_unexpected(error::wrong_type);
} else {
result = std::vector<std::byte>(value_.bp,
value_.bp + size_and_type_.get_size());
result = std::vector<std::byte>(
value_.bp, value_.bp + size_and_type_.get_size());
}
return result;
}
template<>
template <>
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) {
return tl::make_unexpected(error::wrong_type);
}
return std::span<std::byte const>(value_.bp, size_and_type_.get_size());
}
template<>
constexpr tl::expected<nil, error>
token_base<8>::get() const noexcept
{
template <>
constexpr tl::expected<nil, error> token_base<8>::get() const noexcept {
if (type() != format::type::nil) {
return tl::make_unexpected(error::wrong_type);
}
return nil{};
}
template<>
constexpr tl::expected<invalid, error>
token_base<8>::get() const noexcept
{
template <>
constexpr tl::expected<invalid, error> token_base<8>::get() const noexcept {
if (type() != format::type::invalid) {
return tl::make_unexpected(error::wrong_type);
}
return invalid{};
}
template<>
constexpr tl::expected<array_desc, error>
token_base<8>::get() const noexcept
{
template <>
constexpr tl::expected<array_desc, error> token_base<8>::get() const noexcept {
if (type() != format::type::array) {
return tl::make_unexpected(error::wrong_type);
}
return array_desc{size_and_type_.get_size()};
}
template<>
constexpr tl::expected<map_desc, error>
token_base<8>::get() const noexcept
{
template <>
constexpr tl::expected<map_desc, error> token_base<8>::get() const noexcept {
if (type() != format::type::map) {
return tl::make_unexpected(error::wrong_type);
}
@ -364,5 +359,4 @@ using token = token_base<sizeof(void*)>;
} // namespace msgpack
#endif // msgpack_token_type_f43c22522692063f

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -19,124 +19,119 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_token_views_f19c250e782ed51c
#define msgpack_token_views_f19c250e782ed51c
#include "type.h"
#include <bits/iterator_concepts.h>
#include <fmt/format.h>
#include <ranges>
#include <fmt/format.h>
namespace msgpack {
template <std::ranges::view V>
requires std::ranges::input_range<V> && std::same_as<token,
std::ranges::range_value_t<V>>
struct map_view : public std::ranges::view_interface<map_view<V>> {
template <std::ranges::view V>
requires std::ranges::input_range<V>
&& std::same_as<token, std::ranges::range_value_t<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:
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 {
friend class sentinel;
iterator() = default;
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:
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_);
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_);
--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:
V base_;
V const* base_{};
base_iterator k_{};
base_iterator v_{};
std::size_t remaining_{};
};
template <class Range>
map_view(Range&&) -> map_view<std::views::all_t<Range>>;
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 {}; }
private:
V base_;
};
template <class Range>
map_view(Range&&) -> map_view<std::views::all_t<Range>>;
} // namespace msgpack
#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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
@ -15,7 +15,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef msgpack_endianness_d8ae54f45851ed13
#define msgpack_endianness_d8ae54f45851ed13
@ -24,11 +24,7 @@
#include <cstdint>
#include <type_traits>
enum class endianness {
big,
little,
other
};
enum class endianness { big, little, other };
namespace detail {
@ -37,8 +33,8 @@ namespace detail {
*/
struct host_endianness_check {
constexpr static inline std::uint32_t impl = 0x01020304;
constexpr static inline auto test = static_cast<const unsigned char&>(impl);
constexpr static inline auto value = []{
constexpr static inline auto test = static_cast<unsigned char const&>(impl);
constexpr static inline auto value = [] {
switch (test) {
case 4: return endianness::little;
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.
*/
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 {
if constexpr (From == To) {
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>
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 {
if constexpr (From == To) {
std::copy(begin, end, dest);
@ -98,10 +94,16 @@ constexpr auto raw_cast(T val) noexcept {
T val;
std::array<std::byte, sizeof(T)> data;
};
trick_to_array u{val};
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.
* Beware, this must be used on trivial data types.
@ -112,6 +114,7 @@ constexpr auto value_cast(Array&& data) noexcept {
Array data;
T val;
};
trick_to_value u{std::forward<Array>(data)};
return u.val;
}
@ -120,14 +123,14 @@ constexpr auto value_cast(Array&& data) noexcept {
* Byte swap implementation for arbitrary endiannesses.
*/
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 {
using array_type = std::array<std::byte, sizeof(T)>;
return std::bit_cast<T>(
maybe_swap<From, To>(std::bit_cast<array_type>(val)));
}
} // namespace detail;
} // namespace detail
/**
* 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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Proto
//
@ -14,16 +14,13 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef message_0c61530748b9f966
#define message_0c61530748b9f966
#include <cstdint>
#include <span>
#include <string_view>
#include <cstdint>
#include <tl/expected.hpp>
#include <variant>
namespace parselink {
namespace proto {
@ -37,40 +34,45 @@ namespace proto {
// This may be revised in the future. The header could remain as msgpack, or
// switch to something hand-crafted for saving bits.
struct error_message {
std::uint32_t code; // An error code
std::string_view what; // A string
struct base_message {};
template <typename T>
concept message_type = std::is_base_of_v<base_message, T>;
struct error_message : base_message {
std::uint32_t code; // An error code
std::string_view what; // A string
};
// C->S: Request to (re)connect.
struct connect_message {
std::uint32_t user_id; // The user id.
struct connect_message : base_message {
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.
};
// S->C: Challenge to authenticate client as user_id
struct challenge_message {
struct challenge_message : base_message {
std::uint32_t version;
std::span<std::byte> challenge;
};
// C->S: Calculated response to a challenge.
struct response_message {
struct response_message : base_message {
std::span<std::byte> response;
};
// S->C: Session token.
struct session_established_message {
struct session_established_message : base_message {
std::span<std::byte> session_token;
};
struct parser_data_message {
struct parser_data_message : base_message {
std::string_view opts;
std::span<std::byte> payload;
};
} // namespace message
} // namespace proto
} // namespace parselink
#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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
@ -14,12 +14,12 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef server_5b46f075be3caa00
#define server_5b46f075be3caa00
#include <memory>
#include <cstdint>
#include <memory>
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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Utility
//
@ -15,16 +15,16 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifndef argparse_d2ddac0dab0d7b88
#define argparse_d2ddac0dab0d7b88
#include <chrono>
#include <charconv>
#include <chrono>
#include <initializer_list>
#include <optional>
#include <map>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
@ -37,210 +37,205 @@
namespace argparse {
namespace custom {
template <typename T>
struct argument_parser {};
namespace custom {
template <typename T>
struct argument_parser {};
template <typename T>
concept has_parser = requires {
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) }
-> std::same_as<T*>;
};
}
template <typename T>
concept has_parser = requires {
{
argument_parser<std::decay_t<T>>::parse(std::string_view{})
} -> std::same_as<T*>;
};
} // namespace custom
template <typename T>
struct argument_parser {};
template <typename T>
struct argument_parser {};
template <>
struct argument_parser<bool> {
static bool* parse(std::string_view value) noexcept {
if (value == "1" || value == "true") {
return new bool{true};
} else if (value == "0" || value == "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);
template <>
struct argument_parser<bool> {
static bool* parse(std::string_view value) noexcept {
if (value == "1" || value == "true") {
return new bool{true};
} else if (value == "0" || value == "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;
}
} // namespace detail
class command_line_parser {
public:
using argument = detail::any_arg;
constexpr static char delimiter = '=';
using opt_list = std::initializer_list<
std::tuple<std::string_view, argument>>;
using opt_list =
std::initializer_list<std::tuple<std::string_view, argument>>;
struct result {
enum class code {
@ -261,8 +256,8 @@ public:
template <typename T>
T const* maybe_opt(std::string_view name) const noexcept {
auto entry = opts.find(name);
return entry != opts.end() ?
detail::arg_cast<T>(&entry->second) : nullptr;
return entry != opts.end() ? detail::arg_cast<T>(&entry->second)
: nullptr;
}
template <typename T>
@ -306,7 +301,6 @@ public:
}
private:
static auto split_option(std::string_view kv) {
auto delim = kv.find(delimiter);
if (delim != kv.npos) {
@ -350,7 +344,6 @@ private:
std::vector<std::string> arguments_;
};
}
} // namespace argparse
#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 = [
"headers",
"//include/parselink:msgpack",
"//include/parselink:proto",
"//include/parselink:utility",
"//source/logging",
"//source/proto",
"//source/server",
"@boost//:beast",
],
)

View File

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Logging
//
@ -14,7 +14,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#include "parselink/logging.h"
@ -26,7 +26,7 @@ namespace {
struct console_endpoint : public endpoint {
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; }
@ -38,23 +38,25 @@ struct console_endpoint : public endpoint {
}
};
}
} // namespace
auto& console() {
static auto console = std::make_shared<console_endpoint>();
return console;
}
logger::logger(std::string_view name) : name_{name} {
logger::logger(std::string_view name)
: name_{name} {
endpoints_.emplace_back(console());
}
logger::logger(std::string_view name, std::vector<std::shared_ptr<endpoint>> eps)
: name_{name}, endpoints_{std::move(eps)} {}
logger::logger(
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 {
for (auto& ep : endpoints_) {
ep->threshold = new_threshold;
}
}

View File

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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
@ -16,48 +16,46 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#include "parselink/logging.h"
#include "parselink/server.h"
#include "parselink/msgpack/token/reader.h"
#include "parselink/msgpack/token/views.h"
#include "parselink/proto/message.h"
#include "parselink/logging.h"
#include "parselink/msgpack/core/packer.h"
#include "parselink/msgpack/core/unpacker.h"
#include "parselink/proto/parser.h"
#include "parselink/proto/session.h"
#include "parselink/utility/file.h"
#include <fmt/ranges.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include "hydrogen.h"
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.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>
#include <map>
using namespace parselink;
#include <fmt/ranges.h>
using namespace parselink;
using namespace std::chrono_literals;
namespace net = boost::asio;
using net::co_spawn;
using net::awaitable;
using net::use_awaitable;
using net::co_spawn;
using net::deferred;
using net::detached;
enum class error {
system,
msgpack,
};
using net::use_awaitable;
//-----------------------------------------------------------------------------
// TODO(ksassenrath): These are logging formatters for various boost/asio types.
@ -71,34 +69,46 @@ struct parselink::logging::theme<boost::system::error_code>
template <>
struct fmt::formatter<boost::system::error_code>
: fmt::formatter<std::string_view> {
template<typename FormatContext>
template <typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const {
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 <>
struct fmt::formatter<msgpack::token> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template<typename FormatContext>
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()));
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>())));
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>())));
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>())));
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
@ -118,8 +128,7 @@ struct fmt::formatter<msgpack::token> {
case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)");
break;
default:
break;
default: break;
}
return fmt::format_to(out, ">");
}
@ -127,8 +136,8 @@ struct fmt::formatter<msgpack::token> {
template <typename T>
concept endpoint = requires(T const& t) {
{t.address()};
{t.port()};
{ t.address() };
{ t.port() };
};
template <endpoint T>
@ -136,12 +145,11 @@ struct parselink::logging::theme<T>
: parselink::logging::static_theme<fmt::color::coral> {};
template <endpoint T>
struct fmt::formatter<T>
: fmt::formatter<std::string_view> {
template<typename FormatContext>
struct fmt::formatter<T> : fmt::formatter<std::string_view> {
template <typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}:{}", v.address().to_string(),
v.port());
return fmt::format_to(
ctx.out(), "{}:{}", v.address().to_string(), v.port());
}
};
@ -150,16 +158,15 @@ struct fmt::formatter<T>
//-----------------------------------------------------------------------------
namespace {
logging::logger logger("server");
constexpr auto no_ex_coro = net::as_tuple(use_awaitable);
constexpr auto no_ex_defer = net::as_tuple(deferred);
}
logging::logger logger("server");
constexpr auto no_ex_coro = net::as_tuple(use_awaitable);
constexpr auto no_ex_defer = net::as_tuple(deferred);
struct user_session {
std::string user_id;
std::array<std::byte, 32> session_id;
std::chrono::system_clock::time_point expires_at;
};
} // namespace
#include <parselink/server/memory_session_manager.h>
class user_connection;
class monolithic_server : public server {
public:
@ -168,73 +175,33 @@ public:
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:
friend user_session;
awaitable<void> user_listen();
user_session* establish_session(user_session session);
std::map<std::string, user_session, std::less<>> active_user_sessions_;
tl::expected<tl::monostate, std::errc> load_keys() noexcept;
hydro_kx_keypair kp_;
net::io_context io_context_;
net::io_context::strand session_strand_;
memory_session_manager session_mgr_;
net::ip::address addr_;
std::uint16_t user_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> {
public:
user_connection(monolithic_server& server, net::ip::tcp::socket sock)
: server_(server)
, socket_(std::move(sock)) {}
: server_(server)
, socket_(std::move(sock)) {}
~user_connection() {
~user_connection() { stop(); }
void stop() {
logger.debug("Connection to {} closed.", socket_.remote_endpoint());
boost::system::error_code ec;
socket_.shutdown(net::ip::tcp::socket::shutdown_both, ec);
@ -243,138 +210,115 @@ public:
void start() {
logger.debug("New connection from {}", socket_.remote_endpoint());
co_spawn(socket_.get_executor(), [self = shared_from_this()]{
return self->await_connect();
}, detached);
co_spawn(
socket_.get_executor(),
[self = shared_from_this()] { return self->await_connect(); },
detached);
}
tl::expected<std::vector<std::byte>, msgpack::error> parse_header(
std::span<std::byte> data) 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 {
awaitable<tl::expected<tl::monostate, proto::error>> read_message(
std::vector<std::byte>& buffer) noexcept {
// 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(
net::buffer(buffer), no_ex_coro);
net::buffer(hdrbuff), no_ex_coro);
if (ec) {
logger.error("Reading from user socket failed: {}", ec);
co_return tl::make_unexpected(ec);
logger.error("Reading hdr from user socket failed: {}", ec);
co_return tl::make_unexpected(proto::error::system_error);
}
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));
if (!hdr) {
logger.error("Unable to parse header: {}", hdr.error());
co_return tl::make_unexpected(boost::system::error_code: );
co_return;
auto maybe_hdr = proto::parse_header(std::span(hdrbuff.data(), n));
if (!maybe_hdr) {
logger.error("Unable to parse header: {}", maybe_hdr.error());
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) {
logger.error("Unable to parse header: {}", result.error());
co_return;
// Copy remaining portion of message in initial read to the message
// buffer.
std::copy(std::next(hdrbuff.begin(), maybe_hdr->bytes_read),
std::next(hdrbuff.begin(), n), buffer.begin());
auto span = std::span(
buffer.begin() + n - maybe_hdr->bytes_read, buffer.end());
// Buffer remaining message.
std::size_t amt = 0;
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 {
auto reader = msgpack::token_reader(msg);
std::array<msgpack::token, 32> tokens;
auto maybe_user = reader.read_some(tokens)
.and_then(handle_connect)
.map_error([](auto const& error) {
logger.error("Unable to parse msgpack tokens: {}", error);
});
awaitable<void> await_connect() noexcept {
std::vector<std::byte> msgbuf;
if (!maybe_user) {
if (auto maybe_msg = co_await read_message(msgbuf); !maybe_msg) {
logger.debug("returning");
co_return;
}
// Authenticate against database.
logger.debug("User {} established connection", maybe_user->user_id);
session_ = server_.establish_session(std::move(*maybe_user));
auto connect_info = proto::parse<proto::connect_message>(msgbuf);
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 {
init,
authenticated,
active
};
enum class state { init, authenticated, active };
monolithic_server& server_;
user_session* session_{};
net::ip::tcp::socket socket_;
proto::session* session_{};
};
monolithic_server::monolithic_server(std::string_view address,
std::uint16_t user_port, std::uint16_t websocket_port)
: io_context_{1}
, session_strand_(io_context_)
, session_mgr_(io_context_)
, addr_(net::ip::address::from_string(std::string{address}))
, user_port_{user_port}
, websocket_port_{websocket_port} {
logger.debug("Loaded keys: {}", load_keys());
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() {
auto exec = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor{exec, {addr_, user_port_}};
while (true) {
std::make_shared<user_connection>(*this,
co_await acceptor.async_accept(use_awaitable))->start();
std::make_shared<user_connection>(
*this, co_await acceptor.async_accept(use_awaitable))
->start();
}
}
@ -382,7 +326,7 @@ std::error_code monolithic_server::run() noexcept {
logger.info("Starting server.");
net::signal_set signals(io_context_, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){
signals.async_wait([&](auto, auto) {
logger.info("Received signal... Shutting down.");
io_context_.stop();
});
@ -394,13 +338,79 @@ std::error_code monolithic_server::run() noexcept {
return {};
}
user_session* monolithic_server::establish_session(user_session session) {
auto existing_session = active_user_sessions_.find(session.user_id);
if (existing_session == active_user_sessions_.end()) {
// No session exists with that user ID yet.
active_user_sessions_.emplace(session.user_id,
std::move(session.user_id));
}
tl::expected<tl::monostate, std::errc> monolithic_server::load_keys() noexcept {
std::string_view filename = "/home/rihya/server_kp.keys";
enum class result { loaded, generated };
auto load_key = [this](auto const& raw) -> tl::expected<result, std::errc> {
msgpack::unpacker unpacker(raw);
auto pk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.pk)>>();
if (!pk) return tl::make_unexpected(std::errc::bad_message);
auto sk = unpacker.unpack<std::span<std::byte const, sizeof(kp_.sk)>>();
if (!sk) return tl::make_unexpected(std::errc::bad_message);
std::ranges::transform(pk->begin(), pk->end(), std::begin(kp_.pk),
[](auto c) { return std::bit_cast<unsigned char>(c); });
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,

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

@ -1,11 +1,11 @@
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Tests
//
@ -14,7 +14,7 @@
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#include "parselink/logging.h"
@ -32,16 +32,18 @@ struct test_endpoint_base : public endpoint {
void write(message const& msg) override {
if constexpr (Colored) {
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 {
buffer_.append(fmt::format("{} {} | {}",
enum_name_only{msg.lvl}, msg.name, msg.message));
buffer_.append(fmt::format("{} {} | {}", enum_name_only{msg.lvl},
msg.name, msg.message));
}
}
bool colored() const noexcept override { return Colored; }
void clear() { buffer_.clear(); }
std::string_view contents() const { return 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
// 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
// gets correctly-styled content.
struct dynamic_theme_test { int value; };
struct dynamic_theme_test {
int value;
};
template <has_theme T>
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);
} else if constexpr (has_dynamic_theme<T>) {
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style(v));
@ -75,7 +81,7 @@ auto styled(T&& v) {
}
}
}
} // namespace
template <>
struct fmt::formatter<static_theme_test> : public fmt::formatter<int> {
@ -104,7 +110,6 @@ struct parselink::logging::theme<dynamic_theme_test> {
}
};
// Begin tests!
using namespace boost::ut;
@ -226,18 +231,18 @@ suite logging = [] {
{
auto const& cptrref = ptr;
logger.log<level::info>("int const pointer&: {}", cptrref);
auto expected = fmt::format(
"info formatting | int const pointer&: {}",
fmt::ptr(cptrref));
auto expected =
fmt::format("info formatting | int const pointer&: {}",
fmt::ptr(cptrref));
expect(ep->contents() == expected);
ep->clear();
}
{
logger.log<level::info>("std::unique_ptr<int>: {}", uniq_ptr);
auto expected = fmt::format(
"info formatting | std::unique_ptr<int>: {}",
fmt::ptr(uniq_ptr));
auto expected =
fmt::format("info formatting | std::unique_ptr<int>: {}",
fmt::ptr(uniq_ptr));
expect(ep->contents() == expected);
ep->clear();
}
@ -245,17 +250,17 @@ suite logging = [] {
{
auto& uniq_ptrref = uniq_ptr;
logger.log<level::info>("std::unique_ptr<int>&: {}", uniq_ptrref);
auto expected = fmt::format(
"info formatting | std::unique_ptr<int>&: {}",
fmt::ptr(uniq_ptrref));
auto expected =
fmt::format("info formatting | std::unique_ptr<int>&: {}",
fmt::ptr(uniq_ptrref));
expect(ep->contents() == expected);
ep->clear();
}
{
auto const& cuniq_ptrref = uniq_ptr;
logger.log<level::info>("std::unique_ptr<int> const&: {}",
cuniq_ptrref);
logger.log<level::info>(
"std::unique_ptr<int> const&: {}", cuniq_ptrref);
auto expected = fmt::format(
"info formatting | std::unique_ptr<int> const&: {}",
fmt::ptr(cuniq_ptrref));
@ -265,9 +270,9 @@ suite logging = [] {
{
logger.log<level::info>("std::shared_ptr<int>: {}", shr_ptr);
auto expected = fmt::format(
"info formatting | std::shared_ptr<int>: {}",
fmt::ptr(shr_ptr));
auto expected =
fmt::format("info formatting | std::shared_ptr<int>: {}",
fmt::ptr(shr_ptr));
expect(ep->contents() == expected);
ep->clear();
}
@ -275,17 +280,17 @@ suite logging = [] {
{
auto& shr_ptrref = shr_ptr;
logger.log<level::info>("std::shared_ptr<int>&: {}", shr_ptrref);
auto expected = fmt::format(
"info formatting | std::shared_ptr<int>&: {}",
fmt::ptr(shr_ptrref));
auto expected =
fmt::format("info formatting | std::shared_ptr<int>&: {}",
fmt::ptr(shr_ptrref));
expect(ep->contents() == expected);
ep->clear();
}
{
auto const& cshr_ptrref = shr_ptr;
logger.log<level::info>("std::shared_ptr<int> const&: {}",
cshr_ptrref);
logger.log<level::info>(
"std::shared_ptr<int> const&: {}", cshr_ptrref);
auto expected = fmt::format(
"info formatting | std::shared_ptr<int> const&: {}",
fmt::ptr(cshr_ptrref));
@ -330,8 +335,8 @@ suite logging = [] {
dynamic_theme_test red{21}, green{202};
auto formatted = fmt::format("{}", styled(stt));
auto expected = fmt::format("{}",
fmt::styled(stt.value, fmt::fg(fmt::color::black)));
auto expected = fmt::format(
"{}", fmt::styled(stt.value, fmt::fg(fmt::color::black)));
expect(formatted == expected);
formatted = fmt::format("{}", themed_arg{stt});
expect(formatted == expected);
@ -351,8 +356,8 @@ suite logging = [] {
auto [logger, ep] = make_test_logger<colored_test_endpoint>("colored");
logger.log<level::info>("");
auto expected = fmt::format("{} colored | ",
styled(enum_name_only{level::info}));
auto expected = fmt::format(
"{} colored | ", styled(enum_name_only{level::info}));
expect(ep->contents() == expected);
ep->clear();
@ -387,12 +392,11 @@ suite logging = [] {
auto* int_ptr = &int_value;
logger.log<level::info>("pointer {} {}", int_ptr, *int_ptr);
expected = fmt::format("{} colored | pointer {} {}",
styled(enum_name_only{level::info}),
themed_arg{int_ptr}, themed_arg{*int_ptr});
styled(enum_name_only{level::info}), themed_arg{int_ptr},
themed_arg{*int_ptr});
expect(ep->contents() == expected);
ep->clear();
};
};
int main(int, char**) {
}
int main(int, char**) {}

View File

@ -1,10 +1,11 @@
cc_library(
name = "test_deps",
name = "common",
srcs = [
"test_main.cpp",
"rng.h",
],
includes = ["include"],
hdrs = glob(["include/*.h"]),
deps = [
"//include/parselink:msgpack",
"@expected",
@ -12,47 +13,53 @@ cc_library(
"@magic_enum",
"@ut",
],
visibility = ["__subpackages__"],
)
cc_test(
name = "reader",
size = "small",
srcs = [
"test_reader_relaxed.cpp",
"test_reader_strict.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
name = "writer",
size = "small",
srcs = [
"test_writer.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
name = "token",
size = "small",
srcs = [
"test_token.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
name = "token_reader",
size = "small",
srcs = [
"test_token_reader.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_test(
name = "token_views",
size = "small",
srcs = [
"test_token_views.cpp",
],
deps = ["test_deps"],
deps = ["common"],
)
cc_binary(
@ -60,5 +67,13 @@ cc_binary(
srcs = [
"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>
struct rng {
rng(auto s) : seed(s), generator(seed) {}
rng() : rng([]{ return std::random_device{}(); }()) {}
rng(auto s)
: seed(s)
, generator(seed) {}
rng()
: rng([] { return std::random_device{}(); }()) {}
template <typename T>
requires std::is_integral_v<T>
requires std::is_integral_v<T>
T get() noexcept {
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;
} u;
for (auto& d : u.data) {
d = generator();
}
@ -23,7 +30,7 @@ struct rng {
}
template <typename T, std::size_t N>
requires std::is_integral_v<T>
requires std::is_integral_v<T>
auto get() noexcept {
std::array<T, N> 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 <boost/ut.hpp>
#include <string>
namespace {
@ -10,55 +8,56 @@ using namespace boost::ut;
namespace format = msgpack::format;
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, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, std::size_t C = 1024> struct oversized_array {
std::array<T, C> data;
std::size_t size;
template <typename T, std::size_t C = 1024>
struct oversized_array {
std::array<T, C> data;
std::size_t size;
};
constexpr auto to_bytes_array_oversized(auto const &container) {
oversized_array<std::byte> 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;
constexpr auto to_bytes_array_oversized(auto const& container) {
oversized_array<std::byte> 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());
std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data),
constexpr auto oversized = to_bytes_array_oversized(callable());
std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size),
std::begin(out));
return out;
return out;
}
template <std::size_t A, std::size_t B>
consteval auto cat(std::array<std::byte, A> const &a,
std::array<std::byte, B> const &b) {
std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
consteval auto cat(
std::array<std::byte, A> const& a, std::array<std::byte, B> const& b) {
std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
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)));
return out;
return out;
}
template <typename T, typename Itr>
constexpr auto zip(T val, Itr begin, Itr end) {
std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val);
output.emplace_back(*itr);
}
return output;
std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val);
output.emplace_back(*itr);
}
return output;
}
template <typename T, typename Iterable>
constexpr auto zip(T val, Iterable const &itr) {
return zip(val, std::begin(itr), std::end(itr));
constexpr auto zip(T val, Iterable const& itr) {
return zip(val, std::begin(itr), std::end(itr));
}
using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>;
@ -66,65 +65,65 @@ using relaxed_reader = msgpack::reader<msgpack::reader_policy::relaxed>;
} // namespace
suite relaxed = [] {
"empty span"_test = [] {
using error = msgpack::error;
std::span<std::byte> bytes;
relaxed_reader reader(bytes);
auto v = reader.read<msgpack::format::uint8>();
expect(v.error() == error::end_of_message);
};
"empty span"_test = [] {
using error = msgpack::error;
std::span<std::byte> bytes;
relaxed_reader reader(bytes);
auto v = reader.read<msgpack::format::uint8>();
expect(v.error() == error::end_of_message);
};
////////////////////////////////////////////////////////////////////////////////
// Unsigned integers
////////////////////////////////////////////////////////////////////////////////
"reader<reader_policy::relaxed>::read<format::uint8>"_test = [] {
using fmt = format::uint8;
using error = msgpack::error;
/**
* All bytes 0x00->0x79 are effectively literal uint8s.
*/
{
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84);
relaxed_reader reader(payload);
{
auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x52));
}
{
auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x84));
}
////////////////////////////////////////////////////////////////////////////////
// Unsigned integers
////////////////////////////////////////////////////////////////////////////////
"reader<reader_policy::relaxed>::read<format::uint8>"_test = [] {
using fmt = format::uint8;
using error = msgpack::error;
/**
* All bytes 0x00->0x79 are effectively literal uint8s.
*/
{
constexpr auto payload = make_bytes(0x52, 0xcc, 0x84);
relaxed_reader reader(payload);
{
auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x52));
}
{
auto result = reader.read<fmt>();
expect(result == std::uint8_t(0x84));
}
auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::end_of_message));
}
};
auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::end_of_message));
}
};
"reader<reader_policy::relaxed>::read<formats::uint16>"_test = [] {
using fmt = msgpack::format::uint16;
using error = msgpack::error;
/**
* All bytes 0x00->0x79 are effectively literal uint8s.
*/
{
constexpr auto 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(0x84));
}
{
auto result = reader.read<fmt>();
expect(result == std::uint16_t(0xaacc));
}
"reader<reader_policy::relaxed>::read<formats::uint16>"_test = [] {
using fmt = msgpack::format::uint16;
using error = msgpack::error;
/**
* All bytes 0x00->0x79 are effectively literal uint8s.
*/
{
constexpr auto 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(0x84));
}
{
auto result = reader.read<fmt>();
expect(result == std::uint16_t(0xaacc));
}
auto result = reader.read<fmt>();
expect(result == tl::make_unexpected(error::end_of_message));
}
};
auto result = reader.read<fmt>();
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/proto/message.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <magic_enum.hpp>
// #include "parselink/proto/message.h"
#include <chrono>
#include <cstdlib>
#include <fcntl.h>
#include <magic_enum.hpp>
#include <vector>
#include <fmt/format.h>
#include <fmt/ranges.h>
using namespace std::chrono_literals;
struct any {
@ -37,7 +37,6 @@ auto read_file(char const* filename) {
}
int main(int argc, char** argv) {
std::array<msgpack::token, 20> buf;
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 test = reader.read_some(buf);
auto after = std::chrono::steady_clock::now();
test.map([&](auto sp){
fmt::print("Read {} tokens\n", sp.size());
for (auto token : sp) {
fmt::print("token type: {} value: ", magic_enum::enum_name(token.type()));
switch (token.type()) {
case msgpack::format::type::unsigned_int:
fmt::print("{}", *(token.template get<std::uint64_t>()));
break;
case msgpack::format::type::signed_int:
fmt::print("{}", *(token.template get<std::int64_t>()));
break;
case msgpack::format::type::boolean:
fmt::print("{}", *(token.template get<bool>()));
break;
case msgpack::format::type::string:
fmt::print("{}", *(token.template get<std::string_view>()));
break;
case msgpack::format::type::binary:
fmt::print("{}", *(token.template get<std::span<std::byte const>>()));
break;
case msgpack::format::type::map:
fmt::print("map ({} entries)", token.template get<msgpack::map_desc>()->count);
break;
case msgpack::format::type::array:
fmt::print("array ({} entries)", 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;
test.map([&](auto sp) {
fmt::print("Read {} tokens\n", sp.size());
for (auto token : sp) {
fmt::print("token type: {} value: ",
magic_enum::enum_name(token.type()));
switch (token.type()) {
case msgpack::format::type::unsigned_int:
fmt::print(
"{}", *(token.template get<std::uint64_t>()));
break;
case msgpack::format::type::signed_int:
fmt::print("{}", *(token.template get<std::int64_t>()));
break;
case msgpack::format::type::boolean:
fmt::print("{}", *(token.template get<bool>()));
break;
case msgpack::format::type::string:
fmt::print("{}",
*(token.template get<std::string_view>()));
break;
case msgpack::format::type::binary:
fmt::print(
"{}", *(token.template get<
std::span<std::byte const>>()));
break;
case msgpack::format::type::map:
fmt::print("map ({} entries)",
token.template get<msgpack::map_desc>()->count);
break;
case msgpack::format::type::array:
fmt::print("array ({} entries)",
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("Took {} us\n", (after - before) / 1us);

View File

@ -1,40 +1,37 @@
#include "parselink/msgpack/token.h"
#include <boost/ut.hpp>
using namespace boost::ut;
namespace {
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes &&...bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename First, typename... Others>
constexpr bool wrong_types(auto const& obj) {
auto err = tl::make_unexpected(msgpack::error::wrong_type);
if (obj.template get<First>() != err) return false;
if constexpr (sizeof...(Others)) {
return wrong_types<Others...>(obj);
} else {
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;
template <typename First, typename... Others>
constexpr bool wrong_types(auto const& obj) {
auto err = tl::make_unexpected(msgpack::error::wrong_type);
if (obj.template get<First>() != err) return false;
if constexpr (sizeof...(Others)) {
return wrong_types<Others...>(obj);
} else {
return true;
}
}
enum class foo : std::uint8_t {
a, b, c, d, e
};
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;
}
} // namespace
enum class foo : std::uint8_t { a, b, c, d, e };
suite size_and_enum = [] {
"expected use case"_test = [] {
@ -118,18 +115,19 @@ suite assignment_and_access = [] {
std::vector<std::byte> extracted_val;
{
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>>();
expect(bool(retrieved));
expect(std::equal(retrieved->begin(), retrieved->end(),
val.begin(), val.end()));
expect(std::equal(retrieved->begin(), retrieved->end(), val.begin(),
val.end()));
expect(wrong_types<bool, int, unsigned, std::string_view>(obj));
auto bytes_retrieved = obj.get<std::vector<std::byte>>();
expect(bool(bytes_retrieved));
extracted_val = std::move(*bytes_retrieved);
}
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 <boost/ut.hpp>
#include <fmt/format.h>
#include <magic_enum.hpp>
using namespace boost::ut;
@ -18,17 +18,17 @@ constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
}
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))...};
}
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::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])>;
oversized_array<value_type> arr;
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;
}
template <std::size_t ... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept {
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), ...);
@ -89,14 +89,14 @@ constexpr auto from_string_view(std::string_view sv) {
}
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;
std::ostream& operator<<(std::ostream& os, tl::expected<T, E> const& exp) {
if (exp.has_value()) {
os << "Value: '" << *exp << "'";
} else {
os << "Error";
}
return os;
}
bool test_incomplete_message(auto const& payload) {
// Test incomplete message.
@ -105,9 +105,9 @@ bool test_incomplete_message(auto const& payload) {
msgpack::token_reader reader(std::span(payload.data(), i));
auto token = reader.read_one();
if (token != tl::make_unexpected(msgpack::error::incomplete_message)) {
fmt::print("Got the wrong response reading subview[0,{}] of payload: {}\n",
i,
token->get<std::string_view>().value());
fmt::print("Got the wrong response reading subview[0,{}] of "
"payload: {}\n",
i, token->get<std::string_view>().value());
return false;
}
}
@ -115,11 +115,11 @@ bool test_incomplete_message(auto const& payload) {
}
bool test_end_of_message(auto& reader) {
return reader.read_one() ==
tl::make_unexpected(msgpack::error::end_of_message);
return reader.read_one()
== tl::make_unexpected(msgpack::error::end_of_message);
}
}
} // namespace
suite reader_tests = [] {
//--------------------------------------------------------------------------
@ -173,8 +173,8 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() == 0x0102);
expect(token->get<std::uint32_t>() == 0x0102);
expect(token->get<std::uint64_t>() == 0x0102);
@ -193,10 +193,10 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() == 0x01020304);
expect(token->get<std::uint64_t>() == 0x01020304);
expect(token == 0x01020304u);
@ -204,8 +204,8 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::uint64"_test = [] {
constexpr auto payload = make_bytes(0xcf,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
constexpr auto payload = make_bytes(
0xcf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
// Test incomplete message.
expect(test_incomplete_message(payload));
@ -214,12 +214,12 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::unsigned_int);
expect(token->get<std::uint8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint16_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint32_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::uint64_t>() == 0x0102030405060708);
expect(token == 0x0102030405060708u);
// EOM
@ -277,8 +277,8 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() == -256);
expect(token->get<std::int32_t>() == -256);
expect(token->get<std::int64_t>() == -256);
@ -297,10 +297,10 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() == -65536);
expect(token->get<std::int64_t>() == -65536);
expect(token == -65536);
@ -308,8 +308,8 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::int64"_test = [] {
constexpr auto payload = make_bytes(0xd3,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00);
constexpr auto payload = make_bytes(
0xd3, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00);
// Test incomplete message.
expect(test_incomplete_message(payload));
@ -318,12 +318,12 @@ suite reader_tests = [] {
msgpack::token_reader reader(payload);
auto token = reader.read_one();
expect(token && token->type() == msgpack::format::type::signed_int);
expect(token->get<std::int8_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>() ==
tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int8_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int16_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int32_t>()
== tl::make_unexpected(msgpack::error::will_truncate));
expect(token->get<std::int64_t>() == -4294967296);
expect(token == -4294967296);
// EOM
@ -335,8 +335,7 @@ suite reader_tests = [] {
//--------------------------------------------------------------------------
"read format::fixstr"_test = [] {
constexpr std::string_view sv = "hello";
constexpr auto payload =
cat(make_bytes(0xa5),
constexpr auto payload = cat(make_bytes(0xa5),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
@ -355,8 +354,7 @@ suite reader_tests = [] {
};
"read format::str8"_test = [] {
constexpr std::string_view sv = "hello world";
constexpr auto payload =
cat(make_bytes(0xd9, sv.size()),
constexpr auto payload = cat(make_bytes(0xd9, sv.size()),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
@ -374,14 +372,12 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::str16"_test = [] {
static constexpr auto sa = build_string([]{
return repeat("0123456789abcdef", 17);
});
static constexpr auto sa =
build_string([] { return repeat("0123456789abcdef", 17); });
constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint16_t(sv.size()));
auto payload =
cat(make_bytes(0xda), as_bytes(sv_size),
auto payload = cat(make_bytes(0xda), as_bytes(sv_size),
generate_bytes([] { return from_string_view(sv); }));
// Test incomplete message.
@ -399,14 +395,12 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::str32"_test = [] {
static constexpr auto sa = build_string([]{
return repeat("0123456789abcdef", 4097);
});
static constexpr auto sa =
build_string([] { return repeat("0123456789abcdef", 4097); });
constexpr auto sv = std::string_view(sa.data(), sa.size());
constexpr auto sv_size = host_to_be(std::uint32_t(sv.size()));
constexpr auto payload =
cat(make_bytes(0xdb), as_bytes(sv_size),
constexpr auto payload = cat(make_bytes(0xdb), as_bytes(sv_size),
generate_bytes([sv] { return from_string_view(sv); }));
// Test incomplete message.
@ -428,13 +422,11 @@ suite reader_tests = [] {
// Binary format tests
//--------------------------------------------------------------------------
"read format::bin8"_test = [] {
constexpr auto bytes = make_bytes(
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10
);
constexpr auto bytes = make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10);
constexpr auto bytes_size = host_to_be(std::uint8_t(bytes.size()));
constexpr auto payload =
cat(make_bytes(0xc4), as_bytes(bytes_size), bytes);
cat(make_bytes(0xc4), as_bytes(bytes_size), bytes);
// Test incomplete message.
expect(test_incomplete_message(payload));
@ -452,7 +444,7 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::bin16"_test = [] {
static constexpr auto bytes = generate_bytes([]{
static constexpr auto bytes = generate_bytes([] {
return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
16);
@ -477,7 +469,7 @@ suite reader_tests = [] {
expect(test_end_of_message(reader));
};
"read format::bin32"_test = [] {
static constexpr auto bytes = generate_bytes([]{
static constexpr auto bytes = generate_bytes([] {
return repeat(
make_bytes(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08),
8193);
@ -485,7 +477,7 @@ suite reader_tests = [] {
constexpr auto bytes_size = host_to_be(std::uint32_t(bytes.size()));
constexpr auto payload =
cat(make_bytes(0xc6), as_bytes(bytes_size), bytes);
cat(make_bytes(0xc6), as_bytes(bytes_size), bytes);
// Test incomplete message.
expect(test_incomplete_message(payload));
@ -508,7 +500,7 @@ suite reader_tests = [] {
"read format::fixarray"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers.
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);
auto token = reader.read_one();
@ -517,16 +509,16 @@ suite reader_tests = [] {
expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i);
auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i);
}
expect(test_end_of_message(reader));
};
"read format::array16"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = make_bytes(0xdc, 0x00, 0x05,
0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
constexpr auto payload = make_bytes(0xdc, 0x00, 0x05, 0xcc, 0x85, 0xcc,
0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
msgpack::token_reader reader(payload);
auto token = reader.read_one();
@ -535,16 +527,16 @@ suite reader_tests = [] {
expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one();
expect(v && v == 0x85u - i);
auto v = reader.read_one();
expect(v && v == 0x85u - i);
}
expect(test_end_of_message(reader));
};
"read format::array32"_test = [] {
// A MessagePack array of 5 8-bit unsigned integers.
constexpr auto payload = make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05,
0xcc, 0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
constexpr auto payload = make_bytes(0xdd, 0x00, 0x00, 0x00, 0x05, 0xcc,
0x85, 0xcc, 0x84, 0xcc, 0x83, 0xcc, 0x82, 0xcc, 0x81);
msgpack::token_reader reader(payload);
auto token = reader.read_one();
@ -553,8 +545,8 @@ suite reader_tests = [] {
expect(array && array->count == 5);
for (std::size_t i = 0; i < array->count; ++i) {
auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i);
auto v = reader.read_one();
expect(v && v->get<std::uint8_t>() == 0x85 - i);
}
expect(test_end_of_message(reader));
@ -562,18 +554,15 @@ suite reader_tests = [] {
"read format::fixmap"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three"
};
"one", "two", "three"};
constexpr auto payload = cat(
make_bytes(0x83, 0xa3),
constexpr auto payload = cat(make_bytes(0x83, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3),
generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03)
);
make_bytes(0x03));
msgpack::token_reader reader(payload);
auto token = reader.read_one();
@ -593,18 +582,15 @@ suite reader_tests = [] {
"read format::map16"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three"
};
"one", "two", "three"};
constexpr auto payload = cat(
make_bytes(0xde, 0x00, 0x03, 0xa3),
constexpr auto payload = cat(make_bytes(0xde, 0x00, 0x03, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3),
generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03)
);
make_bytes(0x03));
msgpack::token_reader reader(payload);
auto token = reader.read_one();
@ -626,8 +612,7 @@ suite reader_tests = [] {
"read format::map32"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers.
static constexpr std::array<std::string_view, 3> strings = {
"one", "two", "three"
};
"one", "two", "three"};
constexpr auto payload = cat(
make_bytes(0xdf, 0x00, 0x00, 0x00, 0x03, 0xa3),
@ -636,8 +621,7 @@ suite reader_tests = [] {
generate_bytes([] { return from_string_view(strings[1]); }),
make_bytes(0x02, 0xa5),
generate_bytes([] { return from_string_view(strings[2]); }),
make_bytes(0x03)
);
make_bytes(0x03));
msgpack::token_reader reader(payload);
auto token = reader.read_one();

View File

@ -1,11 +1,12 @@
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <msgpack/token.h>
#include <boost/ut.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <magic_enum.hpp>
using namespace boost::ut;
namespace {
template <typename T>
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>
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))...};
}
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::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])>;
oversized_array<value_type> arr;
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;
}
template <std::size_t ... Sizes>
constexpr auto cat(std::array<std::byte, Sizes>const&... a) noexcept {
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), ...);
@ -89,14 +90,14 @@ constexpr auto from_string_view(std::string_view sv) {
}
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;
std::ostream& operator<<(std::ostream& os, tl::expected<T, E> const& exp) {
if (exp.has_value()) {
os << "Value: '" << *exp << "'";
} else {
os << "Error";
}
return os;
}
bool test_incomplete_message(auto const& payload) {
// Test incomplete message.
@ -105,9 +106,9 @@ bool test_incomplete_message(auto const& payload) {
msgpack::token_reader reader(std::span(payload.data(), i));
auto token = reader.read_one();
if (token != tl::make_unexpected(msgpack::error::incomplete_message)) {
fmt::print("Got the wrong response reading subview[0,{}] of payload: {}\n",
i,
token->get<std::string_view>().value());
fmt::print("Got the wrong response reading subview[0,{}] of "
"payload: {}\n",
i, token->get<std::string_view>().value());
return false;
}
}
@ -115,10 +116,10 @@ bool test_incomplete_message(auto const& payload) {
}
bool test_end_of_message(auto& reader) {
return reader.read_one() ==
tl::make_unexpected(msgpack::error::end_of_message);
}
return reader.read_one()
== tl::make_unexpected(msgpack::error::end_of_message);
}
} // namespace
template <>
struct fmt::formatter<msgpack::token> {
@ -126,9 +127,11 @@ struct fmt::formatter<msgpack::token> {
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template<typename FormatContext>
template <typename FormatContext>
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()) {
case msgpack::format::type::unsigned_int:
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>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
(*(v.get<std::span<std::byte const>>())));
out = fmt::format_to(
out, "{}", (*(v.get<std::span<std::byte const>>())));
break;
case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})",
@ -160,8 +163,7 @@ struct fmt::formatter<msgpack::token> {
case msgpack::format::type::invalid:
out = fmt::format_to(out, "(invalid)");
break;
default:
break;
default: break;
}
return fmt::format_to(out, ">");
}
@ -171,11 +173,16 @@ suite views_tests = [] {
"read format::fixmap"_test = [] {
// A MessagePack map of 3 strings to 8-bit unsigned integers.
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(
make_bytes(0x87, 0xa3),
constexpr auto payload = cat(make_bytes(0x87, 0xa3),
generate_bytes([] { return from_string_view(strings[0]); }),
make_bytes(0x01, 0xa3),
@ -201,7 +208,7 @@ suite views_tests = [] {
make_bytes(0xa4),
generate_bytes([] { return from_string_view(strings[6]); })
//make_bytes(0x05)
// make_bytes(0x05)
);
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 <boost/ut.hpp>
#include <string>
#include <fmt/format.h>
#include <fmt/ranges.h>
using namespace boost::ut;
namespace format = msgpack::format;
namespace {
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, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, std::size_t C = 1024> struct oversized_array {
std::array<T, C> data;
std::size_t size;
template <typename T, std::size_t C = 1024>
struct oversized_array {
std::array<T, C> data;
std::size_t size;
};
constexpr auto to_bytes_array_oversized(auto const &container) {
oversized_array<std::byte> 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;
constexpr auto to_bytes_array_oversized(auto const& container) {
oversized_array<std::byte> 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());
std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data),
constexpr auto oversized = to_bytes_array_oversized(callable());
std::array<std::byte, oversized.size> out;
std::copy(std::begin(oversized.data),
std::next(std::begin(oversized.data), oversized.size),
std::begin(out));
return out;
return out;
}
template <std::size_t A, std::size_t B>
consteval auto cat(std::array<std::byte, A> const &a,
std::array<std::byte, B> const &b) {
std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
consteval auto cat(
std::array<std::byte, A> const& a, std::array<std::byte, B> const& b) {
std::array<std::byte, A + B> out;
std::copy(std::begin(a), std::next(std::begin(a), std::size(a)),
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)));
return out;
return out;
}
template <typename T, typename Itr>
constexpr auto zip(T val, Itr begin, Itr end) {
std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val);
output.emplace_back(*itr);
}
return output;
std::vector<T> output;
for (auto itr = begin; itr != end; ++itr) {
output.emplace_back(val);
output.emplace_back(*itr);
}
return output;
}
template <typename T, typename Iterable>
constexpr auto zip(T val, Iterable const &itr) {
return zip(val, std::begin(itr), std::end(itr));
constexpr auto zip(T val, Iterable const& itr) {
return zip(val, std::begin(itr), std::end(itr));
}
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;
std::vector<std::byte> range;
range.resize(sv.size());
auto itr = range.begin();
for (auto c : sv) {
*itr = std::byte(c);
++itr;
}
return range;
}
constexpr auto make_contiguous_range(std::uint8_t start, std::uint8_t end) {
auto count = std::size_t(end) - std::size_t(start) + 1;
std::vector<std::byte> range;
range.resize(count);
for (auto i = std::size_t(start); i <= std::size_t(end); ++i) {
range[i - std::size_t(start)] = std::byte(i);
}
return range;
auto count = std::size_t(end) - std::size_t(start) + 1;
std::vector<std::byte> range;
range.resize(count);
for (auto i = std::size_t(start); i <= std::size_t(end); ++i) {
range[i - std::size_t(start)] = std::byte(i);
}
return range;
}
constexpr auto equal(auto a, auto b) {
@ -89,162 +91,176 @@ constexpr auto equal(auto a, auto b) {
} // namespace
suite writer = [] {
"writer empty span"_test = [] {
std::array<std::byte, 16> payload;
"writer empty span"_test = [] {
std::array<std::byte, 16> payload;
msgpack::writer writer(payload);
expect(writer.tell() == 0);
};
msgpack::writer writer(payload);
expect(writer.tell() == 0);
};
"writer::write<format::positive_fixint>"_test = [] {
using fmt = format::positive_fixint;
using error = msgpack::error;
"writer::write<format::positive_fixint>"_test = [] {
using fmt = format::positive_fixint;
using error = msgpack::error;
std::array<std::byte, 2> payload;
auto constexpr expected = make_bytes(0x32, 0x55);
msgpack::writer writer(payload);
auto result = writer.write<fmt>(std::uint8_t{0x32});
expect(!!result);
expect(writer.tell() == 1);
expect(*writer.subspan().begin() == std::byte{0x32});
expect(writer.write<fmt>(std::uint8_t{0x82}) == tl::make_unexpected(error::bad_value));
std::array<std::byte, 2> payload;
auto constexpr expected = make_bytes(0x32, 0x55);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::uint8_t{0x32}));
expect(writer.tell() == 1);
expect(*writer.subspan().begin() == *expected.begin());
expect(writer.write<fmt>(std::uint8_t{0x82})
== tl::make_unexpected(error::bad_value));
expect(!!writer.write<fmt>(std::uint8_t{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});
expect(writer.tell() == 2);
expect(equal(writer.subspan(), expected));
expect(writer.write(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::uint8>"_test = [] {
using fmt = format::uint8;
using error = msgpack::error;
"writer::write<format::uint8>"_test = [] {
using fmt = format::uint8;
using error = msgpack::error;
std::array<std::byte, 4> payload;
auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82);
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;
auto constexpr expected = make_bytes(0xcc, 0x32, 0xcc, 0x82);
msgpack::writer writer(payload);
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 = [] {
using fmt = format::uint16;
using error = msgpack::error;
"writer::write<format::uint16>"_test = [] {
using fmt = format::uint16;
using error = msgpack::error;
std::array<std::byte, 6> payload;
auto constexpr expected =
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;
auto constexpr expected = 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));
};
"writer::write<format::uint32>"_test = [] {
using fmt = format::uint32;
using error = msgpack::error;
"writer::write<format::uint32>"_test = [] {
using fmt = format::uint32;
using error = msgpack::error;
std::array<std::byte, 5> payload;
auto constexpr expected = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04);
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;
auto constexpr expected = make_bytes(0xce, 0x01, 0x02, 0x03, 0x04);
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));
};
"writer::write<format::uint64>"_test = [] {
using fmt = format::uint64;
using error = msgpack::error;
"writer::write<format::uint64>"_test = [] {
using fmt = format::uint64;
using error = msgpack::error;
std::array<std::byte, 9> payload;
auto constexpr expected = make_bytes(
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;
auto constexpr expected = make_bytes(
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));
};
"writer::write<format::negative_fixint>"_test = [] {
using fmt = format::negative_fixint;
using error = msgpack::error;
"writer::write<format::negative_fixint>"_test = [] {
using fmt = format::negative_fixint;
using error = msgpack::error;
std::array<std::byte, 2> payload;
auto constexpr expected = make_bytes(0xff, 0xe0);
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;
auto constexpr expected = make_bytes(0xff, 0xe0);
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));
};
"writer::write<format::int8>"_test = [] {
using fmt = format::int8;
using error = msgpack::error;
"writer::write<format::int8>"_test = [] {
using fmt = format::int8;
using error = msgpack::error;
std::array<std::byte, 4> payload;
auto constexpr expected = make_bytes(0xd0, 0x32, 0xd0, 0xfb);
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;
auto constexpr expected = make_bytes(0xd0, 0x32, 0xd0, 0xfb);
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(std::uint8_t{0x01}) == tl::make_unexpected(error::out_of_space));
};
"writer::write<format::int16>"_test = [] {
using fmt = format::int16;
using error = msgpack::error;
"writer::write<format::int16>"_test = [] {
using fmt = format::int16;
using error = msgpack::error;
std::array<std::byte, 6> payload;
auto constexpr expected =
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;
auto constexpr expected = 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));
};
"writer::write<format::int32>"_test = [] {
using fmt = format::int32;
using error = msgpack::error;
"writer::write<format::int32>"_test = [] {
using fmt = format::int32;
using error = msgpack::error;
std::array<std::byte, 5> payload;
auto constexpr expected = make_bytes(0xd2, 0x01, 0x02, 0x03, 0x04);
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;
auto constexpr expected = make_bytes(0xd2, 0x01, 0x02, 0x03, 0x04);
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));
};
"writer::write<format::int64>"_test = [] {
using fmt = format::int64;
using error = msgpack::error;
"writer::write<format::int64>"_test = [] {
using fmt = format::int64;
using error = msgpack::error;
std::array<std::byte, 9> payload;
auto constexpr expected = make_bytes(
0xd3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::int64_t{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;
auto constexpr expected = make_bytes(
0xd3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
msgpack::writer writer(payload);
expect(!!writer.write<fmt>(std::int64_t{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::fixstr>"_test = [] {
using fmt = format::fixstr;
@ -259,15 +275,14 @@ suite writer = [] {
"writer::write<format::str8>"_test = [] {
using fmt = format::str8;
std::array<std::byte, 46> payload;
auto constexpr expected = make_bytes(0xd9, 44,
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
auto constexpr expected = make_bytes(0xd9, 44, 't', 'h', 'e', ' ', 'q',
'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o',
'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r',
' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o',
'g');
msgpack::writer writer(payload);
std::string_view txt =
"the quick brown fox jumped over the lazy dog";
std::string_view txt = "the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected));
};
@ -275,15 +290,14 @@ suite writer = [] {
"writer::write<format::str16>"_test = [] {
using fmt = format::str16;
std::array<std::byte, 47> payload;
auto constexpr expected = make_bytes(0xda, 0, 44,
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
auto constexpr expected = make_bytes(0xda, 0, 44, 't', 'h', 'e', ' ',
'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ', 'f',
'o', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e',
'r', ' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o',
'g');
msgpack::writer writer(payload);
std::string_view txt =
"the quick brown fox jumped over the lazy dog";
std::string_view txt = "the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected));
};
@ -291,15 +305,14 @@ suite writer = [] {
"writer::write<format::str32>"_test = [] {
using fmt = format::str32;
std::array<std::byte, 49> payload;
auto constexpr expected = make_bytes(0xdb, 0, 0, 0, 44,
't', 'h', 'e', ' ', 'q', 'u', 'i', 'c', 'k', ' ',
'b', 'r', 'o', 'w', 'n', ' ', 'f', 'o', 'x', ' ',
'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v', 'e', 'r', ' ',
't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd', 'o', 'g');
auto constexpr expected = make_bytes(0xdb, 0, 0, 0, 44, 't', 'h', 'e',
' ', 'q', 'u', 'i', 'c', 'k', ' ', 'b', 'r', 'o', 'w', 'n', ' ',
'f', 'o', 'x', ' ', 'j', 'u', 'm', 'p', 'e', 'd', ' ', 'o', 'v',
'e', 'r', ' ', 't', 'h', 'e', ' ', 'l', 'a', 'z', 'y', ' ', 'd',
'o', 'g');
msgpack::writer writer(payload);
std::string_view txt =
"the quick brown fox jumped over the lazy dog";
std::string_view txt = "the quick brown fox jumped over the lazy dog";
expect(!!writer.write<fmt>(std::move(txt)));
expect(equal(writer.subspan(), expected));
};
@ -307,8 +320,8 @@ suite writer = [] {
"writer::write<format::bin8>"_test = [] {
using fmt = format::bin8;
std::array<std::byte, 12> payload;
auto constexpr expected = make_bytes(0xc4, 0x07, 0x01, 0x02, 0x03, 0x04,
0xf8, 0xf9, 0xfa);
auto constexpr expected = make_bytes(
0xc4, 0x07, 0x01, 0x02, 0x03, 0x04, 0xf8, 0xf9, 0xfa);
msgpack::writer writer(payload);
std::span<std::byte const> bv(expected.begin() + 2, expected.end());
@ -319,8 +332,8 @@ suite writer = [] {
"writer::write<format::bin16>"_test = [] {
using fmt = format::bin16;
std::array<std::byte, 12> payload;
auto constexpr expected = make_bytes(0xc5, 0x0, 0x07, 0x01, 0x02, 0x03,
0x04, 0xf8, 0xf9, 0xfa);
auto constexpr expected = make_bytes(
0xc5, 0x0, 0x07, 0x01, 0x02, 0x03, 0x04, 0xf8, 0xf9, 0xfa);
msgpack::writer writer(payload);
std::span<std::byte const> bv(expected.begin() + 3, expected.end());
@ -422,3 +435,179 @@ suite writer = [] {
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));
}
};
};