Compare commits
24 Commits
add-clang-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b2a6f25306 | |||
| c0945246de | |||
| eb7a02d5c4 | |||
|
|
db601fb770 | ||
| 0e6f05dd68 | |||
|
|
e48ba60c68 | ||
|
|
8f4ac703f4 | ||
|
|
0cafb9bcd7 | ||
| b48eddb8d0 | |||
| 3d7ba40289 | |||
|
|
cb2b5b47b0 | ||
|
|
a027d6b946 | ||
|
|
0e00edc378 | ||
| c89d7cd541 | |||
|
|
53665b8e70 | ||
| ebe2b070b9 | |||
| 84942171ea | |||
| cbca4be237 | |||
|
|
578065be47 | ||
| 1ceccd0720 | |||
| 8f8066c243 | |||
| 6874da27a3 | |||
|
|
c46f68e759 | ||
|
|
3157e39169 |
47
.clang-format
Normal file
47
.clang-format
Normal 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
30
.snippets/cpp.lua
Normal 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
65
BUILD.rapidcheck
Normal 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
6
MODULE.bazel
Normal 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
1245
MODULE.bazel.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
WORKSPACE
46
WORKSPACE
@ -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
0
bazel/BUILD
Normal file
290
beastref.cpp
290
beastref.cpp
@ -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:
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@ -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"],
|
||||
)
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
288
include/parselink/msgpack/core/detail/builtin_packable_types.h
Normal file
288
include/parselink/msgpack/core/detail/builtin_packable_types.h
Normal 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
|
||||
347
include/parselink/msgpack/core/detail/builtin_unpackable_types.h
Normal file
347
include/parselink/msgpack/core/detail/builtin_unpackable_types.h
Normal 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
|
||||
85
include/parselink/msgpack/core/detail/packable_concepts.h
Normal file
85
include/parselink/msgpack/core/detail/packable_concepts.h
Normal 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
|
||||
104
include/parselink/msgpack/core/detail/packable_ranges.h
Normal file
104
include/parselink/msgpack/core/detail/packable_ranges.h
Normal 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
|
||||
92
include/parselink/msgpack/core/detail/unpackable_concepts.h
Normal file
92
include/parselink/msgpack/core/detail/unpackable_concepts.h
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
113
include/parselink/msgpack/core/packer.h
Normal file
113
include/parselink/msgpack/core/packer.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
94
include/parselink/msgpack/core/unpacker.h
Normal file
94
include/parselink/msgpack/core/unpacker.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
121
include/parselink/msgpack/extra/formatters.h
Normal file
121
include/parselink/msgpack/extra/formatters.h
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
267
include/parselink/msgpack/token/writer.h
Normal file
267
include/parselink/msgpack/token/writer.h
Normal 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
|
||||
@ -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.
|
||||
|
||||
35
include/parselink/proto/error.h
Normal file
35
include/parselink/proto/error.h
Normal 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
|
||||
@ -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
|
||||
|
||||
60
include/parselink/proto/parser.h
Normal file
60
include/parselink/proto/parser.h
Normal 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
|
||||
120
include/parselink/proto/session.h
Normal file
120
include/parselink/proto/session.h
Normal 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
|
||||
88
include/parselink/proto/session_id.h
Normal file
88
include/parselink/proto/session_id.h
Normal 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
|
||||
@ -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 {
|
||||
|
||||
|
||||
68
include/parselink/server/memory_session_manager.h
Normal file
68
include/parselink/server/memory_session_manager.h
Normal 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
|
||||
42
include/parselink/server/server.h
Normal file
42
include/parselink/server/server.h
Normal 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
|
||||
69
include/parselink/server/session_manager.h
Normal file
69
include/parselink/server/session_manager.h
Normal 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
|
||||
@ -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
|
||||
|
||||
156
include/parselink/utility/file.h
Normal file
156
include/parselink/utility/file.h
Normal 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
|
||||
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
20
source/proto/BUILD
Normal 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
70
source/proto/parser.cpp
Normal 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 = [¶m](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
198
source/proto/session.cpp
Normal 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() {}
|
||||
38
source/proto/session_id.cpp
Normal file
38
source/proto/session_id.cpp
Normal 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>()) {}
|
||||
@ -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
20
source/server/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
84
source/server/memory_session_manager.cpp
Normal file
84
source/server/memory_session_manager.cpp
Normal 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);
|
||||
}
|
||||
@ -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**) {}
|
||||
|
||||
@ -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"],
|
||||
)
|
||||
|
||||
102
tests/msgpack/include/test_utils.h
Normal file
102
tests/msgpack/include/test_utils.h
Normal 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
|
||||
9
tests/msgpack/packer/BUILD
Normal file
9
tests/msgpack/packer/BUILD
Normal file
@ -0,0 +1,9 @@
|
||||
cc_test(
|
||||
name = "packer",
|
||||
size = "small",
|
||||
srcs = glob([
|
||||
"*.cpp",
|
||||
"*.h",
|
||||
]),
|
||||
deps = ["//tests/msgpack:common", "@rapidcheck"],
|
||||
)
|
||||
63
tests/msgpack/packer/bytes.cpp
Normal file
63
tests/msgpack/packer/bytes.cpp
Normal 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
63
tests/msgpack/packer/ranges.cpp
Normal file
63
tests/msgpack/packer/ranges.cpp
Normal 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
71
tests/msgpack/packer/signed.cpp
Normal file
71
tests/msgpack/packer/signed.cpp
Normal 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>());
|
||||
};
|
||||
};
|
||||
50
tests/msgpack/packer/simple_types.cpp
Normal file
50
tests/msgpack/packer/simple_types.cpp
Normal 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});
|
||||
};
|
||||
};
|
||||
46
tests/msgpack/packer/strings.cpp
Normal file
46
tests/msgpack/packer/strings.cpp
Normal 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>());
|
||||
};
|
||||
};
|
||||
42
tests/msgpack/packer/test_packer.h
Normal file
42
tests/msgpack/packer/test_packer.h
Normal 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
|
||||
71
tests/msgpack/packer/unsigned.cpp
Normal file
71
tests/msgpack/packer/unsigned.cpp
Normal 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>());
|
||||
};
|
||||
};
|
||||
@ -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) {
|
||||
|
||||
17
tests/msgpack/test_code.cpp
Normal file
17
tests/msgpack/test_code.cpp
Normal 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;
|
||||
}
|
||||
412
tests/msgpack/test_packer.cpp
Normal file
412
tests/msgpack/test_packer.cpp
Normal 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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
@ -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);
|
||||
|
||||
@ -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()));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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>());
|
||||
};
|
||||
};
|
||||
|
||||
9
tests/msgpack/unpacker/BUILD
Normal file
9
tests/msgpack/unpacker/BUILD
Normal file
@ -0,0 +1,9 @@
|
||||
cc_test(
|
||||
name = "unpacker",
|
||||
size = "small",
|
||||
srcs = glob([
|
||||
"*.cpp",
|
||||
"*.h",
|
||||
]),
|
||||
deps = ["//tests/msgpack:common", "@rapidcheck"],
|
||||
)
|
||||
68
tests/msgpack/unpacker/bytes.cpp
Normal file
68
tests/msgpack/unpacker/bytes.cpp
Normal 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
105
tests/msgpack/unpacker/signed.cpp
Normal file
105
tests/msgpack/unpacker/signed.cpp
Normal 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>());
|
||||
};
|
||||
};
|
||||
61
tests/msgpack/unpacker/simple_types.cpp
Normal file
61
tests/msgpack/unpacker/simple_types.cpp
Normal 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));
|
||||
};
|
||||
};
|
||||
31
tests/msgpack/unpacker/strings.cpp
Normal file
31
tests/msgpack/unpacker/strings.cpp
Normal 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));
|
||||
};
|
||||
};
|
||||
57
tests/msgpack/unpacker/test_unpacker.h
Normal file
57
tests/msgpack/unpacker/test_unpacker.h
Normal 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
|
||||
131
tests/msgpack/unpacker/unsigned.cpp
Normal file
131
tests/msgpack/unpacker/unsigned.cpp
Normal 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
22
tests/proto/BUILD
Normal 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"],
|
||||
)
|
||||
2
tests/proto/test_main.cpp
Normal file
2
tests/proto/test_main.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
int main(int, char**) {}
|
||||
60
tests/proto/test_session_id.cpp
Normal file
60
tests/proto/test_session_id.cpp
Normal 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));
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user