Initial Release
This commit is contained in:
commit
5a00e06cb9
81 changed files with 12670 additions and 0 deletions
2
.clang-tidy
Normal file
2
.clang-tidy
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
Checks: '*,-llvmlibc-*,-readability-*,-google-readability-*,-fuchsia-*,-hicpp-exception-baseclass,-altera-*,-hicpp-braces-around-statements,-google-runtime-int,-misc-non-private-member-variables-in-classes,-cert-err58-cpp,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-type-union-access,-hicpp-signed-bitwise,-cppcoreguidelines-avoid-magic-numbers,-misc-no-recursion,-bugprone-easily-swappable-parameters,-cppcoreguidelines-owning-memory,-cppcoreguidelines-macro-usage,-google-objc-*'
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.cache/
|
||||
/zig-out/
|
||||
/zig-cache/
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug tests",
|
||||
"program": "${workspaceFolder}/zig-out/bin/test",
|
||||
"env": {
|
||||
"TRACE": "1",
|
||||
},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "build",
|
||||
}
|
||||
]
|
||||
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"clangd.arguments": [
|
||||
"--enable-config"
|
||||
]
|
||||
}
|
52
.vscode/tasks.json
vendored
Normal file
52
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "process",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"command": "./zig",
|
||||
"args": [
|
||||
"build",
|
||||
"-freference-trace",
|
||||
"--prominent-compile-errors"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$zig"
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"type": "process",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"command": "./zig",
|
||||
"args": [
|
||||
"build",
|
||||
"test",
|
||||
"-freference-trace",
|
||||
"--prominent-compile-errors"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$zig"
|
||||
}
|
||||
]
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 CJ van den Berg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
125
build.zig
Normal file
125
build.zig
Normal file
|
@ -0,0 +1,125 @@
|
|||
const std = @import("std");
|
||||
|
||||
const CrossTarget = std.zig.CrossTarget;
|
||||
|
||||
const cppflags = [_][]const u8{
|
||||
"-fcolor-diagnostics",
|
||||
"-std=c++20",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wno-unqualified-std-cast-call",
|
||||
"-Wno-bitwise-instead-of-logical", //for notcurses
|
||||
"-fno-sanitize=undefined",
|
||||
"-gen-cdb-fragment-path",
|
||||
".cache/cdb",
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const enable_tracy_option = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)");
|
||||
const tracy_enabled = if (enable_tracy_option) |enabled| enabled else false;
|
||||
|
||||
const options = b.addOptions();
|
||||
options.addOption(bool, "enable_tracy", tracy_enabled);
|
||||
|
||||
const options_mod = options.createModule();
|
||||
|
||||
const target = b.standardTargetOptions(.{
|
||||
.default_target = if (tracy_enabled)
|
||||
CrossTarget.parse(.{ .arch_os_abi = "native-native-gnu" }) catch unreachable
|
||||
else
|
||||
CrossTarget.parse(.{ .arch_os_abi = "native-native-musl" }) catch unreachable,
|
||||
});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const mode = .{ .target = target, .optimize = optimize };
|
||||
|
||||
const asio_dep = b.dependency("asio", mode);
|
||||
const tracy_dep = if (tracy_enabled) b.dependency("tracy", mode) else undefined;
|
||||
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "thespian",
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
if (tracy_enabled) {
|
||||
lib.defineCMacro("TRACY_ENABLE", null);
|
||||
lib.defineCMacro("TRACY_CALLSTACK", null);
|
||||
}
|
||||
lib.addIncludePath(.{ .path = "src" });
|
||||
lib.addIncludePath(.{ .path = "include" });
|
||||
lib.addCSourceFiles(.{ .files = &[_][]const u8{
|
||||
"src/backtrace.cpp",
|
||||
"src/c/context.cpp",
|
||||
"src/c/env.cpp",
|
||||
"src/c/file_descriptor.cpp",
|
||||
"src/c/handle.cpp",
|
||||
"src/c/instance.cpp",
|
||||
"src/c/metronome.cpp",
|
||||
"src/c/signal.cpp",
|
||||
"src/c/timeout.cpp",
|
||||
"src/c/trace.cpp",
|
||||
"src/cbor.cpp",
|
||||
"src/executor_asio.cpp",
|
||||
"src/hub.cpp",
|
||||
"src/instance.cpp",
|
||||
"src/trace.cpp",
|
||||
}, .flags = &cppflags });
|
||||
if (tracy_enabled) {
|
||||
lib.linkLibrary(tracy_dep.artifact("tracy"));
|
||||
}
|
||||
lib.linkLibrary(asio_dep.artifact("asio"));
|
||||
lib.linkLibCpp();
|
||||
b.installArtifact(lib);
|
||||
|
||||
const cbor_mod = b.addModule("cbor", .{
|
||||
.root_source_file = .{ .path = "src/cbor.zig" },
|
||||
});
|
||||
|
||||
const thespian_mod = b.addModule("thespian", .{
|
||||
.root_source_file = .{ .path = "src/thespian.zig" },
|
||||
.imports = &.{
|
||||
.{ .name = "cbor", .module = cbor_mod },
|
||||
},
|
||||
});
|
||||
thespian_mod.addIncludePath(.{ .path = "include" });
|
||||
thespian_mod.linkLibrary(lib);
|
||||
|
||||
const tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "test/tests.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
tests.root_module.addImport("build_options", options_mod);
|
||||
tests.root_module.addImport("cbor", cbor_mod);
|
||||
tests.root_module.addImport("thespian", thespian_mod);
|
||||
tests.addIncludePath(.{ .path = "test" });
|
||||
tests.addIncludePath(.{ .path = "src" });
|
||||
tests.addIncludePath(.{ .path = "include" });
|
||||
tests.addCSourceFiles(.{ .files = &[_][]const u8{
|
||||
"test/cbor_match.cpp",
|
||||
"test/debug.cpp",
|
||||
"test/endpoint_unx.cpp",
|
||||
"test/endpoint_tcp.cpp",
|
||||
"test/hub_filter.cpp",
|
||||
"test/ip_tcp_client_server.cpp",
|
||||
"test/ip_udp_echo.cpp",
|
||||
"test/metronome_test.cpp",
|
||||
"test/perf_cbor.cpp",
|
||||
"test/perf_hub.cpp",
|
||||
"test/perf_ring.cpp",
|
||||
"test/perf_spawn.cpp",
|
||||
"test/spawn_exit.cpp",
|
||||
"test/tests.cpp",
|
||||
"test/timeout_test.cpp",
|
||||
}, .flags = &cppflags });
|
||||
tests.linkLibrary(lib);
|
||||
tests.linkLibrary(asio_dep.artifact("asio"));
|
||||
tests.linkLibCpp();
|
||||
b.installArtifact(tests);
|
||||
|
||||
const test_run_cmd = b.addRunArtifact(tests);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&test_run_cmd.step);
|
||||
}
|
1
build.zig.version
Normal file
1
build.zig.version
Normal file
|
@ -0,0 +1 @@
|
|||
0.12.0-dev.2644+42fcca49c
|
22
build.zig.zon
Normal file
22
build.zig.zon
Normal file
|
@ -0,0 +1,22 @@
|
|||
.{
|
||||
.name = "thespian",
|
||||
.version = "0.0.1",
|
||||
|
||||
.dependencies = .{
|
||||
.asio = .{
|
||||
.url = "https://github.com/kassane/asio/archive/5fc70d29f458f5b596a20bdf3d638294eef91478.tar.gz",
|
||||
.hash = "1220926d7e934fcb9e4e55f25e24d17613fbe9b145798581e0ae7ea3679b37f4e44d",
|
||||
},
|
||||
.tracy = .{
|
||||
.url = "https://github.com/neurocyte/zig-tracy/archive/d2113e7d778ebe7a063e95b5182ff145343aac38.tar.gz",
|
||||
.hash = "122005c37f1324dcdd00600f266b64f0a66a73428550015ece51d52ae40a552608d1",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"include",
|
||||
"src",
|
||||
"test",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
}
|
26
include/cbor/c/cbor.h
Normal file
26
include/cbor/c/cbor.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <thespian/c/string_view.h>
|
||||
|
||||
#include <stddef.h> // NOLINT
|
||||
#include <stdint.h> // NOLINT
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct cbor_buffer_t {
|
||||
const uint8_t *base;
|
||||
size_t len;
|
||||
};
|
||||
typedef struct // NOLINT
|
||||
cbor_buffer_t cbor_buffer;
|
||||
|
||||
typedef // NOLINT
|
||||
void (*cbor_to_json_callback)(c_string_view);
|
||||
|
||||
void cbor_to_json(cbor_buffer, cbor_to_json_callback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
463
include/cbor/cbor.hpp
Normal file
463
include/cbor/cbor.hpp
Normal file
|
@ -0,0 +1,463 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace cbor {
|
||||
|
||||
constexpr auto buffer_reserve = 128;
|
||||
|
||||
constexpr auto cbor_magic_null = 0xf6;
|
||||
constexpr auto cbor_magic_true = 0xf5;
|
||||
constexpr auto cbor_magic_false = 0xf4;
|
||||
|
||||
constexpr auto cbor_magic_type_array = 4;
|
||||
constexpr auto cbor_magic_type_map = 5;
|
||||
|
||||
enum class type {
|
||||
number,
|
||||
bytes,
|
||||
string,
|
||||
array,
|
||||
map,
|
||||
tag,
|
||||
boolean,
|
||||
null,
|
||||
any,
|
||||
more,
|
||||
unknown,
|
||||
};
|
||||
const auto any = type::any;
|
||||
const auto more = type::more;
|
||||
|
||||
template <typename T> inline auto is_more(const T & /*unused*/) -> bool {
|
||||
return false;
|
||||
}
|
||||
template <> inline auto is_more<type>(const type &t) -> bool {
|
||||
return t == type::more;
|
||||
}
|
||||
|
||||
using buffer_base = std::vector<uint8_t>;
|
||||
|
||||
class buffer : public buffer_base {
|
||||
private:
|
||||
using iter = const_iterator;
|
||||
|
||||
public:
|
||||
using extractor = std::function<bool(iter &, const iter &)>;
|
||||
|
||||
private:
|
||||
static auto decode_range_header(iter &, const iter &) -> size_t;
|
||||
static auto decode_array_header(iter &, const iter &) -> size_t;
|
||||
static auto decode_map_header(iter &, const iter &) -> size_t;
|
||||
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &, const extractor &)
|
||||
-> bool;
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &, type) -> bool;
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &, int64_t) -> bool;
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &, bool) -> bool;
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &,
|
||||
const std::string &) -> bool;
|
||||
[[nodiscard]] static auto match_value(iter &, const iter &,
|
||||
const std::string_view) -> bool;
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, const char *s)
|
||||
-> bool {
|
||||
return match_value(b, e, std::string(s));
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint64_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, int32_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint32_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, int16_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint16_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, int8_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint8_t v)
|
||||
-> bool {
|
||||
return match_value(b, e, static_cast<int64_t>(v));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static auto match_next(size_t n, iter &b, const iter &e, T &&p)
|
||||
-> bool {
|
||||
if (is_more(p)) {
|
||||
while (n) {
|
||||
if (!match_value(b, e, any))
|
||||
return false;
|
||||
--n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!n)
|
||||
return false;
|
||||
if (!match_value(b, e, p))
|
||||
return false;
|
||||
return n == 1;
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
[[nodiscard]] static auto match_next(size_t n, iter &b, const iter &e, T &&p,
|
||||
Ts &&...parms) -> bool {
|
||||
if (!n)
|
||||
return false;
|
||||
if (!match_value(b, e, p))
|
||||
return false;
|
||||
return match_next(--n, b, e, std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
auto push_typed_val(int type, uint64_t value) -> void;
|
||||
|
||||
auto push_parms() -> buffer & { return *this; }
|
||||
template <typename T, typename... Ts>
|
||||
auto push_parms(const T &p, Ts &&...parms) -> buffer & {
|
||||
push(p);
|
||||
return push_parms(std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
[[nodiscard]] static auto match_array(iter &b, const iter &e, Ts &&...parms)
|
||||
-> bool {
|
||||
auto n = decode_array_header(b, e);
|
||||
return match_next(n, b, e, std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
[[nodiscard]] static auto match_map(iter &b, const iter &e, Ts &&...parms)
|
||||
-> bool {
|
||||
auto n = decode_map_header(b, e);
|
||||
n *= 2;
|
||||
return match_next(n, b, e, std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
public:
|
||||
using buffer_base::buffer_base;
|
||||
auto operator[](size_t) -> value_type & = delete;
|
||||
|
||||
static const buffer null_value;
|
||||
|
||||
[[nodiscard]] auto raw_cbegin() const -> iter {
|
||||
return buffer_base::cbegin();
|
||||
}
|
||||
[[nodiscard]] auto raw_cend() const -> iter { return buffer_base::cend(); }
|
||||
|
||||
template <typename... Ts>
|
||||
[[nodiscard]] auto operator()(Ts &&...parms) const -> bool {
|
||||
auto b = raw_cbegin();
|
||||
auto e = raw_cend();
|
||||
return match_array(b, e, std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_null() const -> bool { return *this == null_value; }
|
||||
|
||||
auto push_null() -> buffer & {
|
||||
push_back(cbor_magic_null);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto push_bool(bool v) -> buffer & {
|
||||
if (v)
|
||||
push_back(cbor_magic_true);
|
||||
else
|
||||
push_back(cbor_magic_false);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto push_string(const std::string &) -> buffer &;
|
||||
auto push_string(const std::string_view &s) -> buffer &;
|
||||
|
||||
auto push_int(int64_t value) -> buffer & {
|
||||
if (value < 0) {
|
||||
push_typed_val(1, -(value + 1));
|
||||
} else {
|
||||
push_typed_val(0, value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
auto push(const long long &i) -> buffer & { return push_int(i); }
|
||||
auto push(const int64_t &i) -> buffer & { return push_int(i); }
|
||||
auto push(const int32_t &i) -> buffer & { return push_int(i); }
|
||||
auto push(const int16_t &i) -> buffer & { return push_int(i); }
|
||||
auto push(const int8_t &i) -> buffer & { return push_int(i); }
|
||||
auto push(const bool &v) -> buffer & { return push_bool(v); }
|
||||
auto push(const std::string &s) -> buffer & { return push_string(s); }
|
||||
auto push(const std::string_view &s) -> buffer & { return push_string(s); }
|
||||
auto push(char *s) -> buffer & { return push_string(std::string_view(s)); }
|
||||
auto push(const char *s) -> buffer & {
|
||||
return push_string(std::string_view(s));
|
||||
}
|
||||
|
||||
auto push_uint(uint64_t value) -> buffer & {
|
||||
push_typed_val(0, value);
|
||||
return *this;
|
||||
}
|
||||
auto push(const unsigned long long &i) -> buffer & { return push_uint(i); }
|
||||
auto push(const uint64_t &i) -> buffer & { return push_uint(i); }
|
||||
auto push(const uint32_t &i) -> buffer & { return push_uint(i); }
|
||||
auto push(const uint16_t &i) -> buffer & { return push_uint(i); }
|
||||
auto push(const uint8_t &i) -> buffer & { return push_uint(i); }
|
||||
|
||||
template <typename T> auto push(const T &a) -> buffer & {
|
||||
a.to_cbor(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto array_header(size_t sz) -> buffer & {
|
||||
push_typed_val(cbor_magic_type_array, sz);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto map_header(size_t sz) -> buffer & {
|
||||
push_typed_val(cbor_magic_type_map, sz);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T> auto push_array(const T &a) -> buffer & {
|
||||
if (a.empty())
|
||||
push_null();
|
||||
else {
|
||||
array_header(a.size());
|
||||
for (auto v : a)
|
||||
push(v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto push_json(const std::string &) -> void;
|
||||
|
||||
auto array() -> buffer & { return push_null(); }
|
||||
|
||||
template <typename... Ts> auto array(Ts &&...parms) -> buffer & {
|
||||
array_header(sizeof...(parms));
|
||||
return push_parms(std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
auto map() -> buffer & { return push_null(); }
|
||||
|
||||
template <typename... Ts> auto map(Ts &&...parms) -> buffer & {
|
||||
constexpr size_t sz = sizeof...(parms);
|
||||
static_assert(sz % 2 == 0,
|
||||
"cbor maps must contain an even number of values");
|
||||
map_header(sz / 2);
|
||||
return push_parms(std::forward<Ts>(parms)...);
|
||||
}
|
||||
|
||||
auto to_cbor(buffer &b) const -> buffer & {
|
||||
b.insert(b.raw_cend(), raw_cbegin(), raw_cend());
|
||||
return b;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_json() const -> std::string;
|
||||
auto to_json(std::ostream &) const -> void;
|
||||
[[nodiscard]] auto hexdump() const -> std::string;
|
||||
|
||||
class range;
|
||||
|
||||
struct value_accessor {
|
||||
iter b;
|
||||
iter e;
|
||||
|
||||
[[nodiscard]] auto type_() const -> type;
|
||||
|
||||
operator type() const; // NOLINT
|
||||
operator int64_t() const; // NOLINT
|
||||
operator uint64_t() const; // NOLINT
|
||||
operator int32_t() const; // NOLINT
|
||||
operator uint32_t() const; // NOLINT
|
||||
operator int16_t() const; // NOLINT
|
||||
operator uint16_t() const; // NOLINT
|
||||
operator int8_t() const; // NOLINT
|
||||
operator uint8_t() const; // NOLINT
|
||||
operator bool() const; // NOLINT
|
||||
operator std::string_view() const; // NOLINT
|
||||
operator buffer::range() const; // NOLINT
|
||||
|
||||
struct unvisitable_type {
|
||||
cbor::type t;
|
||||
};
|
||||
|
||||
template <typename F> auto visit(F f) const -> decltype(f(true)) {
|
||||
type t{type_()};
|
||||
switch (t) {
|
||||
case type::string:
|
||||
return f(static_cast<std::string_view>(*this));
|
||||
case type::number:
|
||||
return f(static_cast<int64_t>(*this));
|
||||
case type::boolean:
|
||||
return f(static_cast<bool>(*this));
|
||||
case type::array:
|
||||
case type::map:
|
||||
return f(static_cast<range>(*this));
|
||||
case type::null:
|
||||
case type::bytes:
|
||||
case type::tag:
|
||||
case type::any:
|
||||
case type::more:
|
||||
case type::unknown:
|
||||
return f(unvisitable_type{t});
|
||||
}
|
||||
return f(unvisitable_type{t});
|
||||
}
|
||||
|
||||
auto to_cbor(buffer &buf) const -> buffer & {
|
||||
buf.insert(buf.raw_cend(), b, e);
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
auto push(const value_accessor::unvisitable_type & /*unused*/) -> buffer & {
|
||||
return push("cbor::value_accessor::unvisitable_type");
|
||||
}
|
||||
|
||||
struct value_iterator {
|
||||
iter b;
|
||||
iter e;
|
||||
size_t n = 0;
|
||||
|
||||
auto operator!=(const value_iterator &other) const -> bool {
|
||||
return b != other.b;
|
||||
}
|
||||
auto operator++() -> void {
|
||||
if (n) {
|
||||
--n;
|
||||
[[maybe_unused]] bool _ = match_value(b, e, type::any);
|
||||
}
|
||||
}
|
||||
auto operator*() -> value_accessor {
|
||||
if (n)
|
||||
return {b, e};
|
||||
throw std::out_of_range("cbor iterator out of range");
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] auto cbegin() const -> value_iterator = delete;
|
||||
[[nodiscard]] auto cend() const -> value_iterator = delete;
|
||||
[[nodiscard]] auto begin() const -> value_iterator {
|
||||
auto b = raw_cbegin();
|
||||
auto e = raw_cend();
|
||||
auto n = decode_range_header(b, e);
|
||||
return {b, e, n};
|
||||
}
|
||||
[[nodiscard]] auto end() const -> value_iterator {
|
||||
return {raw_cend(), raw_cend()};
|
||||
}
|
||||
|
||||
class range {
|
||||
iter b_;
|
||||
iter e_;
|
||||
friend auto extract(range &) -> extractor;
|
||||
[[nodiscard]] auto raw_cbegin() const -> iter { return b_; }
|
||||
[[nodiscard]] auto raw_cend() const -> iter { return e_; }
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto begin() const -> value_iterator {
|
||||
auto b = raw_cbegin();
|
||||
auto e = raw_cend();
|
||||
auto n = decode_range_header(b, e);
|
||||
return {b, e, n};
|
||||
}
|
||||
[[nodiscard]] auto end() const -> value_iterator {
|
||||
return {raw_cend(), raw_cend()};
|
||||
}
|
||||
|
||||
auto is_null() -> bool {
|
||||
auto b = raw_cbegin();
|
||||
return decode_range_header(b, raw_cend()) == 0;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
[[nodiscard]] auto operator()(Ts &&...parms) const -> bool {
|
||||
auto b = raw_cbegin();
|
||||
return match_array(b, raw_cend(), std::forward<Ts>(parms)...);
|
||||
}
|
||||
[[nodiscard]] auto to_json() const -> std::string;
|
||||
auto to_json(std::ostream &) const -> void;
|
||||
auto to_cbor(buffer &b) const -> buffer & {
|
||||
b.insert(b.raw_cend(), raw_cbegin(), raw_cend());
|
||||
return b;
|
||||
}
|
||||
auto operator==(const range &b) const -> bool {
|
||||
using std::equal;
|
||||
return equal(b.raw_cbegin(), b.raw_cend(), raw_cbegin(), raw_cend());
|
||||
}
|
||||
};
|
||||
friend class range;
|
||||
friend struct value_accessor;
|
||||
friend auto extract(range &) -> extractor;
|
||||
template <typename... Ts> friend auto A(Ts &&...parms) -> buffer::extractor;
|
||||
template <typename... Ts> friend auto M(Ts &&...parms) -> buffer::extractor;
|
||||
};
|
||||
|
||||
template <typename... Ts> auto array(Ts &&...parms) -> buffer {
|
||||
buffer b;
|
||||
b.reserve(buffer_reserve);
|
||||
b.array(std::forward<Ts>(parms)...);
|
||||
return b;
|
||||
}
|
||||
|
||||
template <typename... Ts> auto map(Ts &&...parms) -> buffer {
|
||||
buffer b;
|
||||
b.reserve(buffer_reserve);
|
||||
b.map(std::forward<Ts>(parms)...);
|
||||
return b;
|
||||
}
|
||||
|
||||
auto extract(type &) -> buffer::extractor;
|
||||
auto extract(int64_t &) -> buffer::extractor;
|
||||
auto extract(unsigned long long &) -> buffer::extractor;
|
||||
auto extract(uint64_t &) -> buffer::extractor;
|
||||
auto extract(int32_t &) -> buffer::extractor;
|
||||
auto extract(uint32_t &) -> buffer::extractor;
|
||||
auto extract(int16_t &) -> buffer::extractor;
|
||||
auto extract(uint16_t &) -> buffer::extractor;
|
||||
auto extract(int8_t &) -> buffer::extractor;
|
||||
auto extract(uint8_t &) -> buffer::extractor;
|
||||
auto extract(bool &) -> buffer::extractor;
|
||||
auto extract(std::string &) -> buffer::extractor;
|
||||
auto extract(std::string_view &) -> buffer::extractor;
|
||||
auto extract(buffer::range &) -> buffer::extractor;
|
||||
|
||||
template <typename... Ts> auto A(Ts &&...parms) -> buffer::extractor {
|
||||
return [=](buffer::iter &b, const buffer::iter &e) mutable {
|
||||
return buffer::match_array(b, e, std::forward<Ts>(parms)...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename... Ts> auto M(Ts &&...parms) -> buffer::extractor {
|
||||
return [=](buffer::iter &b, const buffer::iter &e) mutable {
|
||||
return buffer::match_map(b, e, std::forward<Ts>(parms)...);
|
||||
};
|
||||
}
|
||||
|
||||
inline auto operator<<(std::ostream &s, const buffer &b) -> std::ostream & {
|
||||
b.to_json(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline auto operator<<(std::ostream &s, const buffer::range &b)
|
||||
-> std::ostream & {
|
||||
b.to_json(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace cbor
|
28
include/cbor/cbor_in.hpp
Normal file
28
include/cbor/cbor_in.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "cbor.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <netinet/in.h>
|
||||
|
||||
namespace cbor {
|
||||
|
||||
template <> inline auto buffer::push<in6_addr>(const in6_addr &a) -> buffer & {
|
||||
push(std::string_view(reinterpret_cast<const char *>(&a), // NOLINT
|
||||
sizeof(a)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline auto extract(in6_addr &a) -> cbor::buffer::extractor {
|
||||
return [&a](auto &b, const auto &e) {
|
||||
std::string_view s;
|
||||
auto ret = cbor::extract(s)(b, e);
|
||||
if (ret && s.size() == sizeof(in6_addr)) {
|
||||
std::memcpy(&a, s.data(), sizeof(in6_addr));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace cbor
|
13
include/thespian/backtrace.h
Normal file
13
include/thespian/backtrace.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void install_debugger();
|
||||
void install_backtrace();
|
||||
void install_jitdebugger();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
39
include/thespian/c/context.h
Normal file
39
include/thespian/c/context.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <thespian/c/env.h>
|
||||
#include <thespian/c/handle.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*thespian_last_exit_handler)();
|
||||
typedef void (*thespian_context_destroy)(void *);
|
||||
|
||||
typedef void *thespian_behaviour_state;
|
||||
typedef void *thespian_exit_handler_state;
|
||||
typedef thespian_result (*thespian_behaviour)(thespian_behaviour_state);
|
||||
typedef void (*thespian_exit_handler)(thespian_exit_handler_state,
|
||||
const char *msg, size_t len);
|
||||
|
||||
struct thespian_context_t;
|
||||
typedef struct thespian_context_t *thespian_context;
|
||||
|
||||
thespian_context thespian_context_create(thespian_context_destroy *);
|
||||
void thespian_context_run(thespian_context);
|
||||
void thespian_context_on_last_exit(thespian_context,
|
||||
thespian_last_exit_handler);
|
||||
|
||||
int thespian_context_spawn_link(thespian_context, thespian_behaviour,
|
||||
thespian_behaviour_state, thespian_exit_handler,
|
||||
thespian_exit_handler_state, const char *name,
|
||||
thespian_env, thespian_handle *);
|
||||
|
||||
const char *thespian_get_last_error();
|
||||
void thespian_set_last_error(const char *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using)
|
43
include/thespian/c/env.h
Normal file
43
include/thespian/c/env.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <thespian/c/handle.h>
|
||||
#include <thespian/c/string_view.h>
|
||||
#include <thespian/c/trace.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct thespian_env_t;
|
||||
typedef struct
|
||||
thespian_env_t *thespian_env;
|
||||
|
||||
void thespian_env_enable_all_channels(thespian_env);
|
||||
void thespian_env_disable_all_channels(thespian_env);
|
||||
void thespian_env_enable(thespian_env, thespian_trace_channel);
|
||||
void thespian_env_disable(thespian_env, thespian_trace_channel);
|
||||
bool thespian_env_enabled(thespian_env, thespian_trace_channel);
|
||||
void thespian_env_on_trace(thespian_env, thespian_trace_handler);
|
||||
void thespian_env_trace(thespian_env, cbor_buffer);
|
||||
|
||||
bool thespian_env_is(thespian_env, c_string_view key);
|
||||
void thespian_env_set(thespian_env, c_string_view key, bool);
|
||||
int64_t thespian_env_num(thespian_env, c_string_view key);
|
||||
void thespian_env_num_set(thespian_env, c_string_view key, int64_t);
|
||||
c_string_view thespian_env_str(thespian_env, c_string_view key);
|
||||
void thespian_env_str_set(thespian_env, c_string_view key,
|
||||
c_string_view);
|
||||
thespian_handle thespian_env_proc(thespian_env, c_string_view key);
|
||||
void thespian_env_proc_set(thespian_env, c_string_view key,
|
||||
thespian_handle);
|
||||
|
||||
thespian_env thespian_env_get();
|
||||
thespian_env thespian_env_new();
|
||||
thespian_env thespian_env_clone(thespian_env);
|
||||
void thespian_env_destroy(thespian_env);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using)
|
19
include/thespian/c/file_descriptor.h
Normal file
19
include/thespian/c/file_descriptor.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct thespian_file_descriptor_handle;
|
||||
struct thespian_file_descriptor_handle *
|
||||
thespian_file_descriptor_create(const char* tag, int fd);
|
||||
int thespian_file_descriptor_wait_write(struct thespian_file_descriptor_handle *);
|
||||
int thespian_file_descriptor_wait_read(struct thespian_file_descriptor_handle *);
|
||||
int thespian_file_descriptor_cancel(struct thespian_file_descriptor_handle *);
|
||||
void thespian_file_descriptor_destroy(struct thespian_file_descriptor_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type)
|
29
include/thespian/c/handle.h
Normal file
29
include/thespian/c/handle.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "thespian/c/string_view.h"
|
||||
#include <cbor/c/cbor.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-*, hicpp-*)
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef cbor_buffer thespian_error;
|
||||
typedef thespian_error *thespian_result;
|
||||
|
||||
struct thespian_handle_t;
|
||||
typedef struct thespian_handle_t *thespian_handle;
|
||||
|
||||
thespian_handle thespian_handle_clone(thespian_handle);
|
||||
void thespian_handle_destroy(thespian_handle);
|
||||
|
||||
thespian_result thespian_handle_send_raw(thespian_handle, cbor_buffer);
|
||||
thespian_result thespian_handle_send_exit(thespian_handle, c_string_view);
|
||||
bool thespian_handle_is_expired(thespian_handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-*, hicpp-*)
|
29
include/thespian/c/instance.h
Normal file
29
include/thespian/c/instance.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "context.h"
|
||||
#include "env.h"
|
||||
#include "handle.h"
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef thespian_result (*thespian_receiver)(thespian_behaviour_state,
|
||||
thespian_handle from, cbor_buffer);
|
||||
|
||||
void thespian_receive(thespian_receiver, thespian_behaviour_state);
|
||||
|
||||
bool thespian_get_trap();
|
||||
bool thespian_set_trap(bool);
|
||||
void thespian_link(thespian_handle);
|
||||
|
||||
thespian_handle thespian_self();
|
||||
|
||||
int thespian_spawn_link(thespian_behaviour, thespian_behaviour_state,
|
||||
const char *name, thespian_env, thespian_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using)
|
20
include/thespian/c/metronome.h
Normal file
20
include/thespian/c/metronome.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct thespian_metronome_handle;
|
||||
struct thespian_metronome_handle *
|
||||
thespian_metronome_create_ms(unsigned long ms);
|
||||
struct thespian_metronome_handle *
|
||||
thespian_metronome_create_us(unsigned long us);
|
||||
int thespian_metronome_start(struct thespian_metronome_handle *);
|
||||
int thespian_metronome_stop(struct thespian_metronome_handle *);
|
||||
void thespian_metronome_destroy(struct thespian_metronome_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type)
|
19
include/thespian/c/signal.h
Normal file
19
include/thespian/c/signal.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/c/cbor.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct thespian_signal_handle;
|
||||
struct thespian_signal_handle *
|
||||
thespian_signal_create(int signum, cbor_buffer m);
|
||||
int thespian_signal_cancel(struct thespian_signal_handle *);
|
||||
void thespian_signal_destroy(struct thespian_signal_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type)
|
19
include/thespian/c/string_view.h
Normal file
19
include/thespian/c/string_view.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
// NOLINTBEGIN(modernize-*, hicpp-*)
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct c_string_view_t {
|
||||
const char *base;
|
||||
size_t len;
|
||||
};
|
||||
typedef struct c_string_view_t c_string_view;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-*, hicpp-*)
|
21
include/thespian/c/timeout.h
Normal file
21
include/thespian/c/timeout.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/c/cbor.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct thespian_timeout_handle;
|
||||
struct thespian_timeout_handle *
|
||||
thespian_timeout_create_ms(unsigned long ms, cbor_buffer m);
|
||||
struct thespian_timeout_handle *
|
||||
thespian_timeout_create_us(unsigned long us, cbor_buffer m);
|
||||
int thespian_timeout_cancel(struct thespian_timeout_handle *);
|
||||
void thespian_timeout_destroy(struct thespian_timeout_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type)
|
40
include/thespian/c/trace.h
Normal file
40
include/thespian/c/trace.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/c/cbor.h>
|
||||
|
||||
// NOLINTBEGIN(modernize-*, hicpp-*)
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int thespian_trace_channel_set;
|
||||
|
||||
typedef int thespian_trace_channel;
|
||||
|
||||
static const thespian_trace_channel thespian_trace_channel_send = 1;
|
||||
static const thespian_trace_channel thespian_trace_channel_receive = 2;
|
||||
static const thespian_trace_channel thespian_trace_channel_lifetime = 4;
|
||||
static const thespian_trace_channel thespian_trace_channel_link = 8;
|
||||
static const thespian_trace_channel thespian_trace_channel_execute = 16;
|
||||
static const thespian_trace_channel thespian_trace_channel_udp = 32;
|
||||
static const thespian_trace_channel thespian_trace_channel_tcp = 64;
|
||||
static const thespian_trace_channel thespian_trace_channel_timer = 128;
|
||||
static const thespian_trace_channel thespian_trace_channel_metronome = 256;
|
||||
static const thespian_trace_channel thespian_trace_channel_endpoint = 512;
|
||||
static const thespian_trace_channel thespian_trace_channel_signal = 1024;
|
||||
static const thespian_trace_channel thespian_trace_channel_all = INT_MAX;
|
||||
|
||||
typedef void (*thespian_trace_handler)(cbor_buffer);
|
||||
|
||||
void thespian_on_trace(thespian_trace_handler);
|
||||
void thespian_trace_to_json_file(const char *file_name);
|
||||
void thespian_trace_to_cbor_file(const char *file_name);
|
||||
void thespian_trace_to_mermaid_file(const char *file_name);
|
||||
void thespian_trace_to_trace(thespian_trace_channel default_channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-*, hicpp-*)
|
46
include/thespian/context.hpp
Normal file
46
include/thespian/context.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "env.hpp"
|
||||
#include "handle.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
using behaviour = std::function<auto()->result>;
|
||||
using exit_handler = std::function<void(std::string_view)>;
|
||||
|
||||
struct context {
|
||||
using dtor = void (*)(context *);
|
||||
using ref = std::unique_ptr<context, dtor>;
|
||||
using last_exit_handler = std::function<void()>;
|
||||
|
||||
[[nodiscard]] static auto create() -> ref;
|
||||
[[nodiscard]] static auto create(dtor *) -> context *;
|
||||
void run();
|
||||
void on_last_exit(last_exit_handler);
|
||||
|
||||
[[nodiscard]] auto spawn(behaviour b, std::string_view name)
|
||||
-> expected<handle, error>;
|
||||
[[nodiscard]] auto spawn(behaviour b, std::string_view name, env_t env)
|
||||
-> expected<handle, error>;
|
||||
[[nodiscard]] auto spawn_link(behaviour b, exit_handler eh,
|
||||
std::string_view name)
|
||||
-> expected<handle, error>;
|
||||
[[nodiscard]] auto spawn_link(behaviour b, exit_handler eh,
|
||||
std::string_view name, env_t env)
|
||||
-> expected<handle, error>;
|
||||
|
||||
protected:
|
||||
context() = default;
|
||||
~context() = default;
|
||||
|
||||
public:
|
||||
context(const context &) = delete;
|
||||
context(context &&) = delete;
|
||||
void operator=(const context &) = delete;
|
||||
void operator=(context &&) = delete;
|
||||
};
|
||||
|
||||
} // namespace thespian
|
22
include/thespian/debug.hpp
Normal file
22
include/thespian/debug.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
#include "handle.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
using port_t = unsigned short;
|
||||
|
||||
namespace thespian::debug {
|
||||
|
||||
auto enable(context &) -> void;
|
||||
auto disable(context &) -> void;
|
||||
auto isenabled(context &) -> bool;
|
||||
|
||||
namespace tcp {
|
||||
|
||||
auto create(context &, port_t port, const std::string &prompt)
|
||||
-> expected<handle, error>;
|
||||
|
||||
} // namespace tcp
|
||||
} // namespace thespian::debug
|
33
include/thespian/endpoint.hpp
Normal file
33
include/thespian/endpoint.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "handle.hpp"
|
||||
#include "tcp.hpp"
|
||||
#include "unx.hpp"
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
using port_t = unsigned short;
|
||||
|
||||
namespace thespian::endpoint {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace tcp {
|
||||
|
||||
auto listen(in6_addr, port_t) -> expected<handle, error>;
|
||||
auto connect(in6_addr, port_t, std::chrono::milliseconds retry_time = 50ms,
|
||||
size_t retry_count = 5) -> expected<handle, error>;
|
||||
|
||||
} // namespace tcp
|
||||
|
||||
namespace unx {
|
||||
using thespian::unx::mode;
|
||||
|
||||
auto listen(std::string_view, mode = mode::abstract) -> expected<handle, error>;
|
||||
auto connect(std::string_view, mode = mode::abstract,
|
||||
std::chrono::milliseconds retry_time = 50ms,
|
||||
size_t retry_count = 5) -> expected<handle, error>;
|
||||
|
||||
} // namespace unx
|
||||
|
||||
} // namespace thespian::endpoint
|
51
include/thespian/env.hpp
Normal file
51
include/thespian/env.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "handle.hpp"
|
||||
#include "trace.hpp"
|
||||
|
||||
#include <any>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct env_t {
|
||||
void enable_all_channels() {
|
||||
trace_channels = static_cast<channel_set>(channel::all);
|
||||
}
|
||||
void disable_all_channels() { trace_channels = 0; }
|
||||
void enable(channel c) { trace_channels |= static_cast<channel_set>(c); }
|
||||
void disable(channel c) { trace_channels ^= static_cast<channel_set>(c); }
|
||||
auto enabled(channel c) -> bool {
|
||||
return trace_channels & static_cast<channel_set>(c);
|
||||
}
|
||||
|
||||
auto on_trace(trace_handler h) {
|
||||
std::swap(h, trace_handler);
|
||||
return h;
|
||||
}
|
||||
|
||||
auto trace(const cbor::buffer &msg) const {
|
||||
if (trace_handler)
|
||||
trace_handler(msg);
|
||||
}
|
||||
|
||||
auto is(std::string_view k) -> bool & { return b[std::string(k)]; }
|
||||
auto num(std::string_view k) -> int64_t & { return i[std::string(k)]; }
|
||||
auto str(std::string_view k) -> std::string & { return s[std::string(k)]; }
|
||||
auto proc(std::string_view k) -> handle & { return h[std::string(k)]; }
|
||||
[[nodiscard]] auto proc(std::string_view k) const -> const handle & {
|
||||
return h.at(std::string(k));
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> s;
|
||||
std::map<std::string, bool> b;
|
||||
std::map<std::string, int64_t> i;
|
||||
std::map<std::string, handle> h;
|
||||
channel_set trace_channels{0};
|
||||
trace_handler trace_handler;
|
||||
};
|
||||
auto env() -> env_t &;
|
||||
|
||||
} // namespace thespian
|
1716
include/thespian/expected.hpp
Normal file
1716
include/thespian/expected.hpp
Normal file
File diff suppressed because it is too large
Load diff
32
include/thespian/file_descriptor.hpp
Normal file
32
include/thespian/file_descriptor.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct file_descriptor_impl;
|
||||
using file_descriptor_dtor = void (*)(file_descriptor_impl *);
|
||||
using file_descriptor_ref =
|
||||
std::unique_ptr<file_descriptor_impl, file_descriptor_dtor>;
|
||||
|
||||
struct file_descriptor {
|
||||
static auto create(std::string_view tag, int fd) -> file_descriptor;
|
||||
auto wait_write() -> void;
|
||||
auto wait_read() -> void;
|
||||
auto cancel() -> void;
|
||||
static void wait_write(file_descriptor_impl *);
|
||||
static void wait_read(file_descriptor_impl *);
|
||||
static void cancel(file_descriptor_impl *);
|
||||
static void destroy(file_descriptor_impl *);
|
||||
|
||||
//->("fd", tag, "write_ready")
|
||||
//->("fd", tag, "write_error", int err, string message)
|
||||
//->("fd", tag, "read_ready")
|
||||
//->("fd", tag, "read_error", int err, string message)
|
||||
|
||||
file_descriptor_ref ref;
|
||||
};
|
||||
|
||||
} // namespace thespian
|
49
include/thespian/handle.hpp
Normal file
49
include/thespian/handle.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/cbor.hpp>
|
||||
|
||||
#include "expected.hpp"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
template <typename T, typename E>
|
||||
using expected = std::experimental::expected<T, E>;
|
||||
using error = cbor::buffer;
|
||||
|
||||
using result = expected<void, error>;
|
||||
using to_error = std::experimental::unexpected<error>;
|
||||
|
||||
[[nodiscard]] inline auto ok() -> result { return result{}; }
|
||||
template <typename T> [[nodiscard]] auto ok(T v) { return result{v}; }
|
||||
template <typename T> [[nodiscard]] auto to_result(T ret) -> result {
|
||||
if (ret)
|
||||
return ok();
|
||||
return to_error(ret.error());
|
||||
}
|
||||
|
||||
struct instance;
|
||||
using ref = std::weak_ptr<instance>;
|
||||
|
||||
struct handle {
|
||||
[[nodiscard]] auto send_raw(cbor::buffer) const -> result;
|
||||
template <typename... Ts> [[nodiscard]] auto send(Ts &&...parms) const {
|
||||
return send_raw(cbor::array(std::forward<Ts>(parms)...));
|
||||
}
|
||||
template <typename... Ts>
|
||||
auto exit(const std::string &error, Ts &&...details) const {
|
||||
return send_raw(cbor::array("exit", error, std::forward<Ts>(details)...));
|
||||
}
|
||||
[[nodiscard]] inline auto expired() const { return ref_.expired(); }
|
||||
|
||||
private:
|
||||
ref ref_;
|
||||
friend auto handle_ref(handle &) -> ref &;
|
||||
friend auto handle_ref(const handle &) -> const ref &;
|
||||
friend auto operator==(const handle &, const handle &) -> bool;
|
||||
};
|
||||
|
||||
auto operator==(const handle &, const handle &) -> bool;
|
||||
|
||||
} // namespace thespian
|
46
include/thespian/hub.hpp
Normal file
46
include/thespian/hub.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "handle.hpp"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct hub : private handle {
|
||||
using filter = std::function<bool(const cbor::buffer &)>;
|
||||
|
||||
hub() = default;
|
||||
auto expired() -> bool { return handle::expired(); }
|
||||
|
||||
template <typename... Ts> auto broadcast(Ts &&...parms) const {
|
||||
return send("broadcast", cbor::array(std::forward<Ts>(parms)...));
|
||||
}
|
||||
[[nodiscard]] auto subscribe() const -> result;
|
||||
[[nodiscard]] auto subscribe(filter) const -> result;
|
||||
[[nodiscard]] auto listen(std::string_view unix_socket_descriptor) const
|
||||
-> result;
|
||||
[[nodiscard]] auto shutdown() const { return send("shutdown"); }
|
||||
|
||||
[[nodiscard]] static auto create(std::string_view name)
|
||||
-> expected<hub, error>;
|
||||
|
||||
struct pipe {
|
||||
pipe();
|
||||
pipe(const pipe &) = delete;
|
||||
pipe(pipe &&) = delete;
|
||||
auto operator=(const pipe &) -> pipe & = delete;
|
||||
auto operator=(pipe &&) -> pipe & = delete;
|
||||
virtual ~pipe();
|
||||
};
|
||||
|
||||
private:
|
||||
hub(const handle &, std::shared_ptr<pipe>);
|
||||
std::shared_ptr<pipe> pipe_;
|
||||
friend auto operator==(const handle &, const hub &) -> bool;
|
||||
friend auto operator==(const hub &, const handle &) -> bool;
|
||||
};
|
||||
|
||||
auto operator==(const handle &, const hub &) -> bool;
|
||||
auto operator==(const hub &, const handle &) -> bool;
|
||||
|
||||
} // namespace thespian
|
51
include/thespian/instance.hpp
Normal file
51
include/thespian/instance.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
#include "env.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "trace.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
using receiver = std::function<result(handle from, cbor::buffer)>;
|
||||
using sync_receiver = std::function<cbor::buffer(handle from, cbor::buffer)>;
|
||||
|
||||
[[nodiscard]] auto send_raw(cbor::buffer) -> result;
|
||||
template <typename... Ts> [[nodiscard]] auto send(Ts &&...parms) -> result {
|
||||
return send_raw(cbor::array(std::forward<Ts>(parms)...));
|
||||
}
|
||||
auto receive(receiver) -> void;
|
||||
auto receive_sync(sync_receiver) -> void;
|
||||
|
||||
auto trap() -> bool;
|
||||
auto trap(bool) -> bool;
|
||||
auto link(const handle &) -> void;
|
||||
|
||||
auto self() -> handle;
|
||||
auto self_ref() -> handle&;
|
||||
|
||||
[[nodiscard]] auto spawn(behaviour, std::string_view name)
|
||||
-> expected<handle, error>;
|
||||
[[nodiscard]] auto spawn_link(behaviour, std::string_view name)
|
||||
-> expected<handle, error>;
|
||||
|
||||
[[nodiscard]] auto spawn(behaviour, std::string_view name, env_t)
|
||||
-> expected<handle, error>;
|
||||
[[nodiscard]] auto spawn_link(behaviour, std::string_view name, env_t)
|
||||
-> expected<handle, error>;
|
||||
|
||||
[[nodiscard]] auto ok() -> result;
|
||||
[[nodiscard]] auto exit() -> result;
|
||||
[[nodiscard]] auto exit(std::string_view e) -> result;
|
||||
[[nodiscard]] auto exit(std::string_view e, std::string_view m) -> result;
|
||||
[[nodiscard]] auto exit(std::string_view e, int err) -> result;
|
||||
[[nodiscard]] auto exit(std::string_view e, size_t n) -> result;
|
||||
|
||||
[[nodiscard]] auto unexpected(const cbor::buffer & /*b*/) -> result;
|
||||
|
||||
} // namespace thespian
|
25
include/thespian/metronome.hpp
Normal file
25
include/thespian/metronome.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct metronome_impl;
|
||||
using metronome_dtor = void (*)(metronome_impl *);
|
||||
using metronome_ref = std::unique_ptr<metronome_impl, metronome_dtor>;
|
||||
|
||||
struct metronome {
|
||||
auto start() -> void;
|
||||
auto stop() -> void;
|
||||
|
||||
metronome_ref ref;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto create_metronome(std::chrono::microseconds us) -> metronome;
|
||||
void start_metronome(metronome_impl *);
|
||||
void stop_metronome(metronome_impl *);
|
||||
void destroy_metronome(metronome_impl *);
|
||||
|
||||
} // namespace thespian
|
25
include/thespian/signal.hpp
Normal file
25
include/thespian/signal.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/cbor.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct signal_impl;
|
||||
using signal_dtor = void (*)(signal_impl *);
|
||||
using signal_ref = std::unique_ptr<signal_impl, signal_dtor>;
|
||||
|
||||
struct signal {
|
||||
auto cancel() -> void;
|
||||
|
||||
signal_ref ref;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto create_signal(int signum, cbor::buffer m) -> signal;
|
||||
void cancel_signal(signal_impl *);
|
||||
void destroy_signal(signal_impl *);
|
||||
|
||||
} // namespace thespian
|
29
include/thespian/socket.hpp
Normal file
29
include/thespian/socket.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct socket_impl;
|
||||
using socket_dtor = void (*)(socket_impl *);
|
||||
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
|
||||
|
||||
struct socket {
|
||||
static auto create(std::string_view tag, int fd) -> socket;
|
||||
auto write(std::string_view) -> void;
|
||||
auto write(const std::vector<uint8_t> &) -> void;
|
||||
auto read() -> void;
|
||||
auto close() -> void;
|
||||
|
||||
//->("socket", tag, "write_complete", int written)
|
||||
//->("socket", tag, "write_error", int err, string message)
|
||||
//->("socket", tag, "read_complete", string buf)
|
||||
//->("socket", tag, "read_error", int err, string message)
|
||||
//->("socket", tag, "closed")
|
||||
|
||||
socket_ref ref;
|
||||
};
|
||||
|
||||
} // namespace thespian
|
47
include/thespian/tcp.hpp
Normal file
47
include/thespian/tcp.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <netinet/in.h>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
using port_t = unsigned short;
|
||||
|
||||
namespace thespian::tcp {
|
||||
|
||||
struct acceptor_impl;
|
||||
using acceptor_dtor = void (*)(acceptor_impl *);
|
||||
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
|
||||
|
||||
struct acceptor {
|
||||
static auto create(std::string_view tag) -> acceptor;
|
||||
auto listen(in6_addr ip, port_t port) -> port_t;
|
||||
auto close() -> void;
|
||||
|
||||
//->("acceptor", tag, "accept", int fd, in6_addr ip, port_t port)
|
||||
//->("acceptor", tag, "error", int err, string message)
|
||||
//->("acceptor", tag, "closed")
|
||||
|
||||
acceptor_ref ref;
|
||||
};
|
||||
|
||||
struct connector_impl;
|
||||
using connector_dtor = void (*)(connector_impl *);
|
||||
using connector_ref = std::unique_ptr<connector_impl, connector_dtor>;
|
||||
|
||||
struct connector {
|
||||
static auto create(std::string_view tag) -> connector;
|
||||
auto connect(in6_addr ip, port_t port) -> void;
|
||||
auto connect(in6_addr ip, port_t port, in6_addr lip) -> void;
|
||||
auto connect(in6_addr ip, port_t port, port_t lport) -> void;
|
||||
auto connect(in6_addr ip, port_t port, in6_addr lip, port_t lport) -> void;
|
||||
auto cancel() -> void;
|
||||
|
||||
//->("connector", tag, "connected", int fd)
|
||||
//->("connector", tag, "error", int err, string message)
|
||||
//->("connector", tag, "cancelled")
|
||||
|
||||
connector_ref ref;
|
||||
};
|
||||
|
||||
} // namespace thespian::tcp
|
28
include/thespian/timeout.hpp
Normal file
28
include/thespian/timeout.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/cbor.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct timeout_impl;
|
||||
using timeout_dtor = void (*)(timeout_impl *);
|
||||
using timeout_ref = std::unique_ptr<timeout_impl, timeout_dtor>;
|
||||
|
||||
struct timeout {
|
||||
auto cancel() -> void;
|
||||
|
||||
timeout_ref ref;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto create_timeout(std::chrono::microseconds us, cbor::buffer m)
|
||||
-> timeout;
|
||||
|
||||
[[nodiscard]] auto never() -> timeout;
|
||||
void cancel_timeout(timeout_impl *);
|
||||
void destroy_timeout(timeout_impl *);
|
||||
|
||||
} // namespace thespian
|
37
include/thespian/trace.hpp
Normal file
37
include/thespian/trace.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "thespian/handle.hpp"
|
||||
#include <cbor/cbor.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace thespian {
|
||||
|
||||
using channel_set = int;
|
||||
enum class channel {
|
||||
send = 1,
|
||||
receive = 2,
|
||||
lifetime = 4,
|
||||
link = 8,
|
||||
execute = 16,
|
||||
udp = 32,
|
||||
tcp = 64,
|
||||
timer = 128,
|
||||
metronome = 256,
|
||||
endpoint = 512,
|
||||
signal = 1024,
|
||||
all = std::numeric_limits<channel_set>::max(),
|
||||
};
|
||||
|
||||
using trace_handler = std::function<void(const cbor::buffer &)>;
|
||||
|
||||
auto on_trace(trace_handler) -> trace_handler;
|
||||
auto trace_to_json_file(const std::string &file_name) -> void;
|
||||
auto trace_to_cbor_file(const std::string &file_name) -> void;
|
||||
auto trace_to_mermaid_file(const std::string &file_name) -> void;
|
||||
auto trace_to_trace(int default_channel) -> void;
|
||||
|
||||
} // namespace thespian
|
33
include/thespian/udp.hpp
Normal file
33
include/thespian/udp.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <cbor/cbor_in.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
using port_t = unsigned short;
|
||||
|
||||
namespace thespian {
|
||||
|
||||
struct udp_impl;
|
||||
using udp_dtor = void (*)(udp_impl *);
|
||||
using udp_ref = std::unique_ptr<udp_impl, udp_dtor>;
|
||||
|
||||
struct udp {
|
||||
static auto create(std::string tag) -> udp;
|
||||
|
||||
auto open(const in6_addr &, port_t port) -> port_t;
|
||||
[[nodiscard]] auto sendto(std::string_view, in6_addr ip, port_t port)
|
||||
-> size_t;
|
||||
auto close() -> void;
|
||||
|
||||
//->("udp", tag, "open_error", int err, string message);
|
||||
//->("udp", tag, "read_error", int err, string message);
|
||||
//->("udp", tag, "receive", string data, in6_addr remote_ip, int port);
|
||||
//->("udp", tag, "closed");
|
||||
|
||||
udp_ref ref;
|
||||
};
|
||||
|
||||
} // namespace thespian
|
43
include/thespian/unx.hpp
Normal file
43
include/thespian/unx.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace thespian::unx {
|
||||
|
||||
enum class mode { file, abstract };
|
||||
|
||||
struct acceptor_impl;
|
||||
using acceptor_dtor = void (*)(acceptor_impl *);
|
||||
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
|
||||
|
||||
struct acceptor {
|
||||
static auto create(std::string_view tag) -> acceptor;
|
||||
auto listen(std::string_view, mode) -> void;
|
||||
auto close() -> void;
|
||||
|
||||
//->("acceptor", tag, "accept", int fd)
|
||||
//->("acceptor", tag, "error", int err, string message)
|
||||
//->("acceptor",tag, "closed")
|
||||
|
||||
acceptor_ref ref;
|
||||
};
|
||||
|
||||
struct connector_impl;
|
||||
using connector_dtor = void (*)(connector_impl *);
|
||||
using connector_ref = std::unique_ptr<connector_impl, connector_dtor>;
|
||||
|
||||
struct connector {
|
||||
static auto create(std::string_view tag) -> connector;
|
||||
auto connect(std::string_view, mode) -> void;
|
||||
auto cancel() -> void;
|
||||
|
||||
//->("connector", tag, "connected", int fd)
|
||||
//->("connector", tag, "error", int err, string message)
|
||||
//->("connector", tag, "cancelled")
|
||||
|
||||
connector_ref ref;
|
||||
};
|
||||
|
||||
} // namespace thespian::unx
|
102
src/backtrace.cpp
Normal file
102
src/backtrace.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void msg(const char *msg, const char *arg) {
|
||||
if (write(STDERR_FILENO, msg, strlen(msg)) != 0) {
|
||||
}
|
||||
if (write(STDERR_FILENO, arg, strlen(arg)) != 0) {
|
||||
}
|
||||
if (write(STDERR_FILENO, "\n", 1) != 0) {
|
||||
}
|
||||
}
|
||||
|
||||
static char binpath[512]; // NOLINT
|
||||
static char pid_s[11]; // NOLINT
|
||||
|
||||
static void get_pid_binpath() {
|
||||
pid_t pid = getpid();
|
||||
sprintf(pid_s, "%d", pid); // NOLINT
|
||||
size_t ret = readlink("/proc/self/exe", binpath, 511);
|
||||
if (ret < 0)
|
||||
return;
|
||||
binpath[ret] = 0; // NOLINT
|
||||
}
|
||||
|
||||
static const auto lldb{"/usr/bin/lldb"};
|
||||
static const auto default_debugger{"/usr/bin/gdbserver"};
|
||||
|
||||
static auto get_debugger() -> const char * {
|
||||
const char *debugger = secure_getenv("JITDEBUG");
|
||||
if (not debugger)
|
||||
return default_debugger;
|
||||
if (strcmp(debugger, "on") == 0)
|
||||
return default_debugger;
|
||||
if (strcmp(debugger, "1") == 0)
|
||||
return default_debugger;
|
||||
if (strcmp(debugger, "true") == 0)
|
||||
return default_debugger;
|
||||
return debugger;
|
||||
}
|
||||
const char *const debugger = get_debugger();
|
||||
|
||||
void start_debugger(const char * dbg, const char **argv) {
|
||||
#if defined(PR_SET_PTRACER)
|
||||
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); // NOLINT
|
||||
#endif
|
||||
int child_pid = fork();
|
||||
if (!child_pid) {
|
||||
dup2(2, 1); // redirect output to stderr
|
||||
msg("debugging with ", dbg);
|
||||
execv(dbg, const_cast<char *const *>(argv)); // NOLINT
|
||||
_exit(1);
|
||||
} else {
|
||||
int stat(0);
|
||||
waitpid(child_pid, &stat, 0);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void sighdl_debugger(int no, siginfo_t * /*sigi*/, void * /*uco*/) {
|
||||
get_pid_binpath();
|
||||
const char *argv[] = {// NOLINT
|
||||
debugger, "--attach", "[::1]:7777", pid_s, nullptr};
|
||||
start_debugger(debugger, argv);
|
||||
(void)raise(no);
|
||||
}
|
||||
|
||||
extern "C" void sighdl_backtrace(int no, siginfo_t * /*sigi*/, void * /*uco*/) {
|
||||
get_pid_binpath();
|
||||
const char *argv[] = {// NOLINT
|
||||
lldb, "--batch", "-p", pid_s,
|
||||
"--one-line", "bt", binpath, nullptr};
|
||||
start_debugger(lldb, argv);
|
||||
(void)raise(no);
|
||||
}
|
||||
|
||||
static void install_crash_handler(void (*hdlr)(int, siginfo_t *, void *)) {
|
||||
struct sigaction action {};
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO | SA_RESETHAND;
|
||||
#ifdef SA_FULLDUMP
|
||||
action.sa_flags |= SA_FULLDUMP;
|
||||
#endif
|
||||
action.sa_sigaction = hdlr;
|
||||
sigaction(SIGBUS, &action, nullptr);
|
||||
sigaction(SIGSEGV, &action, nullptr);
|
||||
sigaction(SIGABRT, &action, nullptr);
|
||||
sigaction(SIGTRAP, &action, nullptr);
|
||||
sigaction(SIGFPE, &action, nullptr);
|
||||
}
|
||||
|
||||
extern "C" void install_debugger() { install_crash_handler(sighdl_debugger); }
|
||||
extern "C" void install_backtrace() { install_crash_handler(sighdl_backtrace); }
|
||||
extern "C" void install_jitdebugger() {
|
||||
if (secure_getenv("JITDEBUG"))
|
||||
install_debugger();
|
||||
else
|
||||
install_backtrace();
|
||||
}
|
66
src/c/context.cpp
Normal file
66
src/c/context.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "thespian/env.hpp"
|
||||
#include <__type_traits/is_swappable.h>
|
||||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/handle.h>
|
||||
#include <thespian/context.hpp>
|
||||
#include <thespian/handle.hpp>
|
||||
|
||||
using std::string_view;
|
||||
using thespian::context;
|
||||
|
||||
thread_local const char *last_error{}; // NOLINT
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_get_last_error() -> const char * { return last_error; }
|
||||
void thespian_set_last_error(const char *msg) { last_error = msg; }
|
||||
|
||||
thespian_context
|
||||
thespian_context_create(thespian_context_destroy *d) { // NOLINT
|
||||
return reinterpret_cast<thespian_context>( // NOLINT
|
||||
context::create(reinterpret_cast<context::dtor *>(d))); // NOLINT
|
||||
}
|
||||
|
||||
void thespian_context_run(thespian_context ctx) {
|
||||
reinterpret_cast<context *>(ctx)->run(); // NOLINT
|
||||
}
|
||||
|
||||
void thespian_context_on_last_exit(thespian_context ctx,
|
||||
thespian_last_exit_handler h) {
|
||||
reinterpret_cast<context *>(ctx)->on_last_exit(h); // NOLINT
|
||||
}
|
||||
|
||||
auto thespian_context_spawn_link(thespian_context ctx_, thespian_behaviour b,
|
||||
thespian_behaviour_state s,
|
||||
thespian_exit_handler eh,
|
||||
thespian_exit_handler_state es,
|
||||
const char *name, thespian_env env,
|
||||
thespian_handle *handle) -> int {
|
||||
auto *ctx = reinterpret_cast<context *>(ctx_); // NOLINT
|
||||
thespian::env_t empty_env_{};
|
||||
thespian::env_t &env_ =
|
||||
env ? *reinterpret_cast<thespian::env_t *>(env) : empty_env_; // NOLINT
|
||||
|
||||
auto ret = ctx->spawn_link(
|
||||
[b, s]() -> thespian::result {
|
||||
auto *ret = b(s);
|
||||
if (ret) {
|
||||
auto err = cbor::buffer();
|
||||
const uint8_t *data = ret->base; // NOLINT
|
||||
std::copy(data, data + ret->len, back_inserter(err)); // NOLINT
|
||||
return thespian::to_error(err);
|
||||
}
|
||||
return thespian::ok();
|
||||
},
|
||||
[eh, es](auto e) {
|
||||
if (eh)
|
||||
eh(es, e.data(), e.size());
|
||||
},
|
||||
string_view(name), std::move(env_));
|
||||
if (!ret)
|
||||
return -1;
|
||||
*handle = reinterpret_cast<thespian_handle>( // NOLINT
|
||||
new thespian::handle{ret.value()});
|
||||
return 0;
|
||||
}
|
||||
}
|
92
src/c/env.cpp
Normal file
92
src/c/env.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include <cbor/cbor.hpp>
|
||||
#include <thespian/c/env.h>
|
||||
#include <thespian/c/handle.h>
|
||||
#include <thespian/env.hpp>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
|
||||
auto to_env(thespian_env e) -> thespian::env_t & {
|
||||
assert(e);
|
||||
return *reinterpret_cast<thespian::env_t *>(e); // NOLINT
|
||||
}
|
||||
|
||||
auto destroy(thespian_env e) -> void {
|
||||
delete reinterpret_cast<thespian::env_t *>(e); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
void thespian_env_enable_all_channels(thespian_env e) {
|
||||
to_env(e).enable_all_channels();
|
||||
}
|
||||
|
||||
void thespian_env_disable_all_channels(thespian_env e) {
|
||||
to_env(e).disable_all_channels();
|
||||
}
|
||||
void thespian_env_enable(thespian_env e, thespian_trace_channel c) {
|
||||
to_env(e).enable(static_cast<thespian::channel>(c));
|
||||
}
|
||||
void thespian_env_disable(thespian_env e, thespian_trace_channel c) {
|
||||
to_env(e).disable(static_cast<thespian::channel>(c));
|
||||
}
|
||||
auto thespian_env_enabled(thespian_env e, thespian_trace_channel c) -> bool {
|
||||
return to_env(e).enabled(static_cast<thespian::channel>(c));
|
||||
}
|
||||
void thespian_env_on_trace(thespian_env e, thespian_trace_handler h) {
|
||||
to_env(e).on_trace([h](const cbor::buffer &m) { h({m.data(), m.size()}); });
|
||||
}
|
||||
void thespian_env_trace(thespian_env e, cbor_buffer m) {
|
||||
cbor::buffer buf;
|
||||
const uint8_t *data = m.base;
|
||||
std::copy(data, data + m.len, back_inserter(buf)); // NOLINT
|
||||
to_env(e).trace(buf);
|
||||
}
|
||||
|
||||
auto thespian_env_is(thespian_env e, c_string_view key) -> bool {
|
||||
return to_env(e).is({key.base, key.len});
|
||||
}
|
||||
void thespian_env_set(thespian_env e, c_string_view key, bool value) {
|
||||
to_env(e).is({key.base, key.len}) = value;
|
||||
}
|
||||
auto thespian_env_num(thespian_env e, c_string_view key) -> int64_t {
|
||||
return to_env(e).num({key.base, key.len});
|
||||
}
|
||||
void thespian_env_num_set(thespian_env e, c_string_view key, int64_t value) {
|
||||
to_env(e).num({key.base, key.len}) = value;
|
||||
}
|
||||
auto thespian_env_str(thespian_env e, c_string_view key) -> c_string_view {
|
||||
auto &ret = to_env(e).str({key.base, key.len});
|
||||
return {ret.data(), ret.size()};
|
||||
}
|
||||
void thespian_env_str_set(thespian_env e, c_string_view key,
|
||||
c_string_view value) {
|
||||
to_env(e).str({key.base, key.len}) = std::string_view{value.base, value.len};
|
||||
}
|
||||
auto thespian_env_proc(thespian_env e, c_string_view key) -> thespian_handle {
|
||||
auto &ret = to_env(e).proc({key.base, key.len});
|
||||
return reinterpret_cast<thespian_handle>(&ret); // NOLINT
|
||||
}
|
||||
void thespian_env_proc_set(thespian_env e, c_string_view key,
|
||||
thespian_handle value) {
|
||||
thespian::handle *h{reinterpret_cast<thespian::handle *>( // NOLINT
|
||||
value)};
|
||||
to_env(e).proc({key.base, key.len}) = *h;
|
||||
}
|
||||
auto thespian_env_clone(thespian_env e) -> thespian_env {
|
||||
return reinterpret_cast<thespian_env>( // NOLINT
|
||||
new thespian::env_t{to_env(e)});
|
||||
}
|
||||
void thespian_env_destroy(thespian_env e) { destroy(e); }
|
||||
|
||||
auto thespian_env_get() -> thespian_env {
|
||||
return reinterpret_cast<thespian_env>(&thespian::env()); // NOLINT
|
||||
}
|
||||
auto thespian_env_new() -> thespian_env {
|
||||
return reinterpret_cast<thespian_env>(new thespian::env_t); // NOLINT
|
||||
}
|
||||
}
|
76
src/c/file_descriptor.cpp
Normal file
76
src/c/file_descriptor.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/file_descriptor.h>
|
||||
#include <thespian/file_descriptor.hpp>
|
||||
|
||||
using thespian::file_descriptor;
|
||||
using thespian::file_descriptor_impl;
|
||||
using thespian::file_descriptor_ref;
|
||||
|
||||
// NOLINTBEGIN(*-reinterpret-cast, *-use-trailing-*)
|
||||
extern "C" {
|
||||
|
||||
auto thespian_file_descriptor_create(const char *tag, int fd)
|
||||
-> thespian_file_descriptor_handle * {
|
||||
try {
|
||||
auto *p = file_descriptor::create(tag, fd).ref.release();
|
||||
return reinterpret_cast<thespian_file_descriptor_handle *>(p);
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_file_descriptor_create error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int thespian_file_descriptor_wait_write(thespian_file_descriptor_handle *p) {
|
||||
try {
|
||||
file_descriptor::wait_write(reinterpret_cast<file_descriptor_impl *>(p));
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error(
|
||||
"unknown thespian_file_descriptor_wait_write error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int thespian_file_descriptor_wait_read(thespian_file_descriptor_handle *p) {
|
||||
try {
|
||||
file_descriptor::wait_read(reinterpret_cast<file_descriptor_impl *>(p));
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_file_descriptor_wait_read error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int thespian_file_descriptor_cancel(thespian_file_descriptor_handle *p) {
|
||||
try {
|
||||
file_descriptor::cancel(reinterpret_cast<file_descriptor_impl *>(p));
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_file_descriptor_wait_read error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void thespian_file_descriptor_destroy(thespian_file_descriptor_handle *p) {
|
||||
try {
|
||||
file_descriptor::destroy(reinterpret_cast<file_descriptor_impl *>(p));
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_file_descriptor_destroy error");
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOLINTEND(*-reinterpret-cast, *-use-trailing-*)
|
70
src/c/handle.cpp
Normal file
70
src/c/handle.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
#include <thespian/c/handle.h>
|
||||
#include <thespian/handle.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
static thread_local cbor::buffer
|
||||
message_buffer; // NOLINT(*-avoid-non-const-global-variables)
|
||||
static thread_local thespian_error
|
||||
error_buffer; // NOLINT(*-avoid-non-const-global-variables)
|
||||
|
||||
auto to_thespian_result(thespian::result r) -> thespian_result {
|
||||
if (r) {
|
||||
return nullptr;
|
||||
}
|
||||
message_buffer = r.error();
|
||||
error_buffer.base = message_buffer.data();
|
||||
error_buffer.len = message_buffer.size();
|
||||
return &error_buffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_handle_clone(thespian_handle h) -> thespian_handle {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
if (!h_)
|
||||
return nullptr;
|
||||
return reinterpret_cast<thespian_handle>( // NOLINT(*-reinterpret-cast)
|
||||
new thespian::handle{*h_});
|
||||
}
|
||||
|
||||
void thespian_handle_destroy(thespian_handle h) {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
delete h_;
|
||||
}
|
||||
|
||||
auto thespian_handle_send_raw(thespian_handle h, cbor_buffer m)
|
||||
-> thespian_result {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
cbor::buffer buf;
|
||||
const uint8_t *data = m.base;
|
||||
std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic)
|
||||
back_inserter(buf));
|
||||
return to_thespian_result(h_->send_raw(move(buf)));
|
||||
}
|
||||
|
||||
auto thespian_handle_send_exit(thespian_handle h, c_string_view err)
|
||||
-> thespian_result {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
return to_thespian_result(h_->exit(std::string(err.base, err.len)));
|
||||
}
|
||||
|
||||
auto thespian_handle_is_expired(thespian_handle h) -> bool {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
return h_->expired();
|
||||
}
|
||||
|
||||
}
|
65
src/c/instance.cpp
Normal file
65
src/c/instance.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "cbor/c/cbor.h"
|
||||
#include "thespian/c/handle.h"
|
||||
#include <thespian/c/instance.h>
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
using std::string_view;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void thespian_receive(thespian_receiver r, thespian_behaviour_state s) {
|
||||
thespian::receive([r, s](auto from, cbor::buffer msg) -> thespian::result {
|
||||
thespian_handle from_handle = reinterpret_cast<thespian_handle>( // NOLINT
|
||||
&from);
|
||||
auto *ret = r(s, from_handle, {msg.data(), msg.size()});
|
||||
if (ret) {
|
||||
auto err = cbor::buffer();
|
||||
const uint8_t *data = ret->base;
|
||||
std::copy(data, data + ret->len, back_inserter(err)); // NOLINT
|
||||
return thespian::to_error(err);
|
||||
}
|
||||
return thespian::ok();
|
||||
});
|
||||
}
|
||||
|
||||
auto thespian_get_trap() -> bool { return thespian::trap(); }
|
||||
auto thespian_set_trap(bool on) -> bool { return thespian::trap(on); }
|
||||
|
||||
void thespian_link(thespian_handle h) {
|
||||
thespian::handle *h_{
|
||||
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
|
||||
h)};
|
||||
thespian::link(*h_);
|
||||
}
|
||||
|
||||
auto thespian_self() -> thespian_handle {
|
||||
auto &self = thespian::self_ref();
|
||||
return reinterpret_cast<thespian_handle>(&self); // NOLINT(*-reinterpret-cast)
|
||||
}
|
||||
|
||||
auto thespian_spawn_link(thespian_behaviour b, thespian_behaviour_state s,
|
||||
const char *name, thespian_env env,
|
||||
thespian_handle *handle) -> int {
|
||||
thespian::env_t empty_env_{};
|
||||
thespian::env_t env_ =
|
||||
env ? *reinterpret_cast<thespian::env_t *>(env) : empty_env_; // NOLINT
|
||||
|
||||
auto ret = spawn_link(
|
||||
[b, s]() -> thespian::result {
|
||||
auto *ret = b(s);
|
||||
if (ret) {
|
||||
auto err = cbor::buffer();
|
||||
const uint8_t *data = ret->base; // NOLINT
|
||||
std::copy(data, data + ret->len, back_inserter(err)); // NOLINT
|
||||
return thespian::to_error(err);
|
||||
}
|
||||
return thespian::ok();
|
||||
},
|
||||
string_view(name), std::move(env_));
|
||||
if (!ret)
|
||||
return -1;
|
||||
*handle = reinterpret_cast<thespian_handle>( // NOLINT
|
||||
new thespian::handle{ret.value()});
|
||||
return 0;
|
||||
}
|
||||
}
|
76
src/c/metronome.cpp
Normal file
76
src/c/metronome.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/metronome.h>
|
||||
#include <thespian/metronome.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::milliseconds;
|
||||
using thespian::destroy_metronome;
|
||||
using thespian::metronome_impl;
|
||||
using thespian::metronome_ref;
|
||||
using thespian::start_metronome;
|
||||
using thespian::stop_metronome;
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_metronome_create_ms(unsigned long ms)
|
||||
-> thespian_metronome_handle * {
|
||||
try {
|
||||
auto *handle = thespian::create_metronome(milliseconds(ms)).ref.release();
|
||||
return reinterpret_cast<thespian_metronome_handle *>(handle); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_metronome_create_ms error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto thespian_metronome_create_us(unsigned long us)
|
||||
-> thespian_metronome_handle * {
|
||||
try {
|
||||
auto *handle = thespian::create_metronome(microseconds(us)).ref.release();
|
||||
return reinterpret_cast<thespian_metronome_handle *>(handle); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_metronome_create_us error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto thespian_metronome_start(thespian_metronome_handle *handle) -> int {
|
||||
try {
|
||||
start_metronome(reinterpret_cast<metronome_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_metronome_start error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
auto thespian_metronome_stop(thespian_metronome_handle *handle) -> int {
|
||||
try {
|
||||
stop_metronome(reinterpret_cast<metronome_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_metronome_stop error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
void thespian_metronome_destroy(thespian_metronome_handle *handle) {
|
||||
try {
|
||||
destroy_metronome(reinterpret_cast<metronome_impl *>(handle)); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_metronome_destroy error");
|
||||
}
|
||||
}
|
||||
}
|
50
src/c/signal.cpp
Normal file
50
src/c/signal.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/signal.h>
|
||||
#include <thespian/signal.hpp>
|
||||
|
||||
using thespian::cancel_signal;
|
||||
using thespian::destroy_signal;
|
||||
using thespian::signal_impl;
|
||||
using thespian::signal_ref;
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_signal_create(int signum, cbor_buffer m)
|
||||
-> thespian_signal_handle * {
|
||||
try {
|
||||
cbor::buffer buf;
|
||||
const uint8_t *data = m.base;
|
||||
std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic)
|
||||
back_inserter(buf));
|
||||
auto *handle = thespian::create_signal(signum, move(buf)).ref.release();
|
||||
return reinterpret_cast<thespian_signal_handle *>(handle); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_signal_create error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto thespian_signal_cancel(thespian_signal_handle *handle) -> int {
|
||||
try {
|
||||
cancel_signal(reinterpret_cast<signal_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_signal_start error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
void thespian_signal_destroy(thespian_signal_handle *handle) {
|
||||
try {
|
||||
destroy_signal(reinterpret_cast<signal_impl *>(handle)); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_signal_destroy error");
|
||||
}
|
||||
}
|
||||
}
|
73
src/c/timeout.cpp
Normal file
73
src/c/timeout.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/timeout.h>
|
||||
#include <thespian/timeout.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::milliseconds;
|
||||
using thespian::cancel_timeout;
|
||||
using thespian::destroy_timeout;
|
||||
using thespian::timeout_impl;
|
||||
using thespian::timeout_ref;
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_timeout_create_ms(unsigned long ms, cbor_buffer m)
|
||||
-> thespian_timeout_handle * {
|
||||
try {
|
||||
cbor::buffer buf;
|
||||
const uint8_t *data = m.base;
|
||||
std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic)
|
||||
back_inserter(buf));
|
||||
auto *handle =
|
||||
thespian::create_timeout(milliseconds(ms), move(buf)).ref.release();
|
||||
return reinterpret_cast<thespian_timeout_handle *>(handle); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_timeout_create_ms error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto thespian_timeout_create_us(unsigned long us, cbor_buffer m)
|
||||
-> thespian_timeout_handle * {
|
||||
try {
|
||||
cbor::buffer buf;
|
||||
const uint8_t *data = m.base;
|
||||
std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic)
|
||||
back_inserter(buf));
|
||||
auto *handle =
|
||||
thespian::create_timeout(microseconds(us), move(buf)).ref.release();
|
||||
return reinterpret_cast<thespian_timeout_handle *>(handle); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_timeout_create_us error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto thespian_timeout_cancel(thespian_timeout_handle *handle) -> int {
|
||||
try {
|
||||
cancel_timeout(reinterpret_cast<timeout_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_timeout_start error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
void thespian_timeout_destroy(thespian_timeout_handle *handle) {
|
||||
try {
|
||||
destroy_timeout(reinterpret_cast<timeout_impl *>(handle)); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_timeout_destroy error");
|
||||
}
|
||||
}
|
||||
}
|
25
src/c/trace.cpp
Normal file
25
src/c/trace.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include <thespian/c/trace.h>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
extern "C" {
|
||||
|
||||
void thespian_on_trace(thespian_trace_handler h) {
|
||||
thespian::on_trace([h](const cbor::buffer &m) { h({m.data(), m.size()}); });
|
||||
}
|
||||
|
||||
void thespian_trace_to_json_file(const char *file_name) {
|
||||
thespian::trace_to_json_file(file_name);
|
||||
}
|
||||
|
||||
void thespian_trace_to_cbor_file(const char *file_name) {
|
||||
thespian::trace_to_cbor_file(file_name);
|
||||
}
|
||||
|
||||
void thespian_trace_to_mermaid_file(const char *file_name) {
|
||||
thespian::trace_to_mermaid_file(file_name);
|
||||
}
|
||||
|
||||
void thespian_trace_to_trace(thespian_trace_channel default_channel) {
|
||||
thespian::trace_to_trace(default_channel);
|
||||
}
|
||||
}
|
926
src/cbor.cpp
Normal file
926
src/cbor.cpp
Normal file
|
@ -0,0 +1,926 @@
|
|||
#include <cbor/c/cbor.h>
|
||||
#include <cbor/cbor.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
using std::back_inserter;
|
||||
using std::copy;
|
||||
using std::domain_error;
|
||||
using std::hex;
|
||||
using std::make_tuple;
|
||||
using std::numeric_limits;
|
||||
using std::ostream;
|
||||
using std::setfill;
|
||||
using std::setw;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using std::tuple;
|
||||
|
||||
namespace cbor {
|
||||
|
||||
const buffer buffer::null_value = buffer{0xF6};
|
||||
|
||||
auto buffer::push_typed_val(int type, uint64_t value) -> void {
|
||||
type <<= 5;
|
||||
if (value < 24ULL) {
|
||||
push_back(type | value);
|
||||
} else if (value < 256ULL) {
|
||||
push_back(type | 24);
|
||||
push_back(value);
|
||||
} else if (value < 65536ULL) {
|
||||
push_back(type | 25);
|
||||
push_back(value >> 8);
|
||||
push_back(value);
|
||||
} else if (value < 4294967296ULL) {
|
||||
push_back(type | 26);
|
||||
push_back(value >> 24);
|
||||
push_back(value >> 16);
|
||||
push_back(value >> 8);
|
||||
push_back(value);
|
||||
} else {
|
||||
push_back(type | 27);
|
||||
push_back(value >> 56);
|
||||
push_back(value >> 48);
|
||||
push_back(value >> 40);
|
||||
push_back(value >> 32);
|
||||
push_back(value >> 24);
|
||||
push_back(value >> 16);
|
||||
push_back(value >> 8);
|
||||
push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
auto buffer::push_string(const string &s) -> buffer & {
|
||||
push_typed_val(3, s.size());
|
||||
copy(s.begin(), s.end(), back_inserter(*this));
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto buffer::push_string(const string_view &s) -> buffer & {
|
||||
push_typed_val(3, s.size());
|
||||
copy(s.begin(), s.end(), back_inserter(*this));
|
||||
return *this;
|
||||
}
|
||||
|
||||
using json_iter = string::const_iterator;
|
||||
|
||||
static auto match_wsp_char(json_iter &b, const json_iter &e) -> bool {
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
++b;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static auto skip_wsp(json_iter &b, const json_iter &e) -> void {
|
||||
while (match_wsp_char(b, e))
|
||||
;
|
||||
}
|
||||
|
||||
static auto match_char(int c, json_iter &b, const json_iter &e) -> bool {
|
||||
if (b == e || *b != c)
|
||||
return false;
|
||||
++b;
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(*-pointer-arithmetic)
|
||||
static auto match_literal(const char *str, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
json_iter i = b;
|
||||
while (*str && match_char(*str, i, e))
|
||||
++str;
|
||||
if (!*str) {
|
||||
b = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr auto int_str_max = 3 * sizeof(uint64_t) + 1;
|
||||
|
||||
static auto push_json_number(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
const bool neg = match_char('-', b, e);
|
||||
std::array<char, int_str_max> s{};
|
||||
char *p = s.data();
|
||||
char *se = s.data() + int_str_max;
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
if (c >= '0' && c <= '9') {
|
||||
++b;
|
||||
*p = c;
|
||||
++p;
|
||||
if (p == se)
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
while (true) {
|
||||
if (b == e)
|
||||
break;
|
||||
c = *b;
|
||||
if (c >= '0' && c <= '9') {
|
||||
++b;
|
||||
*p = c;
|
||||
++p;
|
||||
if (p == se)
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*p = 0;
|
||||
char *ep = s.data();
|
||||
int64_t i = strtoll(s.data(), &ep, 10);
|
||||
if (ep != p)
|
||||
return false;
|
||||
if (neg)
|
||||
i = -i;
|
||||
buf.push_int(i);
|
||||
return true;
|
||||
}
|
||||
// NOLINTEND(*-pointer-arithmetic)
|
||||
|
||||
static auto push_json_qstring_slow(buffer &buf, json_iter &b,
|
||||
const json_iter &e) -> bool {
|
||||
if (!match_char('"', b, e))
|
||||
return false;
|
||||
string s;
|
||||
while (true) {
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
++b;
|
||||
if (c == '\\') {
|
||||
if (b == e)
|
||||
return false;
|
||||
c = *b;
|
||||
++b;
|
||||
switch (c) {
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
c = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case '\\':
|
||||
c = '\\';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
buf.push_string(s);
|
||||
return true;
|
||||
}
|
||||
s.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
static auto scan_json_qstring(json_iter &b, json_iter &e, bool &is_escaped)
|
||||
-> bool {
|
||||
if (!match_char('"', b, e))
|
||||
return false;
|
||||
json_iter i = b;
|
||||
is_escaped = false;
|
||||
while (true) {
|
||||
if (i == e)
|
||||
return false;
|
||||
char c = *i;
|
||||
if (c == '\\') {
|
||||
is_escaped = true;
|
||||
return true;
|
||||
}
|
||||
if (c == '"') {
|
||||
e = i;
|
||||
return true;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
static auto push_json_qstring(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
json_iter sb = b;
|
||||
json_iter se = e;
|
||||
bool is_escaped{false};
|
||||
if (!scan_json_qstring(sb, se, is_escaped))
|
||||
return false;
|
||||
if (is_escaped)
|
||||
return push_json_qstring_slow(buf, b, e);
|
||||
b = se;
|
||||
++b;
|
||||
buf.push_string(string_view(&*sb, (&*se - &*sb)));
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto push_json_keyword(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
if (match_literal("false", b, e))
|
||||
buf.push_bool(false);
|
||||
else if (match_literal("true", b, e))
|
||||
buf.push_bool(true);
|
||||
else if (match_literal("null", b, e))
|
||||
buf.push_null();
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool;
|
||||
|
||||
static auto push_json_array(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
if (!match_char('[', b, e))
|
||||
return false;
|
||||
skip_wsp(b, e);
|
||||
buffer inner;
|
||||
size_t count{0};
|
||||
if (!push_json_value(inner, b, e))
|
||||
return false;
|
||||
++count;
|
||||
while (true) {
|
||||
skip_wsp(b, e);
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
switch (c) {
|
||||
case ',':
|
||||
++b;
|
||||
skip_wsp(b, e);
|
||||
continue;
|
||||
case ']':
|
||||
++b;
|
||||
inner.to_cbor(buf.array_header(count));
|
||||
return true;
|
||||
default:
|
||||
if (!push_json_value(inner, b, e))
|
||||
return false;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static auto push_json_map(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
if (!match_char('{', b, e))
|
||||
return false;
|
||||
skip_wsp(b, e);
|
||||
buffer inner;
|
||||
size_t count{0};
|
||||
if (!push_json_value(inner, b, e))
|
||||
return false;
|
||||
++count;
|
||||
while (true) {
|
||||
skip_wsp(b, e);
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
switch (c) {
|
||||
case ':':
|
||||
case ',':
|
||||
++b;
|
||||
skip_wsp(b, e);
|
||||
continue;
|
||||
case '}':
|
||||
++b;
|
||||
if (count % 2)
|
||||
return false;
|
||||
inner.to_cbor(buf.map_header(count / 2));
|
||||
return true;
|
||||
default:
|
||||
if (!push_json_value(inner, b, e))
|
||||
return false;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e)
|
||||
-> bool {
|
||||
skip_wsp(b, e);
|
||||
if (b == e)
|
||||
return false;
|
||||
char c = *b;
|
||||
if ((c >= '0' && c <= '9') || c == '-') { // integer
|
||||
return push_json_number(buf, b, e);
|
||||
}
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'z' && c <= 'z')) { // keyword
|
||||
return push_json_keyword(buf, b, e);
|
||||
}
|
||||
switch (c) {
|
||||
case '"': // string
|
||||
return push_json_qstring(buf, b, e);
|
||||
case '[': // array
|
||||
return push_json_array(buf, b, e);
|
||||
case '{': // map
|
||||
return push_json_map(buf, b, e);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buffer::push_json(const string &s) -> void {
|
||||
json_iter b = s.cbegin();
|
||||
if (!push_json_value(*this, b, s.cend())) {
|
||||
stringstream ss;
|
||||
ss << "invalid json value at pos " << std::distance(s.cbegin(), b)
|
||||
<< " in: " << s;
|
||||
throw domain_error{ss.str()};
|
||||
}
|
||||
}
|
||||
|
||||
using iter = buffer::const_iterator;
|
||||
|
||||
static auto decode_int_length_recurse(size_t length, iter &b, const iter &e,
|
||||
int64_t i) -> int64_t {
|
||||
if (b == e)
|
||||
throw domain_error{"cbor message too short"};
|
||||
i |= *b;
|
||||
++b;
|
||||
if (length == 1)
|
||||
return i;
|
||||
i <<= 8;
|
||||
return decode_int_length_recurse(length - 1, b, e, i);
|
||||
};
|
||||
|
||||
static auto decode_int_length(size_t length, iter &b, const iter &e)
|
||||
-> int64_t {
|
||||
return decode_int_length_recurse(length, b, e, 0);
|
||||
}
|
||||
|
||||
static auto decode_pint(uint8_t type, iter &b, const iter &e) -> uint64_t {
|
||||
if (type < 24)
|
||||
return type;
|
||||
switch (type) {
|
||||
case 24: // 1 byte
|
||||
return decode_int_length(1, b, e);
|
||||
case 25: // 2 byte
|
||||
return decode_int_length(2, b, e);
|
||||
case 26: // 4 byte
|
||||
return decode_int_length(4, b, e);
|
||||
case 27: // 8 byte
|
||||
return decode_int_length(8, b, e);
|
||||
default:
|
||||
throw domain_error{"cbor invalid integer type"};
|
||||
}
|
||||
}
|
||||
|
||||
static auto decode_nint(uint8_t type, iter &b, const iter &e) -> int64_t {
|
||||
return -(decode_pint(type, b, e) + 1); // NOLINT(*-narrowing-conversions)
|
||||
}
|
||||
|
||||
static auto decode_string(uint8_t type, iter &b, const iter &e) -> string_view {
|
||||
auto len = decode_pint(type, b, e);
|
||||
const uint8_t *s = &*b;
|
||||
const auto sz = len;
|
||||
while (len) {
|
||||
if (b == e)
|
||||
throw domain_error{"cbor message too short"};
|
||||
++b;
|
||||
--len;
|
||||
}
|
||||
return {
|
||||
reinterpret_cast< // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const char *>(s),
|
||||
sz};
|
||||
}
|
||||
|
||||
static auto decode_bytes(uint8_t type, iter &b, const iter &e) -> string_view {
|
||||
return decode_string(type, b, e);
|
||||
}
|
||||
|
||||
static auto decode_type(iter &b, const iter &e)
|
||||
-> tuple<uint8_t, uint8_t, uint8_t> {
|
||||
if (b == e)
|
||||
throw domain_error{"cbor message too short"};
|
||||
uint8_t type = *b;
|
||||
++b;
|
||||
return make_tuple(uint8_t(type >> 5), uint8_t(type & 31), type);
|
||||
}
|
||||
|
||||
auto buffer::decode_range_header(iter &b, const iter &e) -> size_t {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
if (type == 0xf6)
|
||||
return 0;
|
||||
auto sz = decode_pint(minor, b, e);
|
||||
switch (major) {
|
||||
case 4: // array
|
||||
return sz;
|
||||
case 5: // map
|
||||
return sz * 2;
|
||||
default:
|
||||
throw domain_error{"cbor unexpected type (expected array or map)"};
|
||||
}
|
||||
}
|
||||
|
||||
auto buffer::decode_array_header(iter &b, const iter &e) -> size_t {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
if (type == 0xf6)
|
||||
return 0;
|
||||
if (major != 4)
|
||||
throw domain_error{"cbor unexpected type (expected array)"};
|
||||
return decode_pint(minor, b, e);
|
||||
}
|
||||
|
||||
auto buffer::decode_map_header(iter &b, const iter &e) -> size_t {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
if (type == 0xf6)
|
||||
return 0;
|
||||
if (major != 5)
|
||||
throw domain_error{"cbor unexpected type (expected map)"};
|
||||
return decode_pint(minor, b, e);
|
||||
}
|
||||
|
||||
static auto skip_string(uint8_t type, iter &b, const iter &e) -> void {
|
||||
auto len = decode_pint(type, b, e);
|
||||
while (len) {
|
||||
if (b == e)
|
||||
throw domain_error{"cbor message too short"};
|
||||
++b;
|
||||
--len;
|
||||
}
|
||||
}
|
||||
|
||||
static auto skip_bytes(uint8_t type, iter &b, const iter &e) -> void {
|
||||
return skip_string(type, b, e);
|
||||
}
|
||||
|
||||
static auto skip_array(uint8_t type, iter &b, const iter &e) -> void;
|
||||
static auto skip_map(uint8_t type, iter &b, const iter &e) -> void;
|
||||
|
||||
static auto skip_value_type(uint8_t major, uint8_t minor, iter &b,
|
||||
const iter &e) -> void {
|
||||
switch (major) {
|
||||
case 0: // positive integer
|
||||
decode_pint(minor, b, e);
|
||||
break;
|
||||
case 1: // negative integer
|
||||
decode_nint(minor, b, e);
|
||||
break;
|
||||
case 2: // bytes
|
||||
skip_bytes(minor, b, e);
|
||||
break;
|
||||
case 3: // string
|
||||
skip_string(minor, b, e);
|
||||
break;
|
||||
case 4: // array
|
||||
skip_array(minor, b, e);
|
||||
break;
|
||||
case 5: // map
|
||||
skip_map(minor, b, e);
|
||||
break;
|
||||
case 6: // tag
|
||||
throw domain_error{"cbor unsupported type tag"};
|
||||
case 7: // special
|
||||
break;
|
||||
default:
|
||||
throw domain_error{"cbor unsupported type unknown"};
|
||||
}
|
||||
}
|
||||
static auto skip_value(iter &b, const iter &e) -> void {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
skip_value_type(major, minor, b, e);
|
||||
}
|
||||
|
||||
static auto skip_array(uint8_t type, iter &b, const iter &e) -> void {
|
||||
auto len = decode_pint(type, b, e);
|
||||
while (len) {
|
||||
skip_value(b, e);
|
||||
--len;
|
||||
}
|
||||
}
|
||||
|
||||
static auto skip_map(uint8_t type, iter &b, const iter &e) -> void {
|
||||
auto len = decode_pint(type, b, e);
|
||||
len = len * 2;
|
||||
while (len) {
|
||||
skip_value(b, e);
|
||||
--len;
|
||||
}
|
||||
}
|
||||
|
||||
static auto match_type(iter &b, const iter &e, type &v) -> bool {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
skip_value_type(major, minor, b, e);
|
||||
switch (major) {
|
||||
case 0: // positive integer
|
||||
case 1: // negative integer
|
||||
v = type::number;
|
||||
break;
|
||||
case 2: // bytes
|
||||
v = type::bytes;
|
||||
break;
|
||||
case 3: // string
|
||||
v = type::string;
|
||||
break;
|
||||
case 4: // array
|
||||
v = type::array;
|
||||
break;
|
||||
case 5: // map
|
||||
v = type::map;
|
||||
break;
|
||||
case 7: // special
|
||||
if (type == 0xf6)
|
||||
v = type::null;
|
||||
else if (type == 0xf4 || type == 0xf5)
|
||||
v = type::boolean;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, type t) -> bool {
|
||||
type v{type::any};
|
||||
if (match_type(b, e, v)) {
|
||||
if (t == type::any)
|
||||
return true;
|
||||
return t == v;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto match_uint(iter &b, const iter &e, uint64_t &val) -> bool {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
if (major != 0)
|
||||
return false;
|
||||
val = decode_pint(minor, b, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto match_int(iter &b, const iter &e, int64_t &val) -> bool {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
switch (major) {
|
||||
case 0: // positive integer
|
||||
val = decode_pint(minor, b, e); // NOLINT(*-narrowing-conversions)
|
||||
if (val < 0)
|
||||
return false;
|
||||
break;
|
||||
case 1: // negative integer
|
||||
val = decode_nint(minor, b, e);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, int64_t lit) -> bool {
|
||||
int64_t val{0};
|
||||
if (match_int(b, e, val))
|
||||
return val == lit;
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto match_bool(iter &b, const iter &e, bool &v) -> bool {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
if (major == 7) { // special
|
||||
if (type == 0xf4) {
|
||||
v = false;
|
||||
return true;
|
||||
}
|
||||
if (type == 0xf5) {
|
||||
v = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, bool lit) -> bool {
|
||||
bool val{};
|
||||
if (match_bool(b, e, val))
|
||||
return val == lit;
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto match_string(iter &b, const iter &e, string_view &val) -> bool {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
switch (major) {
|
||||
case 2: // bytes
|
||||
val = decode_bytes(minor, b, e);
|
||||
break;
|
||||
case 3: // string
|
||||
val = decode_string(minor, b, e);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, const string_view lit)
|
||||
-> bool {
|
||||
string_view val;
|
||||
if (match_string(b, e, val))
|
||||
return val == lit;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, const string &lit) -> bool {
|
||||
string_view val;
|
||||
if (match_string(b, e, val))
|
||||
return val == lit;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extract(type &t) -> buffer::extractor {
|
||||
return [&t](iter &b, const iter &e) { return match_type(b, e, t); };
|
||||
}
|
||||
|
||||
template <typename T> static auto extract_int(T &i) -> buffer::extractor {
|
||||
return [&i](iter &b, const iter &e) {
|
||||
int64_t i_{};
|
||||
if (match_int(b, e, i_)) {
|
||||
if (i_ < int64_t(numeric_limits<T>::min()) or
|
||||
i_ > int64_t(numeric_limits<T>::max()))
|
||||
return false;
|
||||
i = i_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
auto extract(int64_t &i) -> buffer::extractor { return extract_int(i); }
|
||||
auto extract(int32_t &i) -> buffer::extractor { return extract_int(i); }
|
||||
auto extract(int16_t &i) -> buffer::extractor { return extract_int(i); }
|
||||
auto extract(int8_t &i) -> buffer::extractor { return extract_int(i); }
|
||||
|
||||
auto extract(uint64_t &i) -> buffer::extractor {
|
||||
return [&i](iter &b, const iter &e) { return match_uint(b, e, i); };
|
||||
}
|
||||
template <typename T> static auto extract_uint(T &i) -> buffer::extractor {
|
||||
return [&i](iter &b, const iter &e) {
|
||||
uint64_t i_{};
|
||||
if (match_uint(b, e, i_)) {
|
||||
if (i_ > uint64_t(numeric_limits<T>::max()))
|
||||
return false;
|
||||
i = i_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
auto extract(unsigned long long &i) -> buffer::extractor {
|
||||
return extract_uint(i);
|
||||
}
|
||||
auto extract(uint32_t &i) -> buffer::extractor { return extract_uint(i); }
|
||||
auto extract(uint16_t &i) -> buffer::extractor { return extract_uint(i); }
|
||||
auto extract(uint8_t &i) -> buffer::extractor { return extract_uint(i); }
|
||||
|
||||
auto extract(bool &val) -> buffer::extractor {
|
||||
return [&val](iter &b, const iter &e) { return match_bool(b, e, val); };
|
||||
}
|
||||
|
||||
auto extract(std::string &s) -> buffer::extractor {
|
||||
return [&s](iter &b, const iter &e) {
|
||||
string_view val;
|
||||
if (match_string(b, e, val)) {
|
||||
s.assign(val.data(), val.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
auto extract(string_view &s) -> buffer::extractor {
|
||||
return [&s](iter &b, const iter &e) { return match_string(b, e, s); };
|
||||
}
|
||||
|
||||
auto extract(buffer::range &r) -> buffer::extractor {
|
||||
return [&r](iter &b, const iter &e) {
|
||||
auto r_b = b;
|
||||
type v{type::any};
|
||||
if (match_type(b, e, v) &&
|
||||
(v == type::null || v == type::array || v == type::map)) {
|
||||
r.b_ = r_b;
|
||||
r.e_ = b;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
auto buffer::match_value(iter &b, const iter &e, const extractor &ex) -> bool {
|
||||
return ex(b, e);
|
||||
}
|
||||
|
||||
static auto tohex(ostream &os, uint8_t v) -> ostream & {
|
||||
return os << hex << setfill('0') << setw(2) << static_cast<unsigned>(v);
|
||||
}
|
||||
|
||||
static auto to_json(ostream &os, string_view s) -> ostream & {
|
||||
os << '"';
|
||||
for (auto c : s) {
|
||||
switch (c) {
|
||||
case '\b':
|
||||
os << "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
os << "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
os << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
os << "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
os << "\\t";
|
||||
break;
|
||||
case '\\':
|
||||
os << "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
os << "\\\"";
|
||||
break;
|
||||
default:
|
||||
if (c >= 0 && c <= 0x1f) {
|
||||
os << "\\u00";
|
||||
tohex(os, c);
|
||||
} else {
|
||||
os << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
os << '"';
|
||||
return os;
|
||||
}
|
||||
|
||||
static auto to_json_stream(ostream &ss, iter &b, const iter &e) -> void {
|
||||
const auto [major, minor, type] = decode_type(b, e);
|
||||
switch (major) {
|
||||
case 0: // positive integer
|
||||
{
|
||||
const auto i = decode_pint(minor, b, e);
|
||||
ss << i;
|
||||
} break;
|
||||
case 1: // negative integer
|
||||
{
|
||||
const auto i = decode_nint(minor, b, e);
|
||||
ss << i;
|
||||
} break;
|
||||
case 2: // bytes
|
||||
{
|
||||
const string_view v = decode_bytes(minor, b, e);
|
||||
to_json(ss, v);
|
||||
} break;
|
||||
case 3: // string
|
||||
{
|
||||
const string_view v = decode_string(minor, b, e);
|
||||
to_json(ss, v);
|
||||
} break;
|
||||
case 4: // array
|
||||
{
|
||||
auto i = decode_pint(minor, b, e);
|
||||
ss << '[';
|
||||
while (i) {
|
||||
to_json_stream(ss, b, e);
|
||||
--i;
|
||||
if (i)
|
||||
ss << ',';
|
||||
}
|
||||
ss << ']';
|
||||
} break;
|
||||
case 5: // map
|
||||
{
|
||||
auto i = decode_pint(minor, b, e);
|
||||
ss << '{';
|
||||
while (i) {
|
||||
to_json_stream(ss, b, e);
|
||||
ss << ':';
|
||||
to_json_stream(ss, b, e);
|
||||
--i;
|
||||
if (i)
|
||||
ss << ',';
|
||||
}
|
||||
ss << '}';
|
||||
} break;
|
||||
case 6: // tag
|
||||
throw domain_error{"cbor unsupported type tag"};
|
||||
case 7: // special
|
||||
{
|
||||
if (type == 0xf4)
|
||||
ss << "false";
|
||||
else if (type == 0xf5)
|
||||
ss << "true";
|
||||
else if (type == 0xf6)
|
||||
ss << "null";
|
||||
} break;
|
||||
default:
|
||||
throw domain_error{"cbor unsupported type unknown"};
|
||||
}
|
||||
}
|
||||
|
||||
static auto to_json_(const iter &b_, const iter &e) -> string {
|
||||
stringstream ss;
|
||||
iter b = b_;
|
||||
try {
|
||||
to_json_stream(ss, b, e);
|
||||
} catch (const domain_error &e) {
|
||||
throw domain_error{string("cbor to json failed: ") + e.what() +
|
||||
"\nafter:\n" + ss.str()};
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
auto buffer::to_json() const -> string {
|
||||
return to_json_(raw_cbegin(), raw_cend());
|
||||
}
|
||||
auto buffer::range::to_json() const -> string {
|
||||
return to_json_(raw_cbegin(), raw_cend());
|
||||
}
|
||||
auto buffer::to_json(ostream &os) const -> void {
|
||||
auto b = raw_cbegin();
|
||||
return to_json_stream(os, b, raw_cend());
|
||||
}
|
||||
auto buffer::range::to_json(ostream &os) const -> void {
|
||||
auto b = raw_cbegin();
|
||||
return to_json_stream(os, b, raw_cend());
|
||||
}
|
||||
|
||||
extern "C" void cbor_to_json(cbor_buffer buf, cbor_to_json_callback cb) {
|
||||
auto cbor = cbor::buffer();
|
||||
const uint8_t *data = buf.base;
|
||||
std::copy(data, data + buf.len, back_inserter(cbor)); // NOLINT(*-pointer-arithmetic)
|
||||
auto json = cbor.to_json();
|
||||
cb({json.data(), json.size()});
|
||||
}
|
||||
|
||||
auto buffer::hexdump() const -> string {
|
||||
stringstream ss;
|
||||
ss << size() << ':';
|
||||
for (auto c : static_cast<const buffer_base &>(*this)) {
|
||||
ss << ' ';
|
||||
tohex(ss, c);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
auto buffer::value_accessor::type_() const -> type {
|
||||
type t{};
|
||||
iter b_ = b;
|
||||
if (not match_value(b_, e, extract(t)))
|
||||
return type::unknown;
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T> static auto get(iter b, const iter &e) -> T {
|
||||
T val;
|
||||
extract(val)(b, e);
|
||||
return val;
|
||||
}
|
||||
buffer::value_accessor::operator type() const { return get<type>(b, e); }
|
||||
buffer::value_accessor::operator int64_t() const { return get<int64_t>(b, e); }
|
||||
buffer::value_accessor::operator uint64_t() const { return get<int64_t>(b, e); }
|
||||
buffer::value_accessor::operator int32_t() const {
|
||||
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
|
||||
}
|
||||
buffer::value_accessor::operator uint32_t() const { return get<int64_t>(b, e); }
|
||||
buffer::value_accessor::operator int16_t() const {
|
||||
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
|
||||
}
|
||||
buffer::value_accessor::operator uint16_t() const { return get<int64_t>(b, e); }
|
||||
buffer::value_accessor::operator int8_t() const {
|
||||
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
|
||||
}
|
||||
buffer::value_accessor::operator uint8_t() const { return get<int64_t>(b, e); }
|
||||
buffer::value_accessor::operator bool() const { return get<bool>(b, e); }
|
||||
buffer::value_accessor::operator string_view() const {
|
||||
return get<string_view>(b, e);
|
||||
}
|
||||
buffer::value_accessor::operator buffer::range() const {
|
||||
return get<buffer::range>(b, e);
|
||||
}
|
||||
|
||||
} // namespace cbor
|
914
src/cbor.zig
Normal file
914
src/cbor.zig
Normal file
|
@ -0,0 +1,914 @@
|
|||
const std = @import("std");
|
||||
|
||||
const eql = std.mem.eql;
|
||||
const bufPrint = std.fmt.bufPrint;
|
||||
const fixedBufferStream = std.io.fixedBufferStream;
|
||||
const maxInt = std.math.maxInt;
|
||||
const minInt = std.math.minInt;
|
||||
const json = std.json;
|
||||
const fba = std.heap.FixedBufferAllocator;
|
||||
|
||||
pub const CborError = error{
|
||||
CborIntegerTooLarge,
|
||||
CborIntegerTooSmall,
|
||||
CborInvalidType,
|
||||
CborTooShort,
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const CborJsonError = error{
|
||||
BufferUnderrun,
|
||||
CborIntegerTooLarge,
|
||||
CborIntegerTooSmall,
|
||||
CborInvalidType,
|
||||
CborTooShort,
|
||||
CborUnsupportedType,
|
||||
NoSpaceLeft,
|
||||
OutOfMemory,
|
||||
Overflow,
|
||||
SyntaxError,
|
||||
UnexpectedEndOfInput,
|
||||
};
|
||||
|
||||
const cbor_magic_null: u8 = 0xf6;
|
||||
const cbor_magic_true: u8 = 0xf5;
|
||||
const cbor_magic_false: u8 = 0xf4;
|
||||
|
||||
const cbor_magic_type_array: u8 = 4;
|
||||
const cbor_magic_type_map: u8 = 5;
|
||||
|
||||
const value_type = enum(u8) {
|
||||
number,
|
||||
bytes,
|
||||
string,
|
||||
array,
|
||||
map,
|
||||
tag,
|
||||
boolean,
|
||||
null,
|
||||
any,
|
||||
more,
|
||||
unknown,
|
||||
};
|
||||
pub const number = value_type.number;
|
||||
pub const bytes = value_type.bytes;
|
||||
pub const string = value_type.string;
|
||||
pub const array = value_type.array;
|
||||
pub const map = value_type.map;
|
||||
pub const tag = value_type.tag;
|
||||
pub const boolean = value_type.boolean;
|
||||
pub const null_ = value_type.null;
|
||||
pub const any = value_type.any;
|
||||
pub const more = value_type.more;
|
||||
|
||||
const null_value_buf = [_]u8{0xF6};
|
||||
pub const null_value: []const u8 = &null_value_buf;
|
||||
|
||||
pub fn isNull(val: []const u8) bool {
|
||||
return eql(u8, val, null_value);
|
||||
}
|
||||
|
||||
fn isAny(value: anytype) bool {
|
||||
return if (comptime @TypeOf(value) == value_type) value == value_type.any else false;
|
||||
}
|
||||
|
||||
fn isMore(value: anytype) bool {
|
||||
return if (comptime @TypeOf(value) == value_type) value == value_type.more else false;
|
||||
}
|
||||
|
||||
fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void {
|
||||
_ = try writer.write(&[_]u8{value});
|
||||
}
|
||||
|
||||
fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void {
|
||||
const t: u8 = type_ << 5;
|
||||
if (value < 24) {
|
||||
try write(writer, t | @as(u8, @truncate(value)));
|
||||
} else if (value < 256) {
|
||||
try write(writer, t | 24);
|
||||
try write(writer, @as(u8, @truncate(value)));
|
||||
} else if (value < 65536) {
|
||||
try write(writer, t | 25);
|
||||
try write(writer, @as(u8, @truncate(value >> 8)));
|
||||
try write(writer, @as(u8, @truncate(value)));
|
||||
} else if (value < 4294967296) {
|
||||
try write(writer, t | 26);
|
||||
try write(writer, @as(u8, @truncate(value >> 24)));
|
||||
try write(writer, @as(u8, @truncate(value >> 16)));
|
||||
try write(writer, @as(u8, @truncate(value >> 8)));
|
||||
try write(writer, @as(u8, @truncate(value)));
|
||||
} else {
|
||||
try write(writer, t | 27);
|
||||
try write(writer, @as(u8, @truncate(value >> 56)));
|
||||
try write(writer, @as(u8, @truncate(value >> 48)));
|
||||
try write(writer, @as(u8, @truncate(value >> 40)));
|
||||
try write(writer, @as(u8, @truncate(value >> 32)));
|
||||
try write(writer, @as(u8, @truncate(value >> 24)));
|
||||
try write(writer, @as(u8, @truncate(value >> 16)));
|
||||
try write(writer, @as(u8, @truncate(value >> 8)));
|
||||
try write(writer, @as(u8, @truncate(value)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
|
||||
return writeTypedVal(writer, cbor_magic_type_array, sz);
|
||||
}
|
||||
|
||||
pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
|
||||
return writeTypedVal(writer, cbor_magic_type_map, sz);
|
||||
}
|
||||
|
||||
pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void {
|
||||
const args_type_info = @typeInfo(@TypeOf(args));
|
||||
if (args_type_info != .Struct) @compileError("expected tuple or struct argument");
|
||||
const fields_info = args_type_info.Struct.fields;
|
||||
try writeArrayHeader(writer, fields_info.len);
|
||||
inline for (fields_info) |field_info|
|
||||
try writeValue(writer, @field(args, field_info.name));
|
||||
}
|
||||
|
||||
fn writeI64(writer: anytype, value: i64) @TypeOf(writer).Error!void {
|
||||
return if (value < 0)
|
||||
writeTypedVal(writer, 1, @as(u64, @bitCast(-(value + 1))))
|
||||
else
|
||||
writeTypedVal(writer, 0, @as(u64, @bitCast(value)));
|
||||
}
|
||||
|
||||
fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void {
|
||||
return writeTypedVal(writer, 0, value);
|
||||
}
|
||||
|
||||
fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void {
|
||||
try writeTypedVal(writer, 3, s.len);
|
||||
_ = try writer.write(s);
|
||||
}
|
||||
|
||||
fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void {
|
||||
return write(writer, if (value) cbor_magic_true else cbor_magic_false);
|
||||
}
|
||||
|
||||
fn writeNull(writer: anytype) @TypeOf(writer).Error!void {
|
||||
return write(writer, cbor_magic_null);
|
||||
}
|
||||
|
||||
fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void {
|
||||
var buf: [256]u8 = undefined;
|
||||
const errmsg = try bufPrint(&buf, "error.{s}", .{@errorName(err)});
|
||||
return writeString(writer, errmsg);
|
||||
}
|
||||
|
||||
pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void {
|
||||
const T = @TypeOf(value);
|
||||
switch (@typeInfo(T)) {
|
||||
.Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)),
|
||||
.Bool => return writeBool(writer, value),
|
||||
.Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer),
|
||||
.ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err),
|
||||
.ErrorSet => return writeErrorset(writer, value),
|
||||
.Union => |info| {
|
||||
if (info.tag_type) |TagType| {
|
||||
comptime var v = void;
|
||||
inline for (info.fields) |u_field| {
|
||||
if (value == @field(TagType, u_field.name))
|
||||
v = @field(value, u_field.name);
|
||||
}
|
||||
try writeArray(writer, .{
|
||||
@typeName(T),
|
||||
@tagName(@as(TagType, value)),
|
||||
v,
|
||||
});
|
||||
} else {
|
||||
try writeArray(writer, .{@typeName(T)});
|
||||
}
|
||||
},
|
||||
.Struct => |info| {
|
||||
if (info.is_tuple) {
|
||||
if (info.fields.len == 0) return writeNull(writer);
|
||||
try writeArrayHeader(writer, info.fields.len);
|
||||
inline for (info.fields) |f|
|
||||
try writeValue(writer, @field(value, f.name));
|
||||
} else {
|
||||
if (info.fields.len == 0) return writeNull(writer);
|
||||
try writeMapHeader(writer, info.fields.len);
|
||||
inline for (info.fields) |f| {
|
||||
try writeString(writer, f.name);
|
||||
try writeValue(writer, @field(value, f.name));
|
||||
}
|
||||
}
|
||||
},
|
||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||
.One => return writeValue(writer, value.*),
|
||||
.Many, .C => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||
.Slice => {
|
||||
if (ptr_info.child == u8) return writeString(writer, value);
|
||||
if (value.len == 0) return writeNull(writer);
|
||||
try writeArrayHeader(writer, value.len);
|
||||
for (value) |elem|
|
||||
try writeValue(writer, elem);
|
||||
},
|
||||
},
|
||||
.Array => |info| {
|
||||
if (info.child == u8) return writeString(writer, &value);
|
||||
if (value.len == 0) return writeNull(writer);
|
||||
try writeArrayHeader(writer, value.len);
|
||||
for (value) |elem|
|
||||
try writeValue(writer, elem);
|
||||
},
|
||||
.Vector => |info| {
|
||||
try writeArrayHeader(writer, info.len);
|
||||
var i: usize = 0;
|
||||
while (i < info.len) : (i += 1) {
|
||||
try writeValue(writer, value[i]);
|
||||
}
|
||||
},
|
||||
.Null => try writeNull(writer),
|
||||
else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt(buf: []u8, value: anytype) []const u8 {
|
||||
var stream = fixedBufferStream(buf);
|
||||
writeValue(stream.writer(), value) catch unreachable;
|
||||
return stream.getWritten();
|
||||
}
|
||||
|
||||
const CborType = struct { type: u8, minor: u5, major: u3 };
|
||||
|
||||
pub fn decodeType(iter: *[]const u8) error{CborTooShort}!CborType {
|
||||
if (iter.len < 1)
|
||||
return error.CborTooShort;
|
||||
const type_: u8 = iter.*[0];
|
||||
const bits: packed struct { minor: u5, major: u3 } = @bitCast(type_);
|
||||
iter.* = iter.*[1..];
|
||||
return .{ .type = type_, .minor = bits.minor, .major = bits.major };
|
||||
}
|
||||
|
||||
fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 {
|
||||
if (iter.len < 1)
|
||||
return error.CborTooShort;
|
||||
const v: u8 = iter.*[0];
|
||||
iter.* = iter.*[1..];
|
||||
var i = acc | v;
|
||||
if (length == 1)
|
||||
return i;
|
||||
i <<= 8;
|
||||
// return @call(.always_tail, decodeUIntLengthRecurse, .{ iter, length - 1, i }); FIXME: @call(.always_tail) seems broken as of 0.11.0-dev.2964+e9cbdb2cf
|
||||
return decodeUIntLengthRecurse(iter, length - 1, i);
|
||||
}
|
||||
|
||||
fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 {
|
||||
return decodeUIntLengthRecurse(iter, length, 0);
|
||||
}
|
||||
|
||||
fn decodePInt(iter: *[]const u8, minor: u5) !u64 {
|
||||
if (minor < 24) return minor;
|
||||
return switch (minor) {
|
||||
24 => decodeUIntLength(iter, 1), // 1 byte
|
||||
25 => decodeUIntLength(iter, 2), // 2 byte
|
||||
26 => decodeUIntLength(iter, 4), // 4 byte
|
||||
27 => decodeUIntLength(iter, 8), // 8 byte
|
||||
else => error.CborInvalidType,
|
||||
};
|
||||
}
|
||||
|
||||
fn decodeNInt(iter: *[]const u8, minor: u5) CborError!i64 {
|
||||
return -@as(i64, @intCast(try decodePInt(iter, minor) + 1));
|
||||
}
|
||||
|
||||
pub fn decodeMapHeader(iter: *[]const u8) CborError!usize {
|
||||
const t = try decodeType(iter);
|
||||
if (t.type == cbor_magic_null)
|
||||
return 0;
|
||||
if (t.major != 5)
|
||||
return error.CborInvalidType;
|
||||
return decodePInt(iter, t.minor);
|
||||
}
|
||||
|
||||
pub fn decodeArrayHeader(iter: *[]const u8) CborError!usize {
|
||||
const t = try decodeType(iter);
|
||||
if (t.type == cbor_magic_null)
|
||||
return 0;
|
||||
if (t.major != 4)
|
||||
return error.CborInvalidType;
|
||||
return decodePInt(iter, t.minor);
|
||||
}
|
||||
|
||||
fn decodeString(iter_: *[]const u8, minor: u5) CborError![]const u8 {
|
||||
var iter = iter_.*;
|
||||
const len = try decodePInt(&iter, minor);
|
||||
if (iter.len < len)
|
||||
return error.CborTooShort;
|
||||
const s = iter[0..len];
|
||||
iter = iter[len..];
|
||||
iter_.* = iter;
|
||||
return s;
|
||||
}
|
||||
|
||||
fn decodeBytes(iter: *[]const u8, minor: u5) CborError![]const u8 {
|
||||
return decodeString(iter, minor);
|
||||
}
|
||||
|
||||
fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
var n = try decodePInt(&iter, minor);
|
||||
while (n > 0) {
|
||||
const value = try arr.addOne();
|
||||
if (!try matchJsonValue(&iter, value, arr.allocator))
|
||||
return false;
|
||||
n -= 1;
|
||||
}
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
var n = try decodePInt(&iter, minor);
|
||||
while (n > 0) {
|
||||
var key: []u8 = undefined;
|
||||
var value: json.Value = .null;
|
||||
|
||||
if (!try matchString(&iter, &key))
|
||||
return false;
|
||||
if (!try matchJsonValue(&iter, &value, obj.allocator))
|
||||
return false;
|
||||
|
||||
_ = try obj.getOrPutValue(key, value);
|
||||
n -= 1;
|
||||
}
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
val.* = switch (t.major) {
|
||||
0 => blk: { // positive integer
|
||||
const v = try decodePInt(&iter, t.minor);
|
||||
if (v > maxInt(T))
|
||||
return error.CborIntegerTooLarge;
|
||||
break :blk @intCast(v);
|
||||
},
|
||||
1 => blk: { // negative integer
|
||||
const v = try decodeNInt(&iter, t.minor);
|
||||
if (v < minInt(T))
|
||||
return error.CborIntegerTooSmall;
|
||||
break :blk @intCast(v);
|
||||
},
|
||||
|
||||
else => return false,
|
||||
};
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool {
|
||||
var v: T = 0;
|
||||
return if (try matchInt(T, iter, &v)) v == val else false;
|
||||
}
|
||||
|
||||
pub fn matchBool(iter_: *[]const u8, v: *bool) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
if (t.major == 7) { // special
|
||||
if (t.type == cbor_magic_false) {
|
||||
v.* = false;
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
if (t.type == cbor_magic_true) {
|
||||
v.* = true;
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn matchBoolValue(iter: *[]const u8, val: bool) CborError!bool {
|
||||
var v: bool = false;
|
||||
return if (try matchBool(iter, &v)) v == val else false;
|
||||
}
|
||||
|
||||
fn skipString(iter: *[]const u8, minor: u5) CborError!void {
|
||||
const len = try decodePInt(iter, minor);
|
||||
if (iter.len < len)
|
||||
return error.CborTooShort;
|
||||
iter.* = iter.*[len..];
|
||||
}
|
||||
|
||||
fn skipBytes(iter: *[]const u8, minor: u5) CborError!void {
|
||||
return skipString(iter, minor);
|
||||
}
|
||||
|
||||
fn skipArray(iter: *[]const u8, minor: u5) CborError!void {
|
||||
var len = try decodePInt(iter, minor);
|
||||
while (len > 0) {
|
||||
try skipValue(iter);
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn skipMap(iter: *[]const u8, minor: u5) CborError!void {
|
||||
var len = try decodePInt(iter, minor);
|
||||
len *= 2;
|
||||
while (len > 0) {
|
||||
try skipValue(iter);
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skipValue(iter: *[]const u8) CborError!void {
|
||||
const t = try decodeType(iter);
|
||||
try skipValueType(iter, t.major, t.minor);
|
||||
}
|
||||
|
||||
fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void {
|
||||
switch (major) {
|
||||
0 => { // positive integer
|
||||
_ = try decodePInt(iter, minor);
|
||||
},
|
||||
1 => { // negative integer
|
||||
_ = try decodeNInt(iter, minor);
|
||||
},
|
||||
2 => { // bytes
|
||||
try skipBytes(iter, minor);
|
||||
},
|
||||
3 => { // string
|
||||
try skipString(iter, minor);
|
||||
},
|
||||
4 => { // array
|
||||
try skipArray(iter, minor);
|
||||
},
|
||||
5 => { // map
|
||||
try skipMap(iter, minor);
|
||||
},
|
||||
6 => { // tag
|
||||
return error.CborInvalidType;
|
||||
},
|
||||
7 => { // special
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
try skipValueType(&iter, t.major, t.minor);
|
||||
switch (t.major) {
|
||||
0, 1 => v.* = value_type.number, // positive integer or negative integer
|
||||
2 => v.* = value_type.bytes, // bytes
|
||||
3 => v.* = value_type.string, // string
|
||||
4 => v.* = value_type.array, // array
|
||||
5 => v.* = value_type.map, // map
|
||||
7 => { // special
|
||||
if (t.type == cbor_magic_null) {
|
||||
v.* = value_type.null;
|
||||
} else {
|
||||
if (t.type == cbor_magic_false or t.type == cbor_magic_true) {
|
||||
v.* = value_type.boolean;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn matchValueType(iter: *[]const u8, t: value_type) CborError!bool {
|
||||
var v: value_type = value_type.unknown;
|
||||
return if (try matchType(iter, &v)) (t == value_type.any or t == v) else false;
|
||||
}
|
||||
|
||||
pub fn matchString(iter_: *[]const u8, val: *[]const u8) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
val.* = switch (t.major) {
|
||||
2 => try decodeBytes(&iter, t.minor), // bytes
|
||||
3 => try decodeString(&iter, t.minor), // string
|
||||
else => return false,
|
||||
};
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn matchStringValue(iter: *[]const u8, lit: []const u8) CborError!bool {
|
||||
var val: []const u8 = undefined;
|
||||
return if (try matchString(iter, &val)) eql(u8, val, lit) else false;
|
||||
}
|
||||
|
||||
fn matchError(comptime T: type) noreturn {
|
||||
@compileError("cannot match type '" ++ @typeName(T) ++ "' to cbor stream");
|
||||
}
|
||||
|
||||
pub fn matchValue(iter: *[]const u8, value: anytype) CborError!bool {
|
||||
if (@TypeOf(value) == value_type)
|
||||
return matchValueType(iter, value);
|
||||
const T = comptime @TypeOf(value);
|
||||
if (comptime isExtractor(T))
|
||||
return value.extract(iter);
|
||||
return switch (comptime @typeInfo(T)) {
|
||||
.Int => return matchIntValue(T, iter, value),
|
||||
.ComptimeInt => return matchIntValue(i64, iter, value),
|
||||
.Bool => matchBoolValue(iter, value),
|
||||
.Pointer => |info| switch (info.size) {
|
||||
.One => matchValue(iter, value.*),
|
||||
.Many, .C => matchError(T),
|
||||
.Slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info),
|
||||
},
|
||||
.Struct => |info| if (info.is_tuple)
|
||||
matchArray(iter, value, info)
|
||||
else
|
||||
matchError(T),
|
||||
.Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info),
|
||||
else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||
};
|
||||
}
|
||||
|
||||
fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
const ret = switch (t.major) {
|
||||
0 => ret: { // positive integer
|
||||
v.* = json.Value{ .integer = @intCast(try decodePInt(&iter, t.minor)) };
|
||||
break :ret true;
|
||||
},
|
||||
1 => ret: { // negative integer
|
||||
v.* = json.Value{ .integer = try decodeNInt(&iter, t.minor) };
|
||||
break :ret true;
|
||||
},
|
||||
2 => ret: { // bytes
|
||||
break :ret false;
|
||||
},
|
||||
3 => ret: { // string
|
||||
v.* = json.Value{ .string = try decodeString(&iter, t.minor) };
|
||||
break :ret true;
|
||||
},
|
||||
4 => ret: { // array
|
||||
v.* = json.Value{ .array = json.Array.init(a) };
|
||||
break :ret try decodeJsonArray(&iter, t.minor, &v.array);
|
||||
},
|
||||
5 => ret: { // map
|
||||
v.* = json.Value{ .object = json.ObjectMap.init(a) };
|
||||
break :ret try decodeJsonObject(&iter, t.minor, &v.object);
|
||||
},
|
||||
6 => ret: { // tag
|
||||
break :ret false;
|
||||
},
|
||||
7 => ret: { // special
|
||||
switch (t.type) {
|
||||
cbor_magic_false => {
|
||||
v.* = json.Value{ .bool = false };
|
||||
break :ret true;
|
||||
},
|
||||
cbor_magic_true => {
|
||||
v.* = json.Value{ .bool = true };
|
||||
break :ret true;
|
||||
},
|
||||
cbor_magic_null => {
|
||||
v.* = json.Value{ .null = {} };
|
||||
break :ret true;
|
||||
},
|
||||
else => break :ret false,
|
||||
}
|
||||
},
|
||||
};
|
||||
if (ret) iter_.* = iter;
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn matchArrayMore(iter_: *[]const u8, n_: u64) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
var n = n_;
|
||||
while (n > 0) {
|
||||
if (!try matchValue(&iter, value_type.any))
|
||||
return false;
|
||||
n -= 1;
|
||||
}
|
||||
iter_.* = iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) CborError!bool {
|
||||
var iter = iter_.*;
|
||||
var n = try decodeArrayHeader(&iter);
|
||||
inline for (info.fields) |f| {
|
||||
const value = @field(arr, f.name);
|
||||
if (isMore(value))
|
||||
break;
|
||||
} else if (info.fields.len != n)
|
||||
return false;
|
||||
inline for (info.fields) |f| {
|
||||
const value = @field(arr, f.name);
|
||||
if (isMore(value))
|
||||
return matchArrayMore(&iter, n);
|
||||
if (n == 0) return false;
|
||||
const matched = try matchValue(&iter, @field(arr, f.name));
|
||||
if (!matched) return false;
|
||||
n -= 1;
|
||||
}
|
||||
if (n == 0) iter_.* = iter;
|
||||
return n == 0;
|
||||
}
|
||||
|
||||
fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool {
|
||||
var iter = iter_.*;
|
||||
const t = try decodeType(&iter);
|
||||
if (t.type == cbor_magic_null)
|
||||
return true;
|
||||
if (t.major != 5)
|
||||
return error.CborInvalidType;
|
||||
const ret = try decodeJsonObject(&iter, t.minor, obj);
|
||||
if (ret) iter_.* = iter;
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn match(buf: []const u8, pattern: anytype) CborError!bool {
|
||||
var iter: []const u8 = buf;
|
||||
return matchValue(&iter, pattern);
|
||||
}
|
||||
|
||||
fn extractError(comptime T: type) noreturn {
|
||||
@compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream");
|
||||
}
|
||||
|
||||
fn hasExtractorTag(info: anytype) bool {
|
||||
if (info.is_tuple) return false;
|
||||
inline for (info.decls) |decl| {
|
||||
if (comptime eql(u8, decl.name, "EXTRACTOR_TAG"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn isExtractor(comptime T: type) bool {
|
||||
return comptime switch (@typeInfo(T)) {
|
||||
.Struct => |info| hasExtractorTag(info),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
const JsonValueExtractor = struct {
|
||||
dest: *T,
|
||||
const Self = @This();
|
||||
pub const EXTRACTOR_TAG = struct {};
|
||||
const T = json.Value;
|
||||
|
||||
pub fn init(dest: *T) Self {
|
||||
return .{ .dest = dest };
|
||||
}
|
||||
|
||||
pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
|
||||
var null_heap_: [0]u8 = undefined;
|
||||
var heap = fba.init(&null_heap_);
|
||||
return matchJsonValue(iter, self.dest, heap.allocator());
|
||||
}
|
||||
};
|
||||
|
||||
const JsonObjectExtractor = struct {
|
||||
dest: *T,
|
||||
const Self = @This();
|
||||
pub const EXTRACTOR_TAG = struct {};
|
||||
const T = json.ObjectMap;
|
||||
|
||||
pub fn init(dest: *T) Self {
|
||||
return .{ .dest = dest };
|
||||
}
|
||||
|
||||
pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
|
||||
return matchJsonObject(iter, self.dest);
|
||||
}
|
||||
};
|
||||
|
||||
fn Extractor(comptime T: type) type {
|
||||
if (T == json.Value)
|
||||
return JsonValueExtractor;
|
||||
if (T == json.ObjectMap)
|
||||
return JsonObjectExtractor;
|
||||
return struct {
|
||||
dest: *T,
|
||||
const Self = @This();
|
||||
pub const EXTRACTOR_TAG = struct {};
|
||||
|
||||
pub fn init(dest: *T) Self {
|
||||
return .{ .dest = dest };
|
||||
}
|
||||
|
||||
pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
|
||||
switch (comptime @typeInfo(T)) {
|
||||
.Int, .ComptimeInt => return matchInt(T, iter, self.dest),
|
||||
.Bool => return matchBool(iter, self.dest),
|
||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||
.Slice => {
|
||||
if (ptr_info.child == u8) return matchString(iter, self.dest) else extractError(T);
|
||||
},
|
||||
else => extractError(T),
|
||||
},
|
||||
else => extractError(T),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn ExtractorType(comptime T: type) type {
|
||||
const T_type_info = @typeInfo(T);
|
||||
if (T_type_info != .Pointer) @compileError("extract requires a pointer argument");
|
||||
return Extractor(T_type_info.Pointer.child);
|
||||
}
|
||||
|
||||
pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) {
|
||||
comptime {
|
||||
if (!isExtractor(ExtractorType(@TypeOf(dest))))
|
||||
@compileError("isExtractor self check failed for " ++ @typeName(ExtractorType(@TypeOf(dest))));
|
||||
}
|
||||
return ExtractorType(@TypeOf(dest)).init(dest);
|
||||
}
|
||||
|
||||
const CborExtractor = struct {
|
||||
dest: *[]const u8,
|
||||
const Self = @This();
|
||||
pub const EXTRACTOR_TAG = struct {};
|
||||
|
||||
pub fn init(dest: *[]const u8) Self {
|
||||
return .{ .dest = dest };
|
||||
}
|
||||
|
||||
pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
|
||||
const b = iter.*;
|
||||
try skipValue(iter);
|
||||
self.dest.* = b[0..(b.len - iter.len)];
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn extract_cbor(dest: *[]const u8) CborExtractor {
|
||||
return CborExtractor.init(dest);
|
||||
}
|
||||
|
||||
pub fn JsonStream(comptime T: type) type {
|
||||
return struct {
|
||||
const Writer = T.Writer;
|
||||
const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 });
|
||||
|
||||
fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void {
|
||||
var count = try decodePInt(iter, minor);
|
||||
try w.beginArray();
|
||||
while (count > 0) : (count -= 1) {
|
||||
try jsonWriteValue(w, iter);
|
||||
}
|
||||
try w.endArray();
|
||||
}
|
||||
|
||||
fn jsonWriteMap(w: *JsonWriter, iter: *[]const u8, minor: u5) !void {
|
||||
var count = try decodePInt(iter, minor);
|
||||
try w.beginObject();
|
||||
while (count > 0) : (count -= 1) {
|
||||
const t = try decodeType(iter);
|
||||
if (t.major != 3) return error.CborInvalidType;
|
||||
try w.objectField(try decodeString(iter, t.minor));
|
||||
try jsonWriteValue(w, iter);
|
||||
}
|
||||
try w.endObject();
|
||||
}
|
||||
|
||||
pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void {
|
||||
const t = try decodeType(iter);
|
||||
if (t.type == cbor_magic_false)
|
||||
return w.write(false);
|
||||
if (t.type == cbor_magic_true)
|
||||
return w.write(true);
|
||||
if (t.type == cbor_magic_null)
|
||||
return w.write(null);
|
||||
return switch (t.major) {
|
||||
0 => w.write(try decodePInt(iter, t.minor)), // positive integer
|
||||
1 => w.write(try decodeNInt(iter, t.minor)), // negative integer
|
||||
2 => error.CborUnsupportedType, // bytes
|
||||
3 => w.write(try decodeString(iter, t.minor)), // string
|
||||
4 => jsonWriteArray(w, iter, t.minor), // array
|
||||
5 => jsonWriteMap(w, iter, t.minor), // map
|
||||
else => error.CborInvalidType,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toJson(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 {
|
||||
var fbs = fixedBufferStream(json_buf);
|
||||
var s = json.writeStream(fbs.writer(), .{});
|
||||
var iter: []const u8 = cbor_buf;
|
||||
try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter);
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 {
|
||||
var fbs = fixedBufferStream(json_buf);
|
||||
var s = json.writeStream(fbs.writer(), .{ .whitespace = .indent_1 });
|
||||
var iter: []const u8 = cbor_buf;
|
||||
try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter);
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
fn writeJsonValue(writer: anytype, value: json.Value) !void {
|
||||
try switch (value) {
|
||||
.array => |_| unreachable,
|
||||
.object => |_| unreachable,
|
||||
.null => writeNull(writer),
|
||||
.float => |_| error.CborUnsupportedType,
|
||||
inline else => |v| writeValue(writer, v),
|
||||
};
|
||||
}
|
||||
|
||||
fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) CborJsonError!usize {
|
||||
var partial = try std.BoundedArray(u8, 4096).init(0);
|
||||
var count: usize = 0;
|
||||
|
||||
var token = try scanner.next();
|
||||
while (token != end_token) : (token = try scanner.next()) {
|
||||
count += 1;
|
||||
switch (token) {
|
||||
.object_begin => try writeJsonObject(writer, scanner),
|
||||
.array_begin => try writeJsonArray(writer, scanner),
|
||||
|
||||
.true => try writeBool(writer, true),
|
||||
.false => try writeBool(writer, false),
|
||||
.null => try writeNull(writer),
|
||||
|
||||
.number => |v| {
|
||||
try partial.appendSlice(v);
|
||||
try writeJsonValue(writer, json.Value.parseFromNumberSlice(partial.slice()));
|
||||
try partial.resize(0);
|
||||
},
|
||||
.partial_number => |v| {
|
||||
try partial.appendSlice(v);
|
||||
count -= 1;
|
||||
},
|
||||
|
||||
.string => |v| {
|
||||
try partial.appendSlice(v);
|
||||
try writeString(writer, partial.slice());
|
||||
try partial.resize(0);
|
||||
},
|
||||
.partial_string => |v| {
|
||||
try partial.appendSlice(v);
|
||||
count -= 1;
|
||||
},
|
||||
.partial_string_escaped_1 => |v| {
|
||||
try partial.appendSlice(&v);
|
||||
count -= 1;
|
||||
},
|
||||
.partial_string_escaped_2 => |v| {
|
||||
try partial.appendSlice(&v);
|
||||
count -= 1;
|
||||
},
|
||||
.partial_string_escaped_3 => |v| {
|
||||
try partial.appendSlice(&v);
|
||||
count -= 1;
|
||||
},
|
||||
.partial_string_escaped_4 => |v| {
|
||||
try partial.appendSlice(&v);
|
||||
count -= 1;
|
||||
},
|
||||
|
||||
else => return error.SyntaxError,
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
pub const local_heap_size = 4096 * 16;
|
||||
|
||||
fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
|
||||
var buf: [local_heap_size]u8 = undefined;
|
||||
var stream = fixedBufferStream(&buf);
|
||||
const writer = stream.writer();
|
||||
const count = try jsonScanUntil(writer, scanner, .array_end);
|
||||
try writeArrayHeader(writer_, count);
|
||||
try writer_.writeAll(stream.getWritten());
|
||||
}
|
||||
|
||||
fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
|
||||
var buf: [local_heap_size]u8 = undefined;
|
||||
var stream = fixedBufferStream(&buf);
|
||||
const writer = stream.writer();
|
||||
const count = try jsonScanUntil(writer, scanner, .object_end);
|
||||
try writeMapHeader(writer_, count / 2);
|
||||
try writer_.writeAll(stream.getWritten());
|
||||
}
|
||||
|
||||
pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) ![]const u8 {
|
||||
var local_heap_: [local_heap_size]u8 = undefined;
|
||||
var heap = fba.init(&local_heap_);
|
||||
var stream = fixedBufferStream(cbor_buf);
|
||||
const writer = stream.writer();
|
||||
|
||||
var scanner = json.Scanner.initCompleteInput(heap.allocator(), json_buf);
|
||||
defer scanner.deinit();
|
||||
|
||||
_ = try jsonScanUntil(writer, &scanner, .end_of_document);
|
||||
return stream.getWritten();
|
||||
}
|
207
src/executor.hpp
Normal file
207
src/executor.hpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <netinet/in.h>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
using port_t = unsigned short;
|
||||
|
||||
namespace thespian::executor {
|
||||
|
||||
struct strand;
|
||||
|
||||
struct context_impl;
|
||||
using context_ref = std::shared_ptr<context_impl>;
|
||||
|
||||
struct context {
|
||||
static auto create() -> context;
|
||||
auto create_strand() -> strand;
|
||||
void run();
|
||||
auto pending_tasks() -> size_t;
|
||||
auto pending_posts() -> size_t;
|
||||
context_ref ref;
|
||||
};
|
||||
|
||||
struct strand_impl;
|
||||
using strand_ref = std::shared_ptr<strand_impl>;
|
||||
|
||||
struct strand {
|
||||
void post(std::function<void()>);
|
||||
strand_ref ref;
|
||||
};
|
||||
|
||||
struct signal_impl;
|
||||
using signal_dtor = void (*)(signal_impl *);
|
||||
using signal_ref = std::unique_ptr<signal_impl, signal_dtor>;
|
||||
|
||||
struct signal {
|
||||
using handler = std::function<void(const std::error_code &, int signum)>;
|
||||
|
||||
explicit signal(context &);
|
||||
explicit signal(strand &);
|
||||
void fires_on(int signum);
|
||||
void on_fired(handler);
|
||||
void cancel();
|
||||
signal_ref ref;
|
||||
};
|
||||
|
||||
struct timer_impl;
|
||||
using timer_dtor = void (*)(timer_impl *);
|
||||
using timer_ref = std::unique_ptr<timer_impl, timer_dtor>;
|
||||
|
||||
struct timer {
|
||||
using handler = std::function<void(const std::error_code &)>;
|
||||
|
||||
explicit timer(context &);
|
||||
explicit timer(strand &);
|
||||
void expires_at(std::chrono::time_point<std::chrono::system_clock>);
|
||||
void expires_after(std::chrono::microseconds);
|
||||
void on_expired(handler);
|
||||
void cancel();
|
||||
timer_ref ref;
|
||||
};
|
||||
|
||||
auto to_string(const in6_addr &ip) -> std::string;
|
||||
|
||||
constexpr auto receive_buffer_size{4L * 1024L};
|
||||
|
||||
struct endpoint {
|
||||
in6_addr ip;
|
||||
port_t port;
|
||||
};
|
||||
|
||||
namespace udp {
|
||||
|
||||
struct socket_impl;
|
||||
using socket_dtor = void (*)(socket_impl *);
|
||||
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
|
||||
|
||||
struct socket {
|
||||
using handler =
|
||||
std::function<void(const std::error_code &, std::size_t received)>;
|
||||
|
||||
explicit socket(strand &);
|
||||
auto bind(const in6_addr &ip, port_t port) -> std::error_code;
|
||||
[[nodiscard]] auto send_to(std::string_view data, in6_addr ip,
|
||||
port_t port) const -> size_t;
|
||||
void receive(handler);
|
||||
void close();
|
||||
[[nodiscard]] auto local_endpoint() const -> const endpoint &;
|
||||
[[nodiscard]] auto remote_endpoint() const -> const endpoint &;
|
||||
std::array<char, receive_buffer_size> receive_buffer{};
|
||||
socket_ref ref;
|
||||
};
|
||||
|
||||
} // namespace udp
|
||||
|
||||
namespace tcp {
|
||||
|
||||
struct socket_impl;
|
||||
using socket_dtor = void (*)(socket_impl *);
|
||||
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
|
||||
|
||||
struct socket {
|
||||
using connect_handler = std::function<void(const std::error_code &)>;
|
||||
using read_handler =
|
||||
std::function<void(const std::error_code &, std::size_t)>;
|
||||
using write_handler =
|
||||
std::function<void(const std::error_code &, std::size_t)>;
|
||||
|
||||
explicit socket(strand &);
|
||||
explicit socket(strand &, int fd);
|
||||
auto bind(const in6_addr &ip, port_t port) -> std::error_code;
|
||||
void connect(const in6_addr &ip, port_t port, connect_handler);
|
||||
void write(const std::vector<uint8_t> &data, write_handler);
|
||||
void read(read_handler);
|
||||
void close();
|
||||
static void close(int fd);
|
||||
auto release() -> int;
|
||||
[[nodiscard]] auto local_endpoint() const -> const endpoint &;
|
||||
std::array<char, receive_buffer_size> read_buffer{};
|
||||
socket_ref ref;
|
||||
};
|
||||
|
||||
struct acceptor_impl;
|
||||
using acceptor_dtor = void (*)(acceptor_impl *);
|
||||
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
|
||||
|
||||
struct acceptor {
|
||||
using handler = std::function<void(int fd, const std::error_code &)>;
|
||||
|
||||
explicit acceptor(strand &);
|
||||
auto bind(const in6_addr &, port_t) -> std::error_code;
|
||||
auto listen() -> std::error_code;
|
||||
void accept(handler);
|
||||
void close();
|
||||
[[nodiscard]] auto local_endpoint() const -> endpoint;
|
||||
acceptor_ref ref;
|
||||
};
|
||||
|
||||
} // namespace tcp
|
||||
|
||||
namespace unx {
|
||||
|
||||
struct socket_impl;
|
||||
using socket_dtor = void (*)(socket_impl *);
|
||||
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
|
||||
|
||||
struct socket {
|
||||
using connect_handler = std::function<void(const std::error_code &)>;
|
||||
using read_handler =
|
||||
std::function<void(const std::error_code &, std::size_t)>;
|
||||
using write_handler =
|
||||
std::function<void(const std::error_code &, std::size_t)>;
|
||||
|
||||
explicit socket(strand &);
|
||||
explicit socket(strand &, int fd);
|
||||
auto bind(std::string_view path) -> std::error_code;
|
||||
void connect(std::string_view path, connect_handler);
|
||||
void write(const std::vector<uint8_t> &data, write_handler);
|
||||
void read(read_handler);
|
||||
void close();
|
||||
auto release() -> int;
|
||||
std::array<char, receive_buffer_size> read_buffer{};
|
||||
socket_ref ref;
|
||||
};
|
||||
|
||||
struct acceptor_impl;
|
||||
using acceptor_dtor = void (*)(acceptor_impl *);
|
||||
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
|
||||
|
||||
struct acceptor {
|
||||
using handler = std::function<void(int fd, const std::error_code &)>;
|
||||
|
||||
explicit acceptor(strand &);
|
||||
auto bind(std::string_view path) -> std::error_code;
|
||||
auto listen() -> std::error_code;
|
||||
void accept(handler);
|
||||
void close();
|
||||
acceptor_ref ref;
|
||||
};
|
||||
|
||||
} // namespace unx
|
||||
|
||||
namespace file_descriptor {
|
||||
|
||||
struct watcher_impl;
|
||||
using watcher_dtor = void (*)(watcher_impl *);
|
||||
using watcher_ref = std::unique_ptr<watcher_impl, watcher_dtor>;
|
||||
|
||||
struct watcher {
|
||||
using handler = std::function<void(const std::error_code &)>;
|
||||
|
||||
explicit watcher(strand &, int fd);
|
||||
void wait_read(handler);
|
||||
void wait_write(handler);
|
||||
void cancel();
|
||||
watcher_ref ref;
|
||||
};
|
||||
|
||||
} // namespace file_descriptor
|
||||
|
||||
} // namespace thespian::executor
|
645
src/executor_asio.cpp
Normal file
645
src/executor_asio.cpp
Normal file
|
@ -0,0 +1,645 @@
|
|||
#include "asio/error_code.hpp"
|
||||
#include "asio/posix/descriptor_base.hpp"
|
||||
#include "executor.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include <asio/basic_socket_acceptor.hpp>
|
||||
#include <asio/bind_executor.hpp>
|
||||
#include <asio/io_context.hpp>
|
||||
#include <asio/ip/address_v6.hpp>
|
||||
#include <asio/ip/tcp.hpp>
|
||||
#include <asio/ip/udp.hpp>
|
||||
#include <asio/local/stream_protocol.hpp>
|
||||
#include <asio/posix/stream_descriptor.hpp>
|
||||
#include <asio/signal_set.hpp>
|
||||
#include <asio/socket_base.hpp>
|
||||
#include <asio/strand.hpp>
|
||||
#include <asio/system_timer.hpp>
|
||||
#include <asio/thread.hpp>
|
||||
|
||||
#include <csignal>
|
||||
#include <vector>
|
||||
|
||||
using asio::bind_executor;
|
||||
using asio::buffer;
|
||||
using asio::io_context;
|
||||
using asio::string_view;
|
||||
using asio::system_timer;
|
||||
using asio::thread;
|
||||
using asio::posix::stream_descriptor;
|
||||
using std::atomic;
|
||||
using std::error_code;
|
||||
using std::function;
|
||||
using std::make_shared;
|
||||
using std::make_unique;
|
||||
using std::max;
|
||||
using std::memory_order_relaxed;
|
||||
using std::min;
|
||||
using std::move;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
using std::weak_ptr;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::system_clock;
|
||||
using std::chrono::time_point;
|
||||
|
||||
namespace thespian::executor {
|
||||
|
||||
const char *MAX_THREAD_STR = getenv("MAX_THREAD"); // NOLINT
|
||||
const auto MAX_THREAD =
|
||||
static_cast<long>(atoi(MAX_THREAD_STR ? MAX_THREAD_STR : "64")); // NOLINT
|
||||
|
||||
const auto threads = min(sysconf(_SC_NPROCESSORS_ONLN), MAX_THREAD);
|
||||
|
||||
struct context_impl {
|
||||
context_impl() : asio{make_unique<io_context>(threads)} {}
|
||||
unique_ptr<io_context> asio;
|
||||
atomic<size_t> pending;
|
||||
atomic<size_t> posts;
|
||||
};
|
||||
|
||||
auto context::create() -> context {
|
||||
if (::signal(SIGPIPE, SIG_IGN) == SIG_ERR) // NOLINT
|
||||
abort();
|
||||
return {context_ref(new context_impl(), [](context_impl *p) { delete p; })};
|
||||
}
|
||||
|
||||
struct strand_impl {
|
||||
explicit strand_impl(const context_ref &ctx)
|
||||
: ctx{ctx}, strand_{*ctx->asio} {}
|
||||
void post(function<void()> f) {
|
||||
ctx->posts.fetch_add(1);
|
||||
strand_.post([&posts = ctx->posts, f = move(f)]() {
|
||||
posts.fetch_sub(1);
|
||||
f();
|
||||
});
|
||||
}
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
};
|
||||
|
||||
auto context::create_strand() -> strand {
|
||||
return {make_shared<strand_impl>(ref)};
|
||||
}
|
||||
|
||||
void strand::post(function<void()> f) { ref->post(move(f)); }
|
||||
|
||||
auto context::run() -> void {
|
||||
const auto spawn_threads = max(threads - 1, 0L);
|
||||
vector<thread *> running;
|
||||
for (auto i = 0; i < spawn_threads; ++i) {
|
||||
auto *t = new thread([ctx = ref]() { ctx->asio->run(); });
|
||||
running.push_back(t);
|
||||
}
|
||||
ref->asio->run();
|
||||
for (auto &t : running) {
|
||||
t->join();
|
||||
delete t;
|
||||
}
|
||||
}
|
||||
|
||||
auto context::pending_tasks() -> size_t { return ref->pending.load(); }
|
||||
auto context::pending_posts() -> size_t { return ref->posts.load(); }
|
||||
|
||||
} // namespace thespian::executor
|
||||
|
||||
namespace {
|
||||
|
||||
static auto to_address(const in6_addr &ip) -> const asio::ip::address_v6 {
|
||||
asio::ip::address_v6::bytes_type bytes;
|
||||
memcpy(bytes.data(), ip.s6_addr, sizeof(ip.s6_addr));
|
||||
return asio::ip::address_v6{bytes};
|
||||
}
|
||||
|
||||
static auto to_in6_addr(const asio::ip::address_v6 &address) -> in6_addr {
|
||||
auto bytes = address.to_bytes();
|
||||
in6_addr ip{};
|
||||
memcpy(ip.s6_addr, bytes.data(), sizeof(ip.s6_addr));
|
||||
return ip;
|
||||
}
|
||||
|
||||
static auto to_in6_addr(const asio::ip::udp::endpoint &ep) -> in6_addr {
|
||||
return to_in6_addr(ep.address().to_v6());
|
||||
}
|
||||
|
||||
static auto to_in6_addr(const asio::ip::tcp::endpoint &ep) -> in6_addr {
|
||||
return to_in6_addr(ep.address().to_v6());
|
||||
}
|
||||
|
||||
static auto to_endpoint_tcp(const in6_addr &ip, port_t port)
|
||||
-> const asio::ip::tcp::endpoint {
|
||||
return {to_address(ip), port};
|
||||
}
|
||||
|
||||
static auto to_endpoint_udp(const in6_addr &ip, port_t port)
|
||||
-> const asio::ip::udp::endpoint {
|
||||
return {to_address(ip), port};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace thespian::executor {
|
||||
|
||||
auto to_string(const in6_addr &ip) -> string {
|
||||
return to_address(ip).to_string();
|
||||
}
|
||||
|
||||
struct signal_impl {
|
||||
explicit signal_impl(const context_ref &ctx)
|
||||
: ctx{ctx}, signals_{*ctx->asio} {}
|
||||
explicit signal_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, signals_{*ctx->asio} {}
|
||||
void fires_on(int signum) { signals_.add(signum); }
|
||||
void on_fired(signal::handler f) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
signals_.async_wait([c_ = ctx, f = move(f), t = move(weak_token)](
|
||||
const error_code &ec, int signum) {
|
||||
c_->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock()) {
|
||||
f(ec, signum);
|
||||
}
|
||||
});
|
||||
}
|
||||
void cancel() { signals_.cancel(); }
|
||||
context_ref ctx;
|
||||
asio::signal_set signals_;
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
signal::signal(context &ctx)
|
||||
: ref{signal_ref(new signal_impl(ctx.ref),
|
||||
[](signal_impl *p) { delete p; })} {}
|
||||
|
||||
signal::signal(strand &ref)
|
||||
: ref{signal_ref(new signal_impl(ref), [](signal_impl *p) { delete p; })} {}
|
||||
|
||||
void signal::fires_on(int signum) { ref->fires_on(signum); }
|
||||
void signal::on_fired(handler f) { ref->on_fired(move(f)); }
|
||||
void signal::cancel() { ref->cancel(); }
|
||||
|
||||
struct timer_impl {
|
||||
explicit timer_impl(const context_ref &ctx) : ctx{ctx}, timer_{*ctx->asio} {}
|
||||
explicit timer_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, timer_{*ctx->asio} {}
|
||||
void expires_at(time_point<system_clock> t) { timer_.expires_at(t); }
|
||||
void expires_after(microseconds us) { timer_.expires_after(us); }
|
||||
void on_expired(timer::handler f) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
timer_.async_wait(
|
||||
[c_ = ctx, f = move(f), t = move(weak_token)](const error_code &ec) {
|
||||
c_->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock()) {
|
||||
f(ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
void cancel() { timer_.cancel(); }
|
||||
context_ref ctx;
|
||||
system_timer timer_;
|
||||
bool pending_{};
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
timer::timer(context &ctx)
|
||||
: ref{timer_ref(new timer_impl(ctx.ref), [](timer_impl *p) { delete p; })} {
|
||||
}
|
||||
|
||||
timer::timer(strand &ref)
|
||||
: ref{timer_ref(new timer_impl(ref), [](timer_impl *p) { delete p; })} {}
|
||||
|
||||
void timer::expires_at(time_point<system_clock> t) { ref->expires_at(t); }
|
||||
void timer::expires_after(microseconds us) { ref->expires_after(us); }
|
||||
void timer::on_expired(handler f) { ref->on_expired(move(f)); }
|
||||
void timer::cancel() { ref->cancel(); }
|
||||
|
||||
namespace udp {
|
||||
|
||||
struct socket_impl {
|
||||
explicit socket_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_},
|
||||
socket_{*ctx->asio, ::asio::ip::udp::v6()} {}
|
||||
auto bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
error_code ec;
|
||||
socket_.bind(to_endpoint_udp(ip, port), ec);
|
||||
return ec;
|
||||
}
|
||||
[[nodiscard]] auto send_to(string_view data, in6_addr ip, port_t port)
|
||||
-> size_t {
|
||||
return socket_.send_to(buffer(data.data(), data.size()),
|
||||
to_endpoint_udp(ip, port));
|
||||
}
|
||||
void receive(socket &s, socket::handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_receive_from(
|
||||
buffer(s.receive_buffer), asio_remote_endpoint_,
|
||||
bind_executor(strand_,
|
||||
[c = ctx, ep = &remote_endpoint_,
|
||||
aep = &asio_remote_endpoint_, t = move(weak_token),
|
||||
h = move(h)](const error_code &ec, size_t received) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock()) {
|
||||
ep->ip = to_in6_addr(*aep);
|
||||
ep->port = aep->port();
|
||||
h(ec, received);
|
||||
}
|
||||
}));
|
||||
}
|
||||
void close() { socket_.close(); }
|
||||
auto local_endpoint() -> const endpoint & {
|
||||
auto ep = socket_.local_endpoint();
|
||||
local_endpoint_.ip = to_in6_addr(ep);
|
||||
local_endpoint_.port = ep.port();
|
||||
return local_endpoint_;
|
||||
}
|
||||
auto remote_endpoint() -> const endpoint & { return remote_endpoint_; }
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
asio::ip::udp::socket socket_;
|
||||
asio::ip::udp::endpoint asio_remote_endpoint_;
|
||||
executor::endpoint local_endpoint_{};
|
||||
executor::endpoint remote_endpoint_{};
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
socket::socket(strand &strand)
|
||||
: ref{socket_ref(new socket_impl(strand),
|
||||
[](socket_impl *p) { delete p; })} {}
|
||||
|
||||
auto socket::bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
return ref->bind(ip, port);
|
||||
}
|
||||
|
||||
auto socket::send_to(string_view data, in6_addr ip, port_t port) const
|
||||
-> size_t {
|
||||
return ref->send_to(data, ip, port);
|
||||
}
|
||||
|
||||
void socket::receive(handler h) { ref->receive(*this, move(h)); }
|
||||
|
||||
void socket::close() {}
|
||||
|
||||
auto socket::local_endpoint() const -> const endpoint & {
|
||||
return ref->local_endpoint();
|
||||
}
|
||||
|
||||
auto socket::remote_endpoint() const -> const endpoint & {
|
||||
return ref->remote_endpoint();
|
||||
}
|
||||
|
||||
} // namespace udp
|
||||
|
||||
namespace tcp {
|
||||
|
||||
struct socket_impl {
|
||||
explicit socket_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_},
|
||||
socket_{*ctx->asio, asio::ip::tcp::v6()} {}
|
||||
explicit socket_impl(strand &strand, int fd)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_},
|
||||
socket_{*ctx->asio, asio::ip::tcp::v6(), fd} {}
|
||||
auto bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
error_code ec;
|
||||
socket_.bind(to_endpoint_tcp(ip, port), ec);
|
||||
return ec;
|
||||
}
|
||||
void connect(const in6_addr &ip, port_t port, socket::connect_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_connect(
|
||||
to_endpoint_tcp(ip, port),
|
||||
bind_executor(strand_, [c = ctx, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec);
|
||||
}));
|
||||
}
|
||||
void write(const vector<uint8_t> &data, socket::write_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_write_some(
|
||||
buffer(data.data(), data.size()),
|
||||
bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)](
|
||||
const error_code &ec, size_t written) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec, written);
|
||||
}));
|
||||
}
|
||||
void read(socket &s, socket::read_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_read_some(
|
||||
buffer(s.read_buffer.data(), s.read_buffer.size()),
|
||||
bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)](
|
||||
const error_code &ec, size_t read) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec, read);
|
||||
}));
|
||||
}
|
||||
void close() { socket_.close(); }
|
||||
auto release() -> int { return socket_.release(); }
|
||||
auto local_endpoint() -> const endpoint & {
|
||||
auto ep = socket_.local_endpoint();
|
||||
local_endpoint_.ip = to_in6_addr(ep);
|
||||
local_endpoint_.port = ep.port();
|
||||
return local_endpoint_;
|
||||
}
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
asio::ip::tcp::socket socket_;
|
||||
asio::ip::tcp::endpoint remote_endpoint_;
|
||||
executor::endpoint local_endpoint_{};
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
socket::socket(strand &strand)
|
||||
: ref{socket_ref(new socket_impl(strand),
|
||||
[](socket_impl *p) { delete p; })} {}
|
||||
|
||||
socket::socket(strand &strand, int fd)
|
||||
: ref{socket_ref(new socket_impl(strand, fd),
|
||||
[](socket_impl *p) { delete p; })} {}
|
||||
|
||||
auto socket::bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
return ref->bind(ip, port);
|
||||
}
|
||||
|
||||
void socket::connect(const in6_addr &ip, port_t port, connect_handler h) {
|
||||
ref->connect(ip, port, move(h));
|
||||
}
|
||||
|
||||
void socket::write(const vector<uint8_t> &data, write_handler h) {
|
||||
ref->write(data, move(h));
|
||||
}
|
||||
void socket::read(read_handler h) { ref->read(*this, move(h)); }
|
||||
|
||||
void socket::close() { ref->close(); }
|
||||
void socket::close(int fd) { ::close(fd); }
|
||||
auto socket::release() -> int { return ref->release(); }
|
||||
|
||||
auto socket::local_endpoint() const -> const endpoint & {
|
||||
return ref->local_endpoint();
|
||||
}
|
||||
|
||||
struct acceptor_impl {
|
||||
explicit acceptor_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_},
|
||||
acceptor_{*ctx->asio, asio::ip::tcp::v6()}, socket_{*ctx->asio} {}
|
||||
auto bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
error_code ec;
|
||||
acceptor_.bind(to_endpoint_tcp(ip, port), ec);
|
||||
return ec;
|
||||
}
|
||||
auto listen() -> error_code {
|
||||
error_code ec;
|
||||
acceptor_.listen(asio::socket_base::max_listen_connections, ec);
|
||||
return ec;
|
||||
}
|
||||
void accept(acceptor::handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
acceptor_.async_accept(
|
||||
socket_,
|
||||
bind_executor(strand_, [c = ctx, s = &socket_, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec ? 0 : s->release(), ec);
|
||||
}));
|
||||
}
|
||||
void close() { acceptor_.close(); }
|
||||
auto local_endpoint() -> const endpoint & {
|
||||
auto ep = acceptor_.local_endpoint();
|
||||
local_endpoint_.ip = to_in6_addr(ep);
|
||||
local_endpoint_.port = ep.port();
|
||||
return local_endpoint_;
|
||||
}
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
asio::ip::tcp::socket socket_;
|
||||
executor::endpoint local_endpoint_{};
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
acceptor::acceptor(strand &strand)
|
||||
: ref{acceptor_ref(new acceptor_impl(strand),
|
||||
[](acceptor_impl *p) { delete p; })} {}
|
||||
|
||||
auto acceptor::bind(const in6_addr &ip, port_t port) -> error_code {
|
||||
return ref->bind(ip, port);
|
||||
}
|
||||
|
||||
auto acceptor::listen() -> error_code { return ref->listen(); }
|
||||
|
||||
void acceptor::accept(acceptor::handler h) { ref->accept(move(h)); }
|
||||
|
||||
void acceptor::close() { ref->close(); }
|
||||
|
||||
[[nodiscard]] auto acceptor::local_endpoint() const -> endpoint {
|
||||
return ref->local_endpoint();
|
||||
}
|
||||
|
||||
} // namespace tcp
|
||||
|
||||
namespace unx {
|
||||
|
||||
struct socket_impl {
|
||||
explicit socket_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, socket_{
|
||||
*ctx->asio} {}
|
||||
explicit socket_impl(strand &strand, int fd)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, socket_{*ctx->asio,
|
||||
fd} {}
|
||||
auto bind(string_view path) -> error_code {
|
||||
error_code ec;
|
||||
socket_.bind(path, ec);
|
||||
return ec;
|
||||
}
|
||||
void connect(string_view path, socket::connect_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_connect(
|
||||
path,
|
||||
bind_executor(strand_, [c = ctx, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec);
|
||||
}));
|
||||
}
|
||||
void write(const vector<uint8_t> &data, socket::write_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_write_some(
|
||||
buffer(data.data(), data.size()),
|
||||
bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)](
|
||||
const error_code &ec, size_t written) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec, written);
|
||||
}));
|
||||
}
|
||||
void read(socket &s, socket::read_handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
socket_.async_read_some(
|
||||
buffer(s.read_buffer.data(), s.read_buffer.size()),
|
||||
bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)](
|
||||
const error_code &ec, size_t read) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec, read);
|
||||
}));
|
||||
}
|
||||
void close() { socket_.close(); }
|
||||
auto release() -> int { return socket_.release(); }
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
asio::local::stream_protocol::socket socket_;
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
socket::socket(strand &strand)
|
||||
: ref{socket_ref(new socket_impl(strand),
|
||||
[](socket_impl *p) { delete p; })} {}
|
||||
|
||||
socket::socket(strand &strand, int fd)
|
||||
: ref{socket_ref(new socket_impl(strand, fd),
|
||||
[](socket_impl *p) { delete p; })} {}
|
||||
|
||||
auto socket::bind(string_view path) -> error_code { return ref->bind(path); }
|
||||
|
||||
void socket::connect(string_view path, connect_handler h) {
|
||||
ref->connect(path, move(h));
|
||||
}
|
||||
|
||||
void socket::write(const vector<uint8_t> &data, write_handler h) {
|
||||
ref->write(data, move(h));
|
||||
}
|
||||
void socket::read(read_handler h) { ref->read(*this, move(h)); }
|
||||
|
||||
void socket::close() { ref->close(); }
|
||||
auto socket::release() -> int { return ref->release(); }
|
||||
|
||||
struct acceptor_impl {
|
||||
explicit acceptor_impl(strand &strand)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_},
|
||||
acceptor_{*ctx->asio, asio::local::stream_protocol()},
|
||||
socket_{*ctx->asio} {}
|
||||
auto bind(string_view path) -> error_code {
|
||||
error_code ec;
|
||||
acceptor_.bind(path, ec);
|
||||
return ec;
|
||||
}
|
||||
auto listen() -> error_code {
|
||||
error_code ec;
|
||||
acceptor_.listen(asio::socket_base::max_listen_connections, ec);
|
||||
return ec;
|
||||
}
|
||||
void accept(acceptor::handler h) {
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
acceptor_.async_accept(
|
||||
socket_,
|
||||
bind_executor(strand_, [c = ctx, s = &socket_, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock())
|
||||
h(ec ? 0 : s->release(), ec);
|
||||
}));
|
||||
}
|
||||
void close() { acceptor_.close(); }
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
asio::local::stream_protocol::acceptor acceptor_;
|
||||
asio::local::stream_protocol::socket socket_;
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
};
|
||||
|
||||
acceptor::acceptor(strand &strand)
|
||||
: ref{acceptor_ref(new acceptor_impl(strand),
|
||||
[](acceptor_impl *p) { delete p; })} {}
|
||||
|
||||
auto acceptor::bind(string_view path) -> error_code { return ref->bind(path); }
|
||||
|
||||
auto acceptor::listen() -> error_code { return ref->listen(); }
|
||||
|
||||
void acceptor::accept(acceptor::handler h) { ref->accept(move(h)); }
|
||||
|
||||
void acceptor::close() { ref->close(); }
|
||||
|
||||
} // namespace unx
|
||||
|
||||
namespace file_descriptor {
|
||||
|
||||
struct watcher_impl {
|
||||
explicit watcher_impl(strand &strand, int fd)
|
||||
: ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, fd_{*ctx->asio,
|
||||
fd} {}
|
||||
|
||||
void wait_read(watcher::handler h) {
|
||||
if (!read_in_progress_) {
|
||||
read_in_progress_ = true;
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
fd_.async_wait(
|
||||
asio::posix::descriptor_base::wait_type::wait_read,
|
||||
bind_executor(strand_, [c = ctx, b = &read_in_progress_, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock()) {
|
||||
*b = false;
|
||||
h(ec);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void wait_write(watcher::handler h) {
|
||||
if (!write_in_progress_) {
|
||||
write_in_progress_ = true;
|
||||
ctx->pending.fetch_add(1, memory_order_relaxed);
|
||||
weak_ptr<bool> weak_token{token_};
|
||||
fd_.async_wait(
|
||||
asio::posix::descriptor_base::wait_type::wait_write,
|
||||
bind_executor(strand_, [c = ctx, b = &write_in_progress_, h = move(h),
|
||||
t = move(weak_token)](const error_code &ec) {
|
||||
c->pending.fetch_sub(1, memory_order_relaxed);
|
||||
if (auto p = t.lock()) {
|
||||
*b = false;
|
||||
h(ec);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() { fd_.cancel(); }
|
||||
|
||||
context_ref ctx;
|
||||
io_context::strand strand_;
|
||||
stream_descriptor fd_;
|
||||
shared_ptr<bool> token_{make_shared<bool>(true)};
|
||||
bool read_in_progress_{false};
|
||||
bool write_in_progress_{false};
|
||||
};
|
||||
|
||||
watcher::watcher(strand &strand, int fd)
|
||||
: ref{watcher_ref(new watcher_impl(strand, fd),
|
||||
[](watcher_impl *p) { delete p; })} {}
|
||||
|
||||
void watcher::wait_read(watcher::handler h) { ref->wait_read(move(h)); }
|
||||
void watcher::wait_write(watcher::handler h) { ref->wait_write(move(h)); }
|
||||
void watcher::cancel() { ref->cancel(); }
|
||||
|
||||
} // namespace file_descriptor
|
||||
|
||||
} // namespace thespian::executor
|
157
src/hub.cpp
Normal file
157
src/hub.cpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#include <thespian/debug.hpp>
|
||||
#include <thespian/endpoint.hpp>
|
||||
#include <thespian/hub.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using cbor::more;
|
||||
using cbor::type;
|
||||
using std::deque;
|
||||
using std::lock_guard;
|
||||
using std::make_shared;
|
||||
using std::move;
|
||||
using std::mutex;
|
||||
using std::pair;
|
||||
using std::set;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::vector;
|
||||
|
||||
namespace thespian {
|
||||
|
||||
using filter = hub::filter;
|
||||
|
||||
hub::pipe::pipe() = default;
|
||||
hub::pipe::~pipe() = default;
|
||||
|
||||
struct subscribe_queue : public hub::pipe {
|
||||
[[nodiscard]] auto push(const handle &from, filter f) {
|
||||
lock_guard<mutex> lock(m_);
|
||||
q_.push_back(move(f));
|
||||
return from.send("subscribe_filtered");
|
||||
}
|
||||
auto pop() -> filter {
|
||||
lock_guard<mutex> lock(m_);
|
||||
filter f = move(q_.front());
|
||||
q_.pop_front();
|
||||
return f;
|
||||
}
|
||||
|
||||
private:
|
||||
deque<filter> q_;
|
||||
mutex m_;
|
||||
};
|
||||
|
||||
struct hub_impl {
|
||||
hub_impl(const hub_impl &) = delete;
|
||||
hub_impl(hub_impl &&) = delete;
|
||||
auto operator=(const hub_impl &) -> hub_impl & = delete;
|
||||
auto operator=(hub_impl &&) -> hub_impl & = delete;
|
||||
|
||||
explicit hub_impl(shared_ptr<subscribe_queue> q) : q_(move(q)) {}
|
||||
~hub_impl() = default;
|
||||
|
||||
auto receive(handle from, buffer msg) {
|
||||
if (msg("broadcast", type::array)) {
|
||||
msg.erase(msg.raw_cbegin(), msg.raw_cbegin() + 11);
|
||||
for (auto const &s : subscribers_) {
|
||||
result ret = ok();
|
||||
if (s.second) {
|
||||
if (s.second(msg)) {
|
||||
ret = s.first.send_raw(msg);
|
||||
}
|
||||
} else {
|
||||
ret = s.first.send_raw(msg);
|
||||
}
|
||||
// if (not ret)
|
||||
// FIXME: remove dead subscriptions from subscribers_
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
if (msg("subscribe")) {
|
||||
subscribers_.emplace_back(move(from), filter{});
|
||||
return ok();
|
||||
}
|
||||
|
||||
if (msg("subscribe_filtered")) {
|
||||
subscribers_.emplace_back(move(from), q_->pop());
|
||||
return ok();
|
||||
}
|
||||
|
||||
buffer::range r;
|
||||
if (msg("subscribe_messages", extract(r))) {
|
||||
set<string> msgs;
|
||||
for (const auto val : r)
|
||||
msgs.insert(string(val));
|
||||
subscribers_.emplace_back(move(from), [msgs](auto m) {
|
||||
string tag;
|
||||
return m(extract(tag), more) && (msgs.find(tag) != msgs.end());
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
string_view desc;
|
||||
if (msg("listen", extract(desc))) {
|
||||
thespian::endpoint::unx::listen(desc);
|
||||
}
|
||||
|
||||
if (msg("shutdown"))
|
||||
return exit("shutdown");
|
||||
|
||||
return unexpected(msg);
|
||||
}
|
||||
|
||||
using subscriber_list_t = vector<pair<handle, filter>>;
|
||||
subscriber_list_t subscribers_;
|
||||
shared_ptr<subscribe_queue> q_;
|
||||
};
|
||||
|
||||
hub::hub(const handle &h, shared_ptr<pipe> p) : handle(h), pipe_(move(p)) {}
|
||||
|
||||
auto hub::subscribe() const -> result {
|
||||
link(*this);
|
||||
return send("subscribe");
|
||||
}
|
||||
|
||||
auto hub::subscribe(filter f) const -> result {
|
||||
link(*this);
|
||||
return dynamic_cast<subscribe_queue &>(*pipe_).push(
|
||||
static_cast<const handle &>(*this), move(f));
|
||||
}
|
||||
|
||||
auto hub::listen(string_view desc) const -> result {
|
||||
return send("listen", desc);
|
||||
}
|
||||
|
||||
auto hub::create(string_view name) -> expected<hub, error> {
|
||||
auto q = make_shared<subscribe_queue>();
|
||||
auto ret = spawn_link(
|
||||
[=]() {
|
||||
receive([p{make_shared<hub_impl>(q)}](auto from, auto m) {
|
||||
return p->receive(move(from), move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
name);
|
||||
if (not ret)
|
||||
return to_error(ret.error());
|
||||
return hub{ret.value(), q};
|
||||
}
|
||||
|
||||
auto operator==(const handle &a, const hub &b) -> bool {
|
||||
return a == static_cast<const handle &>(b);
|
||||
}
|
||||
|
||||
auto operator==(const hub &a, const handle &b) -> bool {
|
||||
return static_cast<const handle &>(a) == b;
|
||||
}
|
||||
|
||||
} // namespace thespian
|
1988
src/instance.cpp
Normal file
1988
src/instance.cpp
Normal file
File diff suppressed because it is too large
Load diff
233
src/subprocess.zig
Normal file
233
src/subprocess.zig
Normal file
|
@ -0,0 +1,233 @@
|
|||
const std = @import("std");
|
||||
const cbor = @import("cbor");
|
||||
const tp = @import("thespian.zig");
|
||||
|
||||
pid: ?tp.pid,
|
||||
stdin_behavior: std.ChildProcess.StdIo,
|
||||
|
||||
const Self = @This();
|
||||
pub const max_chunk_size = 4096 - 32;
|
||||
pub const Writer = std.io.Writer(*Self, error{Exit}, write);
|
||||
pub const BufferedWriter = std.io.BufferedWriter(max_chunk_size, Writer);
|
||||
|
||||
pub fn init(a: std.mem.Allocator, argv: tp.message, tag: [:0]const u8, stdin_behavior: std.ChildProcess.StdIo) !Self {
|
||||
return .{
|
||||
.pid = try Proc.create(a, argv, tag, stdin_behavior),
|
||||
.stdin_behavior = stdin_behavior,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
if (self.pid) |pid| {
|
||||
pid.deinit();
|
||||
self.pid = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(self: *Self, bytes: []const u8) error{Exit}!usize {
|
||||
try self.send(bytes);
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
pub fn send(self: *const Self, bytes_: []const u8) tp.result {
|
||||
if (self.stdin_behavior != .Pipe) return tp.exit("cannot send to closed stdin");
|
||||
const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed);
|
||||
var bytes = bytes_;
|
||||
while (bytes.len > 0)
|
||||
bytes = loop: {
|
||||
if (bytes.len > max_chunk_size) {
|
||||
try pid.send(.{ "stdin", bytes[0..max_chunk_size] });
|
||||
break :loop bytes[max_chunk_size..];
|
||||
} else {
|
||||
try pid.send(.{ "stdin", bytes });
|
||||
break :loop &[_]u8{};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Self) tp.result {
|
||||
defer self.deinit();
|
||||
if (self.stdin_behavior == .Pipe)
|
||||
if (self.pid) |pid| try pid.send(.{"stdin_close"});
|
||||
}
|
||||
|
||||
pub fn writer(self: *Self) Writer {
|
||||
return .{ .context = self };
|
||||
}
|
||||
|
||||
pub fn bufferedWriter(self: *Self) BufferedWriter {
|
||||
return .{ .unbuffered_writer = self.writer() };
|
||||
}
|
||||
|
||||
const Proc = struct {
|
||||
a: std.mem.Allocator,
|
||||
receiver: Receiver,
|
||||
args: std.heap.ArenaAllocator,
|
||||
parent: tp.pid,
|
||||
child: std.ChildProcess,
|
||||
tag: [:0]const u8,
|
||||
stdin_buffer: std.ArrayList(u8),
|
||||
fd_stdin: ?tp.file_descriptor = null,
|
||||
fd_stdout: ?tp.file_descriptor = null,
|
||||
fd_stderr: ?tp.file_descriptor = null,
|
||||
write_pending: bool = false,
|
||||
stdin_close_pending: bool = false,
|
||||
|
||||
const Receiver = tp.Receiver(*Proc);
|
||||
|
||||
fn create(a: std.mem.Allocator, argv: tp.message, tag: [:0]const u8, stdin_behavior: std.ChildProcess.StdIo) !tp.pid {
|
||||
const self: *Proc = try a.create(Proc);
|
||||
|
||||
var args = std.heap.ArenaAllocator.init(a);
|
||||
const args_a = args.allocator();
|
||||
var iter = argv.buf;
|
||||
var len = cbor.decodeArrayHeader(&iter) catch return error.InvalidArgument;
|
||||
var argv_ = try args_a.alloc([]const u8, len);
|
||||
var arg: []const u8 = undefined;
|
||||
var i: usize = 0;
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (!(cbor.matchString(&iter, &arg) catch return error.InvalidArgument))
|
||||
return error.InvalidArgument;
|
||||
argv_[i] = try args_a.dupe(u8, arg);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
var child = std.ChildProcess.init(argv_, a);
|
||||
child.stdin_behavior = stdin_behavior;
|
||||
child.stdout_behavior = .Pipe;
|
||||
child.stderr_behavior = .Pipe;
|
||||
|
||||
self.* = .{
|
||||
.a = a,
|
||||
.receiver = Receiver.init(receive, self),
|
||||
.args = args,
|
||||
.parent = tp.self_pid().clone(),
|
||||
.child = child,
|
||||
.tag = try a.dupeZ(u8, tag),
|
||||
.stdin_buffer = std.ArrayList(u8).init(a),
|
||||
};
|
||||
return tp.spawn_link(a, self, Proc.start, tag);
|
||||
}
|
||||
|
||||
fn deinit(self: *Proc) void {
|
||||
self.args.deinit();
|
||||
if (self.fd_stdin) |fd| fd.deinit();
|
||||
if (self.fd_stdout) |fd| fd.deinit();
|
||||
if (self.fd_stderr) |fd| fd.deinit();
|
||||
self.stdin_buffer.deinit();
|
||||
self.parent.deinit();
|
||||
self.a.free(self.tag);
|
||||
}
|
||||
|
||||
fn start(self: *Proc) tp.result {
|
||||
errdefer self.deinit();
|
||||
|
||||
self.child.spawn() catch |e| {
|
||||
try self.parent.send(.{ self.tag, "term", e, 1 });
|
||||
return tp.exit_normal();
|
||||
};
|
||||
_ = self.args.reset(.free_all);
|
||||
|
||||
if (self.child.stdin_behavior == .Pipe)
|
||||
self.fd_stdin = tp.file_descriptor.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e);
|
||||
self.fd_stdout = tp.file_descriptor.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e);
|
||||
self.fd_stderr = tp.file_descriptor.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e);
|
||||
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e);
|
||||
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e);
|
||||
|
||||
tp.receive(&self.receiver);
|
||||
}
|
||||
|
||||
fn receive(self: *Proc, _: tp.pid_ref, m: tp.message) tp.result {
|
||||
errdefer self.deinit();
|
||||
var bytes: []u8 = "";
|
||||
var err: i64 = 0;
|
||||
var err_msg: []u8 = "";
|
||||
if (try m.match(.{ "fd", "stdout", "read_ready" })) {
|
||||
try self.dispatch_stdout();
|
||||
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e);
|
||||
} else if (try m.match(.{ "fd", "stderr", "read_ready" })) {
|
||||
try self.dispatch_stderr();
|
||||
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e);
|
||||
} else if (try m.match(.{ "fd", "stdin", "write_ready" })) {
|
||||
if (self.stdin_buffer.items.len > 0) {
|
||||
if (self.child.stdin) |stdin| {
|
||||
const written = stdin.write(self.stdin_buffer.items) catch |e| return tp.exit_error(e);
|
||||
self.write_pending = false;
|
||||
defer {
|
||||
if (self.stdin_close_pending and !self.write_pending)
|
||||
self.stdin_close();
|
||||
}
|
||||
if (written == self.stdin_buffer.items.len) {
|
||||
self.stdin_buffer.clearRetainingCapacity();
|
||||
} else {
|
||||
std.mem.copyForwards(u8, self.stdin_buffer.items, self.stdin_buffer.items[written..]);
|
||||
self.stdin_buffer.items.len = self.stdin_buffer.items.len - written;
|
||||
if (self.fd_stdin) |fd_stdin| {
|
||||
fd_stdin.wait_write() catch |e| return tp.exit_error(e);
|
||||
self.write_pending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (try m.match(.{ "stdin", tp.extract(&bytes) })) {
|
||||
if (self.fd_stdin) |fd_stdin| {
|
||||
self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e);
|
||||
fd_stdin.wait_write() catch |e| return tp.exit_error(e);
|
||||
self.write_pending = true;
|
||||
}
|
||||
} else if (try m.match(.{"stdin_close"})) {
|
||||
if (self.write_pending) {
|
||||
self.stdin_close_pending = true;
|
||||
} else {
|
||||
self.stdin_close();
|
||||
}
|
||||
} else if (try m.match(.{"stdout_close"})) {
|
||||
if (self.child.stdout) |*fd| {
|
||||
fd.close();
|
||||
self.child.stdout = null;
|
||||
}
|
||||
} else if (try m.match(.{"stderr_close"})) {
|
||||
if (self.child.stderr) |*fd| {
|
||||
fd.close();
|
||||
self.child.stderr = null;
|
||||
}
|
||||
} else if (try m.match(.{ "fd", tp.any, "read_error", tp.extract(&err), tp.extract(&err_msg) })) {
|
||||
return tp.exit(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn stdin_close(self: *Proc) void {
|
||||
if (self.child.stdin) |*fd| {
|
||||
fd.close();
|
||||
self.child.stdin = null;
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_stdout(self: *Proc) tp.result {
|
||||
var buffer: [max_chunk_size]u8 = undefined;
|
||||
const bytes = self.child.stdout.?.read(&buffer) catch |e| return tp.exit_error(e);
|
||||
if (bytes == 0)
|
||||
return self.handle_terminate();
|
||||
try self.parent.send(.{ self.tag, "stdout", buffer[0..bytes] });
|
||||
}
|
||||
|
||||
fn dispatch_stderr(self: *Proc) tp.result {
|
||||
var buffer: [max_chunk_size]u8 = undefined;
|
||||
const bytes = self.child.stderr.?.read(&buffer) catch |e| return tp.exit_error(e);
|
||||
if (bytes == 0)
|
||||
return;
|
||||
try self.parent.send(.{ self.tag, "stderr", buffer[0..bytes] });
|
||||
}
|
||||
|
||||
fn handle_terminate(self: *Proc) tp.result {
|
||||
const term = self.child.wait() catch |e| return tp.exit_error(e);
|
||||
(switch (term) {
|
||||
.Exited => |val| self.parent.send(.{ self.tag, "term", "exited", val }),
|
||||
.Signal => |val| self.parent.send(.{ self.tag, "term", "signal", val }),
|
||||
.Stopped => |val| self.parent.send(.{ self.tag, "term", "stop", val }),
|
||||
.Unknown => |val| self.parent.send(.{ self.tag, "term", "unknown", val }),
|
||||
}) catch {};
|
||||
return tp.exit_normal();
|
||||
}
|
||||
};
|
716
src/thespian.zig
Normal file
716
src/thespian.zig
Normal file
|
@ -0,0 +1,716 @@
|
|||
const std = @import("std");
|
||||
const c = @cImport({
|
||||
@cInclude("thespian/c/file_descriptor.h");
|
||||
@cInclude("thespian/c/instance.h");
|
||||
@cInclude("thespian/c/metronome.h");
|
||||
@cInclude("thespian/c/timeout.h");
|
||||
@cInclude("thespian/c/signal.h");
|
||||
@cInclude("thespian/backtrace.h");
|
||||
});
|
||||
const cbor = @import("cbor");
|
||||
pub const subprocess = @import("subprocess.zig");
|
||||
|
||||
pub const install_debugger = c.install_debugger;
|
||||
pub const max_message_size = 8 * 4096;
|
||||
threadlocal var message_buffer: [max_message_size]u8 = undefined;
|
||||
threadlocal var error_message_buffer: [256]u8 = undefined;
|
||||
threadlocal var error_buffer_tl: c.thespian_error = .{
|
||||
.base = null,
|
||||
.len = 0,
|
||||
};
|
||||
|
||||
pub const result = error{Exit}!void;
|
||||
pub const extract = cbor.extract;
|
||||
pub const extract_cbor = cbor.extract_cbor;
|
||||
|
||||
pub const number = cbor.number;
|
||||
pub const bytes = cbor.bytes;
|
||||
pub const string = cbor.string;
|
||||
pub const array = cbor.array;
|
||||
pub const map = cbor.map;
|
||||
pub const tag = cbor.tag;
|
||||
pub const boolean = cbor.boolean;
|
||||
pub const null_ = cbor.null_;
|
||||
pub const any = cbor.any;
|
||||
pub const more = cbor.more;
|
||||
|
||||
pub const Ownership = enum {
|
||||
Wrapped,
|
||||
Owned,
|
||||
};
|
||||
|
||||
fn Pid(comptime own: Ownership) type {
|
||||
return struct {
|
||||
h: c.thespian_handle,
|
||||
|
||||
const ownership = own;
|
||||
const Self = @This();
|
||||
|
||||
pub fn clone(self: Self) Pid(.Owned) {
|
||||
return .{ .h = c.thespian_handle_clone(self.h) };
|
||||
}
|
||||
|
||||
pub fn ref(self: Self) Pid(.Wrapped) {
|
||||
return .{ .h = self.h };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
comptime if (ownership == Ownership.Wrapped)
|
||||
@compileError("cannot call deinit on Pid(.Wrapped)");
|
||||
c.thespian_handle_destroy(self.h);
|
||||
}
|
||||
|
||||
pub fn send_raw(self: Self, m: message) result {
|
||||
const ret = c.thespian_handle_send_raw(self.h, m.to(c.cbor_buffer));
|
||||
if (ret) |err|
|
||||
return set_error(err.*);
|
||||
}
|
||||
|
||||
pub fn send(self: Self, m: anytype) result {
|
||||
return self.send_raw(message.fmt(m));
|
||||
}
|
||||
|
||||
pub fn call(self: Self, request: anytype) !message {
|
||||
return CallContext.call(self.ref(), message.fmt(request));
|
||||
}
|
||||
|
||||
pub fn link(self: Self) result {
|
||||
return c.thespian_link(self.h);
|
||||
}
|
||||
|
||||
pub fn expired(self: Self) bool {
|
||||
return c.thespian_handle_is_expired(self.h);
|
||||
}
|
||||
|
||||
pub fn wait_expired(self: Self, timeout_ns: isize) !void {
|
||||
var max_sleep: isize = timeout_ns;
|
||||
while (!self.expired()) {
|
||||
if (max_sleep <= 0) return error.Timeout;
|
||||
std.time.sleep(100);
|
||||
max_sleep -= 100;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub const pid = Pid(.Owned);
|
||||
pub const pid_ref = Pid(.Wrapped);
|
||||
fn wrap_handle(h: c.thespian_handle) pid_ref {
|
||||
return .{ .h = h };
|
||||
}
|
||||
fn wrap_pid(h: c.thespian_handle) pid {
|
||||
return .{ .h = h };
|
||||
}
|
||||
|
||||
pub const message = struct {
|
||||
buf: buffer_type = "",
|
||||
|
||||
const Self = @This();
|
||||
pub const buffer_type: type = []const u8;
|
||||
pub const c_buffer_type = c.cbor_buffer;
|
||||
|
||||
pub fn fmt(value: anytype) Self {
|
||||
return fmtbuf(&message_buffer, value) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn fmtbuf(buf: []u8, value: anytype) !Self {
|
||||
const f = comptime switch (@typeInfo(@TypeOf(value))) {
|
||||
.Struct => |info| if (info.is_tuple)
|
||||
fmtbufInternal
|
||||
else
|
||||
@compileError("thespian.message template should be a tuple: " ++ @typeName(@TypeOf(value))),
|
||||
else => fmtbufInternalScalar,
|
||||
};
|
||||
return f(buf, value);
|
||||
}
|
||||
|
||||
fn fmtbufInternalScalar(buf: []u8, value: anytype) !Self {
|
||||
return fmtbufInternal(buf, .{value});
|
||||
}
|
||||
|
||||
fn fmtbufInternal(buf: []u8, value: anytype) !Self {
|
||||
var stream = std.io.fixedBufferStream(buf);
|
||||
try cbor.writeValue(stream.writer(), value);
|
||||
return .{ .buf = stream.getWritten() };
|
||||
}
|
||||
|
||||
pub fn len(self: Self) usize {
|
||||
return self.buf.len;
|
||||
}
|
||||
|
||||
pub fn from(span: anytype) Self {
|
||||
return .{ .buf = span.base[0..span.len] };
|
||||
}
|
||||
|
||||
pub fn to(self: *const Self, comptime T: type) T {
|
||||
return T{ .base = self.buf.ptr, .len = self.buf.len };
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Self, a: std.mem.Allocator) !Self {
|
||||
return .{ .buf = try a.dupe(u8, self.buf) };
|
||||
}
|
||||
|
||||
pub fn to_json(self: *const Self, buffer: []u8) cbor.CborJsonError![]const u8 {
|
||||
return cbor.toJson(self.buf, buffer);
|
||||
}
|
||||
|
||||
pub const json_string_view = c.c_string_view;
|
||||
const json_callback = *const fn (json_string_view) callconv(.C) void;
|
||||
|
||||
pub fn to_json_cb(self: *const Self, callback: json_callback) void {
|
||||
c.cbor_to_json(self.to(c_buffer_type), callback);
|
||||
}
|
||||
pub fn match(self: Self, m: anytype) error{Exit}!bool {
|
||||
return if (cbor.match(self.buf, m)) |ret| ret else |e| exit_error(e);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn exit_message(e: anytype) message {
|
||||
return message.fmtbuf(&error_message_buffer, .{ "exit", e }) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn exit_normal() result {
|
||||
return set_error_msg(exit_message("normal"));
|
||||
}
|
||||
|
||||
pub fn exit(e: []const u8) error{Exit} {
|
||||
return set_error_msg(exit_message(e));
|
||||
}
|
||||
|
||||
pub fn exit_fmt(comptime fmt: anytype, args: anytype) result {
|
||||
var buf: [1024]u8 = undefined;
|
||||
const msg = std.fmt.bufPrint(&buf, fmt, args) catch "FMTERROR";
|
||||
return set_error_msg(exit_message(msg));
|
||||
}
|
||||
|
||||
pub fn exit_error(e: anyerror) error{Exit} {
|
||||
return switch (e) {
|
||||
error.Exit => error.Exit,
|
||||
else => set_error_msg(exit_message(e)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unexpected(b: message) error{Exit} {
|
||||
const txt = "UNEXPECTED_MESSAGE: ";
|
||||
var buf: [max_message_size]u8 = undefined;
|
||||
@memcpy(buf[0..txt.len], txt);
|
||||
const json = b.to_json(buf[txt.len..]) catch |e| return exit_error(e);
|
||||
return set_error_msg(message.fmt(.{ "exit", buf[0..(txt.len + json.len)] }));
|
||||
}
|
||||
|
||||
pub fn error_message() []const u8 {
|
||||
if (error_buffer_tl.base) |base|
|
||||
return base[0..error_buffer_tl.len];
|
||||
set_error_msg(exit_message("NOERROR")) catch {};
|
||||
return error_message();
|
||||
}
|
||||
|
||||
pub fn error_text() []const u8 {
|
||||
const msg_: message = .{ .buf = error_message() };
|
||||
var msg__: []const u8 = undefined;
|
||||
return if (msg_.match(.{ "exit", extract(&msg__) }) catch false)
|
||||
msg__
|
||||
else
|
||||
&[_]u8{};
|
||||
}
|
||||
|
||||
pub fn self_pid() pid_ref {
|
||||
return wrap_handle(c.thespian_self());
|
||||
}
|
||||
|
||||
pub const trace_channel = c.thespian_trace_channel;
|
||||
pub const channel = struct {
|
||||
pub const send = c.thespian_trace_channel_send;
|
||||
pub const receive = c.thespian_trace_channel_receive;
|
||||
pub const lifetime = c.thespian_trace_channel_lifetime;
|
||||
pub const link = c.thespian_trace_channel_link;
|
||||
pub const execute = c.thespian_trace_channel_execute;
|
||||
pub const udp = c.thespian_trace_channel_udp;
|
||||
pub const tcp = c.thespian_trace_channel_tcp;
|
||||
pub const timer = c.thespian_trace_channel_timer;
|
||||
pub const metronome = c.thespian_trace_channel_metronome;
|
||||
pub const endpoint = c.thespian_trace_channel_endpoint;
|
||||
pub const signal = c.thespian_trace_channel_signal;
|
||||
pub const event: c.thespian_trace_channel = 2048;
|
||||
pub const widget: c.thespian_trace_channel = 4096;
|
||||
pub const input: c.thespian_trace_channel = 8192;
|
||||
pub const all = c.thespian_trace_channel_all;
|
||||
};
|
||||
|
||||
pub const env = struct {
|
||||
env: *c.thespian_env_t,
|
||||
owned: bool = false,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn get() Self {
|
||||
return .{ .env = c.thespian_env_get() orelse unreachable, .owned = false };
|
||||
}
|
||||
|
||||
pub fn init() Self {
|
||||
return .{ .env = c.thespian_env_new() orelse unreachable, .owned = true };
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Self) Self {
|
||||
return .{ .env = c.thespian_env_clone(self.env) orelse unreachable, .owned = true };
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
if (self.owned) c.thespian_env_destroy(self.env);
|
||||
}
|
||||
|
||||
pub fn enable_all_channels(self: *const Self) void {
|
||||
c.thespian_env_enable_all_channels(self.env);
|
||||
}
|
||||
|
||||
pub fn disable_all_channels(self: *const Self) void {
|
||||
c.thespian_env_disable_all_channels(self.env);
|
||||
}
|
||||
|
||||
pub fn enable(self: *const Self, chan: c.thespian_trace_channel) void {
|
||||
c.thespian_env_enable(self.env, chan);
|
||||
}
|
||||
|
||||
pub fn disable(self: *const Self, chan: c.thespian_trace_channel) void {
|
||||
c.thespian_env_disable(self.env, chan);
|
||||
}
|
||||
|
||||
pub fn enabled(self: *const Self, chan: c.thespian_trace_channel) bool {
|
||||
return c.thespian_env_enabled(self.env, chan);
|
||||
}
|
||||
|
||||
pub const trace_handler = *const fn (c.cbor_buffer) callconv(.C) void;
|
||||
|
||||
pub fn on_trace(self: *const Self, h: trace_handler) void {
|
||||
c.thespian_env_on_trace(self.env, h);
|
||||
}
|
||||
|
||||
pub fn trace(self: *const Self, m: c.cbor_buffer) void {
|
||||
c.thespian_env_trace(self.env, m);
|
||||
}
|
||||
|
||||
pub fn is(self: *const Self, key: []const u8) bool {
|
||||
return c.thespian_env_is(self.env, .{ .base = key.ptr, .len = key.len });
|
||||
}
|
||||
pub fn set(self: *const Self, key: []const u8, value: bool) void {
|
||||
c.thespian_env_set(self.env, .{ .base = key.ptr, .len = key.len }, value);
|
||||
}
|
||||
|
||||
pub fn num(self: *const Self, key: []const u8) i64 {
|
||||
return c.thespian_env_num(self.env, .{ .base = key.ptr, .len = key.len });
|
||||
}
|
||||
pub fn num_set(self: *const Self, key: []const u8, value: i64) void {
|
||||
c.thespian_env_num_set(self.env, .{ .base = key.ptr, .len = key.len }, value);
|
||||
}
|
||||
|
||||
pub fn str(self: *const Self, key: []const u8) []const u8 {
|
||||
const ret = c.thespian_env_str(self.env, .{ .base = key.ptr, .len = key.len });
|
||||
return ret.base[0..ret.len];
|
||||
}
|
||||
pub fn str_set(self: *const Self, key: []const u8, value: []const u8) void {
|
||||
c.thespian_env_str(self.env, .{ .base = key.ptr, .len = key.len }, .{ .base = value.ptr, .len = value.len });
|
||||
}
|
||||
|
||||
pub fn proc(self: *const Self, key: []const u8) pid_ref {
|
||||
return wrap_handle(c.thespian_env_proc(self.env, .{ .base = key.ptr, .len = key.len }));
|
||||
}
|
||||
pub fn proc_set(self: *const Self, key: []const u8, value: pid_ref) void {
|
||||
c.thespian_env_proc_set(self.env, .{ .base = key.ptr, .len = key.len }, value.h);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn trace(chan: trace_channel, value: anytype) void {
|
||||
if (env.get().enabled(chan)) {
|
||||
if (@TypeOf(value) == message) {
|
||||
env.get().trace(value.to(message.c_buffer_type));
|
||||
} else {
|
||||
var trace_buffer: [512]u8 = undefined;
|
||||
const m = message.fmtbuf(&trace_buffer, value);
|
||||
env.get().trace(m.to(message.c_buffer_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const context = struct {
|
||||
a: std.mem.Allocator,
|
||||
context: c.thespian_context,
|
||||
context_destroy: *const fn (?*anyopaque) callconv(.C) void,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(a: std.mem.Allocator) !Self {
|
||||
var ctx_destroy: c.thespian_context_destroy = null;
|
||||
const ctx = c.thespian_context_create(&ctx_destroy) orelse return error.ThespianContextCreateFailed;
|
||||
return .{
|
||||
.a = a,
|
||||
.context = ctx,
|
||||
.context_destroy = ctx_destroy orelse return error.ThespianContextCreateFailed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn run(self: *Self) void {
|
||||
c.thespian_context_run(self.context);
|
||||
}
|
||||
|
||||
pub fn spawn_link(
|
||||
self: *const Self,
|
||||
data: anytype,
|
||||
f: Behaviour(@TypeOf(data)).FunT,
|
||||
name: [:0]const u8,
|
||||
eh: anytype,
|
||||
env_: ?*const env,
|
||||
) !pid {
|
||||
const Tclosure = Behaviour(@TypeOf(data));
|
||||
var handle_: c.thespian_handle = null;
|
||||
try neg_to_error(c.thespian_context_spawn_link(
|
||||
self.context,
|
||||
Tclosure.run,
|
||||
try Tclosure.create(self.a, f, data),
|
||||
exitHandlerRun(@TypeOf(eh)),
|
||||
eh,
|
||||
name,
|
||||
if (env_) |env__| env__.env else null,
|
||||
&handle_,
|
||||
), error.ThespianSpawnFailed);
|
||||
return .{ .h = handle_ };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.context_destroy(self.context);
|
||||
}
|
||||
};
|
||||
|
||||
fn exitHandlerRun(comptime T: type) c.thespian_exit_handler {
|
||||
if (T == @TypeOf(null)) return null;
|
||||
return switch (@typeInfo(T)) {
|
||||
.Optional => |info| switch (@typeInfo(info.child)) {
|
||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||
.One => ptr_info.child.run,
|
||||
else => @compileError("expected single item pointer, found: '" ++ @typeName(T) ++ "'"),
|
||||
},
|
||||
},
|
||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||
.One => ptr_info.child.run,
|
||||
else => @compileError("expected single item pointer, found: '" ++ @typeName(T) ++ "'"),
|
||||
},
|
||||
else => @compileError("expected optional pointer or pointer type, found: '" ++ @typeName(T) ++ "'"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn receive(r: anytype) void {
|
||||
const T = switch (@typeInfo(@TypeOf(r))) {
|
||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||
.One => ptr_info.child,
|
||||
else => @compileError("invalid receiver type"),
|
||||
},
|
||||
else => @compileError("invalid receiver type"),
|
||||
};
|
||||
c.thespian_receive(T.run, r);
|
||||
}
|
||||
|
||||
pub fn Receiver(comptime T: type) type {
|
||||
return struct {
|
||||
f: FunT,
|
||||
data: T,
|
||||
const FunT: type = *const fn (T, from: pid_ref, m: message) result;
|
||||
const Self = @This();
|
||||
pub fn init(f: FunT, data: T) Self {
|
||||
return .{ .f = f, .data = data };
|
||||
}
|
||||
pub fn run(ostate: c.thespian_behaviour_state, from: c.thespian_handle, m: c.cbor_buffer) callconv(.C) c.thespian_result {
|
||||
const state: *Self = @ptrCast(@alignCast(ostate orelse unreachable));
|
||||
reset_error();
|
||||
return to_result(state.f(state.data, wrap_handle(from), message.from(m)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_trap() bool {
|
||||
return c.thespian_get_trap();
|
||||
}
|
||||
|
||||
pub fn set_trap(on: bool) bool {
|
||||
return c.thespian_set_trap(on);
|
||||
}
|
||||
|
||||
pub fn spawn_link(
|
||||
a: std.mem.Allocator,
|
||||
data: anytype,
|
||||
f: Behaviour(@TypeOf(data)).FunT,
|
||||
name: [:0]const u8,
|
||||
) !pid {
|
||||
return spawn_link_env(a, data, f, name, env.get());
|
||||
}
|
||||
|
||||
pub fn spawn_link_env(
|
||||
a: std.mem.Allocator,
|
||||
data: anytype,
|
||||
f: Behaviour(@TypeOf(data)).FunT,
|
||||
name: [:0]const u8,
|
||||
env_: ?env,
|
||||
) !pid {
|
||||
const Tclosure = Behaviour(@TypeOf(data));
|
||||
var handle_: c.thespian_handle = null;
|
||||
try neg_to_error(c.thespian_spawn_link(
|
||||
Tclosure.run,
|
||||
try Tclosure.create(a, f, data),
|
||||
name,
|
||||
if (env_) |env__| env__.env else null,
|
||||
&handle_,
|
||||
), error.ThespianSpawnFailed);
|
||||
return wrap_pid(handle_);
|
||||
}
|
||||
|
||||
pub fn neg_to_error(errcode: c_int, errortype: anyerror) !void {
|
||||
if (errcode < 0) return errortype;
|
||||
}
|
||||
|
||||
pub fn nonzero_to_error(errcode: c_int, errortype: anyerror) !void {
|
||||
if (errcode != 0) return errortype;
|
||||
}
|
||||
|
||||
pub fn wrap(comptime f: anytype, errortype: anyerror) fn (std.meta.ArgsTuple(@TypeOf(f))) @TypeOf(errortype)!void {
|
||||
const Local = struct {
|
||||
pub fn wrapped(args: std.meta.ArgsTuple(@TypeOf(f))) @TypeOf(errortype)!void {
|
||||
return nonzero_to_error(@call(.auto, f, args), errortype);
|
||||
}
|
||||
};
|
||||
return Local.wrapped;
|
||||
}
|
||||
|
||||
fn Behaviour(comptime T: type) type {
|
||||
return struct {
|
||||
a: std.mem.Allocator,
|
||||
f: FunT,
|
||||
data: T,
|
||||
const FunT: type = *const fn (T) result;
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: std.mem.Allocator, f: FunT, data: T) !*Self {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = .{ .a = a, .f = f, .data = data };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Self) void {
|
||||
self.a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn run(state: c.thespian_behaviour_state) callconv(.C) c.thespian_result {
|
||||
const self: *Self = @ptrCast(@alignCast(state orelse unreachable));
|
||||
defer self.destroy();
|
||||
reset_error();
|
||||
return to_result(self.f(self.data));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn to_result(ret: result) c.thespian_result {
|
||||
if (ret) |_| {
|
||||
return null;
|
||||
} else |_| {
|
||||
const msg = error_message();
|
||||
if (!(cbor.match(msg, .{ "exit", "normal" }) catch false)) {
|
||||
if (env.get().is("dump-stack-trace")) {
|
||||
const trace_ = @errorReturnTrace();
|
||||
if (trace_) |t| std.debug.dumpStackTrace(t.*);
|
||||
}
|
||||
}
|
||||
return &error_buffer_tl;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_error_msg(m: message) error{Exit} {
|
||||
return set_error(m.to(c.thespian_error));
|
||||
}
|
||||
|
||||
fn set_error(e: c.thespian_error) error{Exit} {
|
||||
error_buffer_tl = e;
|
||||
return error.Exit;
|
||||
}
|
||||
|
||||
pub fn reset_error() void {
|
||||
error_buffer_tl = .{ .base = null, .len = 0 };
|
||||
}
|
||||
|
||||
pub fn make_exit_handler(data: anytype, f: ExitHandler(@TypeOf(data)).FunT) ExitHandler(@TypeOf(data)) {
|
||||
return ExitHandler(@TypeOf(data)).init(f, data);
|
||||
}
|
||||
|
||||
fn ExitHandler(comptime T: type) type {
|
||||
return struct {
|
||||
a: ?std.mem.Allocator = null,
|
||||
f: FunT,
|
||||
data: T,
|
||||
const FunT: type = *const fn (T, []const u8) void;
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(f: FunT, data: T) Self {
|
||||
return .{ .f = f, .data = data };
|
||||
}
|
||||
|
||||
pub fn create(a: std.mem.Allocator, f: FunT, data: T) !*Self {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = .{ .a = a, .f = f, .data = data };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Self) void {
|
||||
if (self.a) |a_| a_.destroy(self);
|
||||
}
|
||||
|
||||
pub fn run(state: c.thespian_exit_handler_state, msg: [*c]const u8, len: usize) callconv(.C) void {
|
||||
const self: *Self = @ptrCast(@alignCast(state orelse unreachable));
|
||||
defer self.destroy();
|
||||
self.f(self.data, msg[0..len]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const signal = struct {
|
||||
handle: *c.struct_thespian_signal_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(signum: c_int, m: message) !Self {
|
||||
return .{ .handle = c.thespian_signal_create(signum, m.to(c.cbor_buffer)) orelse return error.ThespianSignalInitFailed };
|
||||
}
|
||||
|
||||
pub fn cancel(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_signal_cancel(self.handle), error.ThespianSignalCancelFailed);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_signal_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
pub const metronome = struct {
|
||||
handle: *c.struct_thespian_metronome_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(tick_time_us: u64) !Self {
|
||||
return .{ .handle = c.thespian_metronome_create_us(tick_time_us) orelse return error.ThespianMetronomeInitFailed };
|
||||
}
|
||||
|
||||
pub fn init_ms(tick_time_us: u64) !Self {
|
||||
return .{ .handle = c.thespian_metronome_create_ms(tick_time_us) orelse return error.ThespianMetronomeInitFailed };
|
||||
}
|
||||
|
||||
pub fn start(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_metronome_start(self.handle), error.ThespianMetronomeStartFailed);
|
||||
}
|
||||
|
||||
pub fn stop(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_metronome_stop(self.handle), error.ThespianMetronomeStopFailed);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_metronome_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
pub const timeout = struct {
|
||||
handle: *c.struct_thespian_timeout_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(tick_time_us: u64, m: message) !Self {
|
||||
return .{ .handle = c.thespian_timeout_create_us(tick_time_us, m.to(c.cbor_buffer)) orelse return error.ThespianTimeoutInitFailed };
|
||||
}
|
||||
|
||||
pub fn init_ms(tick_time_us: u64, m: message) !Self {
|
||||
return .{ .handle = c.thespian_timeout_create_ms(tick_time_us, m.to(c.cbor_buffer)) orelse return error.ThespianTimeoutInitFailed };
|
||||
}
|
||||
|
||||
pub fn start(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_timeout_start(self.handle), error.ThespianTimeoutStartFailed);
|
||||
}
|
||||
|
||||
pub fn stop(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_timeout_stop(self.handle), error.ThespianTimeoutStopFailed);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_timeout_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
pub const file_descriptor = struct {
|
||||
handle: *c.struct_thespian_file_descriptor_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(tag_: []const u8, fd: i32) !Self {
|
||||
return .{ .handle = c.thespian_file_descriptor_create(tag_.ptr, fd) orelse return error.ThespianFileDescriptorInitFailed };
|
||||
}
|
||||
|
||||
pub fn wait_write(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_file_descriptor_wait_write(self.handle), error.ThespianFileDescriptorWaitWriteFailed);
|
||||
}
|
||||
|
||||
pub fn wait_read(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_file_descriptor_wait_read(self.handle), error.ThespianFileDescriptorWaitReadFailed);
|
||||
}
|
||||
|
||||
pub fn cancel(self: *const Self) !void {
|
||||
return neg_to_error(c.thespian_file_descriptor_cancel(self.handle), error.ThespianFileDescriptorCancelFailed);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_file_descriptor_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
const CallContext = struct {
|
||||
receiver: ReceiverT,
|
||||
to: pid_ref,
|
||||
request: message,
|
||||
response: ?message,
|
||||
a: std.mem.Allocator,
|
||||
|
||||
const Self = @This();
|
||||
const ReceiverT = Receiver(*Self);
|
||||
|
||||
pub fn call(to: pid_ref, request: message) !message {
|
||||
var heap: [32 + 1024]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&heap);
|
||||
const a = fba.allocator();
|
||||
|
||||
var self: Self = undefined;
|
||||
const rec = ReceiverT.init(receive_, &self);
|
||||
|
||||
self = .{
|
||||
.receiver = rec,
|
||||
.to = to,
|
||||
.request = request,
|
||||
.response = null,
|
||||
.a = a,
|
||||
};
|
||||
|
||||
const proc = try spawn_link(a, &self, start, @typeName(Self));
|
||||
while (!proc.expired()) {
|
||||
std.time.sleep(50);
|
||||
}
|
||||
if (self.response) |resp| {
|
||||
const m = message_buffer[0..resp.buf.len];
|
||||
@memcpy(m, resp.buf);
|
||||
return .{ .buf = m };
|
||||
} else {
|
||||
return .{};
|
||||
}
|
||||
}
|
||||
|
||||
fn start(self: *Self) result {
|
||||
_ = set_trap(true);
|
||||
try self.to.link();
|
||||
try self.to.send_raw(self.request);
|
||||
receive(&self.receiver);
|
||||
}
|
||||
|
||||
fn receive_(self: *Self, from: pid_ref, m: message) result {
|
||||
_ = from;
|
||||
self.response = m.clone(self.a) catch |e| return exit_error(e);
|
||||
try exit_normal();
|
||||
}
|
||||
};
|
166
src/trace.cpp
Normal file
166
src/trace.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
using cbor::any;
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using cbor::more;
|
||||
using std::atomic;
|
||||
using std::fstream;
|
||||
using std::ios_base;
|
||||
using std::lock_guard;
|
||||
using std::make_shared;
|
||||
using std::ostream;
|
||||
using std::recursive_mutex;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
|
||||
namespace thespian {
|
||||
constexpr auto trace_file_flags =
|
||||
ios_base::binary | ios_base::out | ios_base::ate;
|
||||
const auto trace_buf_size = 64U * 1024U;
|
||||
|
||||
template <typename T, typename Q> struct trace_file {
|
||||
trace_file(const trace_file &) = delete;
|
||||
trace_file(const trace_file &&) = delete;
|
||||
auto operator=(const trace_file &) -> trace_file & = delete;
|
||||
auto operator=(trace_file &&) -> trace_file & = delete;
|
||||
|
||||
struct node {
|
||||
Q data{};
|
||||
node *next{};
|
||||
};
|
||||
atomic<node *> head{nullptr};
|
||||
recursive_mutex m;
|
||||
fstream s;
|
||||
char buf[trace_buf_size]{}; // NOLINT
|
||||
|
||||
explicit trace_file(const string &file_name)
|
||||
: s(file_name, trace_file_flags) {
|
||||
s.rdbuf()->pubsetbuf(buf, trace_buf_size);
|
||||
}
|
||||
~trace_file() {
|
||||
while (do_work())
|
||||
;
|
||||
}
|
||||
|
||||
auto reverse(node *p) -> node * {
|
||||
node *prev{nullptr};
|
||||
node *next{nullptr};
|
||||
while (p) {
|
||||
next = p->next;
|
||||
p->next = prev;
|
||||
prev = p;
|
||||
p = next;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
auto push(Q data) -> void {
|
||||
auto p = new node{move(data), head};
|
||||
while (!head.compare_exchange_weak(p->next, p))
|
||||
;
|
||||
while (do_work())
|
||||
;
|
||||
}
|
||||
auto do_work() -> bool {
|
||||
if (!m.try_lock())
|
||||
return false;
|
||||
lock_guard<recursive_mutex> lock(m);
|
||||
m.unlock();
|
||||
auto p = head.load();
|
||||
while (p && !head.compare_exchange_weak(p, nullptr)) {
|
||||
;
|
||||
}
|
||||
if (!p)
|
||||
return false;
|
||||
p = reverse(p);
|
||||
while (p) {
|
||||
static_cast<T *>(this)->on_trace(move(p->data));
|
||||
auto p_ = p;
|
||||
p = p->next;
|
||||
delete p_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
auto to_mermaid(ostream &s, const buffer &m) -> void {
|
||||
string_view from;
|
||||
string_view typ;
|
||||
if (not m(A(any, any, extract(from)), extract(typ), more))
|
||||
return;
|
||||
if (from.empty())
|
||||
from = "UNKNOWN";
|
||||
if (typ == "spawn") {
|
||||
s << " Note right of " << from << ": SPAWN\n";
|
||||
} else if (typ == "receive") {
|
||||
// ignore
|
||||
} else if (typ == "run") {
|
||||
s << " activate " << from << '\n';
|
||||
} else if (typ == "sleep") {
|
||||
s << " deactivate " << from << '\n';
|
||||
} else if (typ == "send") {
|
||||
string_view to;
|
||||
buffer::range msg;
|
||||
if (m(any, any, A(any, any, extract(to)), extract(msg))) {
|
||||
s << " " << from << "->>" << to << ": send ";
|
||||
msg.to_json(s);
|
||||
s << '\n';
|
||||
}
|
||||
} else if (typ == "link") {
|
||||
string_view to;
|
||||
if (m(any, any, A(any, any, extract(to))))
|
||||
s << " " << from << "-->>" << to << ": create link\n";
|
||||
} else if (typ == "exit") {
|
||||
string_view msg;
|
||||
if (m(any, any, A(any, extract(msg), more)))
|
||||
s << " Note right of " << from << ": EXIT " << msg << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
static const auto cbor_cr = array("\n");
|
||||
|
||||
auto trace_to_cbor_file(const string &file_name) -> void {
|
||||
struct cbor_file : trace_file<cbor_file, buffer> {
|
||||
using trace_file::trace_file;
|
||||
auto on_trace(const buffer &msg) -> void {
|
||||
s.write(reinterpret_cast<const char *>(msg.data()), msg.size()); // NOLINT
|
||||
s.write(reinterpret_cast<const char *>(cbor_cr.data()), // NOLINT
|
||||
cbor_cr.size()); // NOLINT
|
||||
}
|
||||
};
|
||||
auto f = make_shared<cbor_file>(file_name);
|
||||
on_trace([f](auto m) { f->push(move(m)); });
|
||||
}
|
||||
|
||||
auto trace_to_json_file(const string &file_name) -> void {
|
||||
struct json_file : trace_file<json_file, buffer> {
|
||||
using trace_file::trace_file;
|
||||
auto on_trace(const buffer &msg) -> void {
|
||||
msg.to_json(s);
|
||||
s << '\n';
|
||||
}
|
||||
};
|
||||
auto f = make_shared<json_file>(file_name);
|
||||
on_trace([f](auto m) { f->push(move(m)); });
|
||||
}
|
||||
|
||||
auto trace_to_mermaid_file(const string &file_name) -> void {
|
||||
struct mermaid_file : trace_file<mermaid_file, buffer> {
|
||||
explicit mermaid_file(const string &_file_name) : trace_file(_file_name) {
|
||||
s << "sequenceDiagram\n";
|
||||
}
|
||||
auto on_trace(const buffer &msg) -> void { to_mermaid(s, msg); }
|
||||
};
|
||||
auto f = make_shared<mermaid_file>(file_name);
|
||||
on_trace([f](auto m) { f->push(move(m)); });
|
||||
}
|
||||
|
||||
} // namespace thespian
|
315
test/cbor_match.cpp
Normal file
315
test/cbor_match.cpp
Normal file
|
@ -0,0 +1,315 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
using cbor::A;
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using cbor::M;
|
||||
using cbor::map;
|
||||
using cbor::type;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::result;
|
||||
|
||||
namespace {
|
||||
|
||||
auto test() -> result {
|
||||
auto verbose = env().is("verbose");
|
||||
auto log = env().proc("log");
|
||||
result _;
|
||||
|
||||
buffer m = array("five", 5, "four", 4, array("three", 3));
|
||||
|
||||
if (verbose) {
|
||||
_ = log.send("message:", m.to_json());
|
||||
_ = log.send("buffer:", m.hexdump());
|
||||
}
|
||||
|
||||
check(m.hexdump() ==
|
||||
"21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 65 74 68 72 65 65 03");
|
||||
|
||||
check(m.to_json() == R"(["five",5,"four",4,["three",3]])");
|
||||
|
||||
check(m(type::string, type::number, type::string, type::number, type::array));
|
||||
check(!m(type::string, type::number, type::string, type::number));
|
||||
check(!m(type::number, type::string, type::number, type::array, type::any));
|
||||
check(m(type::any, type::number, type::string, type::number, type::any));
|
||||
|
||||
check(m("five", type::number, type::string, 4, type::any));
|
||||
check(m("five", 5, "four", 4, type::array));
|
||||
check(m("five", 5, type::more));
|
||||
check(m("five", 5, "four", 4, type::array, type::more));
|
||||
check(!m("five", 5, "four", 4, type::array, type::any));
|
||||
|
||||
check(array().is_null());
|
||||
check(!array(1).is_null());
|
||||
{
|
||||
buffer b;
|
||||
b.array_header(5)
|
||||
.push("five")
|
||||
.push(5)
|
||||
.push("four")
|
||||
.push(4)
|
||||
.array_header(2)
|
||||
.push("three")
|
||||
.push(3);
|
||||
check(m == b);
|
||||
}
|
||||
{
|
||||
buffer b = array(false, true, 5, "five");
|
||||
check(b == array(false, true, 5, "five"));
|
||||
check(b(false, true, 5, "five"));
|
||||
check(!b(true, false, 5, "five"));
|
||||
bool t = false;
|
||||
check(b(extract(t), true, 5, "five"));
|
||||
check(!t);
|
||||
check(b(false, extract(t), 5, "five"));
|
||||
check(t);
|
||||
}
|
||||
{
|
||||
buffer::range r;
|
||||
check(
|
||||
m(type::string, type::number, type::string, type::number, extract(r)));
|
||||
check(r(type::string, type::number));
|
||||
check(r("three", 3));
|
||||
}
|
||||
{
|
||||
int64_t i{};
|
||||
check(m(type::string, type::number, type::string, type::number,
|
||||
A(type::string, extract(i))));
|
||||
check(i == 3);
|
||||
}
|
||||
{
|
||||
buffer ref = array(array(124), array("blip"));
|
||||
check(ref(A(type::more), A(type::more)));
|
||||
}
|
||||
{
|
||||
int i{0};
|
||||
auto dump = [&](string_view val) {
|
||||
++i;
|
||||
if (verbose)
|
||||
_ = log.send("value", i, ":", val);
|
||||
};
|
||||
buffer a = array("five", "four", "three", "two", "one");
|
||||
for (const auto val : a)
|
||||
dump(val);
|
||||
check(i == 5);
|
||||
}
|
||||
{
|
||||
int i{0};
|
||||
auto dump = [&](auto val) {
|
||||
++i;
|
||||
if (verbose)
|
||||
_ = log.send("value", i, ":", val);
|
||||
};
|
||||
for (const auto val : m)
|
||||
val.visit(dump);
|
||||
check(i == 5);
|
||||
|
||||
buffer::range r;
|
||||
check(m(type::any, type::any, type::any, type::any, extract(r)));
|
||||
for (const auto val : r)
|
||||
val.visit(dump);
|
||||
check(i == 7);
|
||||
}
|
||||
{
|
||||
buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9));
|
||||
check(a(A(2, 3, 4, 5), A(6, 7, 8, 9)));
|
||||
|
||||
buffer::range a1;
|
||||
buffer::range a2;
|
||||
check(a(extract(a1), extract(a2)));
|
||||
|
||||
int i{0};
|
||||
auto dump = [&](uint32_t val) {
|
||||
++i;
|
||||
if (verbose)
|
||||
_ = log.send("value", i, ":", val);
|
||||
};
|
||||
for (const auto val : a1)
|
||||
dump(val);
|
||||
}
|
||||
{
|
||||
buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9));
|
||||
buffer::range a1;
|
||||
buffer::range a2;
|
||||
check(a(extract(a1), extract(a2)));
|
||||
|
||||
buffer b = array(a1);
|
||||
check(b(A(2, 3, 4, 5)));
|
||||
|
||||
buffer b2;
|
||||
b2.push(a2);
|
||||
check(b2(6, 7, 8, 9));
|
||||
}
|
||||
{
|
||||
buffer a = array(1, 2, map(3, array(3)));
|
||||
|
||||
if (verbose) {
|
||||
_ = log.send("map buffer:", a.hexdump());
|
||||
_ = log.send("map json:", a.to_json());
|
||||
}
|
||||
check(a(1, 2, M(3, A(3))));
|
||||
buffer::range a1;
|
||||
check(a(1, 2, extract(a1)));
|
||||
if (verbose)
|
||||
_ = log.send("map range json:", a1.to_json());
|
||||
check(a1.to_json() == "{3:[3]}");
|
||||
}
|
||||
{
|
||||
buffer a;
|
||||
a.push_json(R"(["five", 5, "four", 4, ["three", 3]])");
|
||||
if (verbose) {
|
||||
_ = log.send("buffer:", a.hexdump());
|
||||
_ = log.send("message:", a.to_json());
|
||||
}
|
||||
check(a.hexdump() == "21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 "
|
||||
"65 74 68 72 65 65 03");
|
||||
}
|
||||
{
|
||||
buffer a;
|
||||
a.push_json(R"({"five": 5, "four": 4, "three": [{3:3}]})");
|
||||
string json = a.to_json();
|
||||
if (verbose) {
|
||||
_ = log.send("buffer:", a.hexdump());
|
||||
_ = log.send("json:", json);
|
||||
}
|
||||
check(a.hexdump() == "23: a3 64 66 69 76 65 05 64 66 6f 75 72 04 65 "
|
||||
"74 68 72 65 65 81 a1 03 03");
|
||||
check(json == R"({"five":5,"four":4,"three":[{3:3}]})");
|
||||
}
|
||||
{
|
||||
buffer a = array("string\r\n", "\tstring\t", "\r\nstring");
|
||||
string json = a.to_json();
|
||||
if (verbose) {
|
||||
_ = log.send("escaped buffer:", a.hexdump());
|
||||
_ = log.send("escaped json:", json);
|
||||
}
|
||||
check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 "
|
||||
"72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67");
|
||||
check(json == R"(["string\r\n","\tstring\t","\r\nstring"])");
|
||||
}
|
||||
{
|
||||
buffer a;
|
||||
a.push_json(R"(["string\r\n", "\tstring\t", "\r\nstring"])");
|
||||
string json = a.to_json();
|
||||
if (verbose) {
|
||||
_ = log.send("escaped2 buffer:", a.hexdump());
|
||||
_ = log.send("escaped2 json:", json);
|
||||
}
|
||||
check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 "
|
||||
"72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67");
|
||||
check(json == R"(["string\r\n","\tstring\t","\r\nstring"])");
|
||||
}
|
||||
{
|
||||
buffer a = array(array());
|
||||
buffer::range r;
|
||||
check(a(extract(r)));
|
||||
int i{0};
|
||||
auto dump = [&](uint32_t /*val*/) { ++i; };
|
||||
for (const auto v1 : r)
|
||||
dump(v1);
|
||||
check(i == 0);
|
||||
check(r.is_null());
|
||||
check(r.to_json() == "null");
|
||||
}
|
||||
{
|
||||
int64_t i{-1};
|
||||
uint64_t sz{0};
|
||||
buffer a = array(i, sz);
|
||||
check(a(extract(i), extract(sz)));
|
||||
check(i == -1);
|
||||
check(sz == 0);
|
||||
}
|
||||
{
|
||||
int64_t i{-1};
|
||||
uint64_t sz{0};
|
||||
buffer a = array(i);
|
||||
check(a(extract(i)));
|
||||
check(not a(extract(sz)));
|
||||
}
|
||||
{
|
||||
int32_t i{-1};
|
||||
uint32_t sz{0};
|
||||
buffer a = array(i);
|
||||
check(a(extract(i)));
|
||||
check(not a(extract(sz)));
|
||||
}
|
||||
{
|
||||
int16_t i{-1};
|
||||
uint16_t sz{0};
|
||||
buffer a = array(i);
|
||||
check(a(extract(i)));
|
||||
check(not a(extract(sz)));
|
||||
}
|
||||
{
|
||||
int8_t i{-1};
|
||||
uint8_t sz{0};
|
||||
buffer a = array(i);
|
||||
check(a(extract(i)));
|
||||
check(not a(extract(sz)));
|
||||
}
|
||||
{
|
||||
uint16_t ui{1000};
|
||||
uint8_t sz{0};
|
||||
buffer a = array(ui);
|
||||
check(a(extract(ui)));
|
||||
check(not a(extract(sz)));
|
||||
}
|
||||
{
|
||||
uint64_t ui{18446744073709551615ULL};
|
||||
int64_t i{0};
|
||||
buffer a = array(ui);
|
||||
check(a(extract(ui)));
|
||||
check(not a(extract(i)));
|
||||
}
|
||||
{
|
||||
buffer a = array(array(1, 2));
|
||||
buffer b = array(array(1, 2));
|
||||
buffer::range ra;
|
||||
check(a(extract(ra)));
|
||||
buffer::range rb;
|
||||
check(b(extract(rb)));
|
||||
check(ra == rb);
|
||||
}
|
||||
{
|
||||
buffer a = array(array(1));
|
||||
buffer b = array(array(1, 2));
|
||||
buffer::range ra;
|
||||
check(a(extract(ra)));
|
||||
buffer::range rb;
|
||||
check(b(extract(rb)));
|
||||
check(not(ra == rb));
|
||||
}
|
||||
if (verbose)
|
||||
_ = log.send("done");
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto cbor_match(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
return test();
|
||||
},
|
||||
[&](auto s) {
|
||||
check(s == "noreceive");
|
||||
result = true;
|
||||
},
|
||||
"cbor_match", move(env_)));
|
||||
};
|
196
test/debug.cpp
Normal file
196
test/debug.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/debug.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/socket.hpp>
|
||||
#include <thespian/tcp.hpp>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using std::make_shared;
|
||||
using std::make_unique;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using std::unique_ptr;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::error;
|
||||
using thespian::exit;
|
||||
using thespian::expected;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::socket;
|
||||
using thespian::spawn_link;
|
||||
using thespian::trap;
|
||||
using thespian::unexpected;
|
||||
using thespian::tcp::connector;
|
||||
|
||||
namespace {
|
||||
|
||||
struct debuggee {
|
||||
auto receive(const handle &from, const buffer &m) {
|
||||
if (m("ping")) {
|
||||
return from.send("pong");
|
||||
}
|
||||
if (m("shutdown"))
|
||||
return exit("debuggee_shutdown");
|
||||
return unexpected(m);
|
||||
return ok();
|
||||
}
|
||||
|
||||
static auto start() -> expected<handle, error> {
|
||||
return spawn_link(
|
||||
[=]() {
|
||||
::receive([p{make_shared<debuggee>()}](auto from, auto m) {
|
||||
return p->receive(move(from), move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
"debuggee");
|
||||
}
|
||||
};
|
||||
|
||||
struct controller {
|
||||
static constexpr string_view tag{"debug_test_controller"};
|
||||
handle debug_tcp;
|
||||
handle debuggee_;
|
||||
connector c;
|
||||
unique_ptr<thespian::socket> s;
|
||||
size_t pong_count{};
|
||||
bool success_{false};
|
||||
string prev_buf;
|
||||
|
||||
explicit controller(handle d, handle debuggee)
|
||||
: debug_tcp{std::move(d)}, debuggee_{std::move(debuggee)},
|
||||
c{connector::create(tag)} {
|
||||
trap(true);
|
||||
}
|
||||
|
||||
auto receive(const handle & /*from*/, const buffer &m) {
|
||||
int fd{};
|
||||
string buf;
|
||||
int written{};
|
||||
int err{};
|
||||
string_view err_msg{};
|
||||
|
||||
if (m("pong")) {
|
||||
pong_count++;
|
||||
if (pong_count == 2)
|
||||
c.connect(in6addr_loopback, 4242);
|
||||
} else if (m("connector", tag, "connected", extract(fd))) {
|
||||
s = make_unique<thespian::socket>(socket::create(tag, fd));
|
||||
s->read();
|
||||
s->write("\n");
|
||||
s->write("debuggee [\"ping\"]\n");
|
||||
} else if (m("socket", tag, "read_error", extract(err), extract(err_msg))) {
|
||||
return exit("read_error", err_msg);
|
||||
} else if (m("socket", tag, "read_complete", extract(buf))) {
|
||||
if (buf.empty()) {
|
||||
s->close();
|
||||
return ok();
|
||||
}
|
||||
s->read();
|
||||
buf.swap(prev_buf);
|
||||
buf.append(prev_buf);
|
||||
string::size_type pos{};
|
||||
while (not buf.empty() and pos != string::npos) {
|
||||
pos = buf.find_first_of('\n');
|
||||
if (pos != string::npos) {
|
||||
auto ret = self().send("dispatch", string(buf.data(), pos));
|
||||
if (not ret)
|
||||
return ret;
|
||||
buf.erase(0, pos + 1);
|
||||
}
|
||||
}
|
||||
prev_buf = buf;
|
||||
} else if (m("dispatch", extract(buf))) {
|
||||
if (buf == "debuggee [\"pong\"]") {
|
||||
s->write("debuggee [\"shutdown\"]\n");
|
||||
}
|
||||
} else if (m("socket", tag, "write_error", extract(err),
|
||||
extract(err_msg))) {
|
||||
return exit("write_error", err_msg);
|
||||
} else if (m("socket", tag, "write_complete", extract(written))) {
|
||||
;
|
||||
} else if (m("socket", tag, "closed")) {
|
||||
if (success_)
|
||||
return exit("success");
|
||||
auto ret = debuggee_.send("shutdown");
|
||||
if (not ret)
|
||||
return ret;
|
||||
return exit("closed");
|
||||
} else if (m("exit", "debuggee_shutdown")) {
|
||||
success_ = true;
|
||||
s->close();
|
||||
} else if (m("connector", tag, "cancelled"))
|
||||
return exit("cancelled");
|
||||
else if (m("connector", tag, "error", extract(buf)))
|
||||
return exit("connect_error", buf);
|
||||
|
||||
else
|
||||
return unexpected(m);
|
||||
return ok();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
auto debug(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
thespian::debug::enable(ctx);
|
||||
return to_result(ctx.spawn_link(
|
||||
[&ctx]() {
|
||||
link(env().proc("log"));
|
||||
auto ret = thespian::debug::tcp::create(ctx, 4242, "");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
auto debug_tcp = ret.value();
|
||||
ret = spawn_link(
|
||||
[&ctx, debug_tcp]() {
|
||||
trap(true);
|
||||
::receive([&ctx, debug_tcp](auto, auto /*m*/) -> ::result {
|
||||
auto ret = debug_tcp.send("shutdown");
|
||||
if (not ret)
|
||||
return ret;
|
||||
thespian::debug::disable(ctx);
|
||||
return exit();
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
"controller_guard");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
auto ret2 = debug_tcp.send("ping");
|
||||
if (not ret2)
|
||||
return ret2;
|
||||
ret = debuggee::start();
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
auto debuggee = ret.value();
|
||||
ret2 = debuggee.send("ping");
|
||||
if (not ret2)
|
||||
return ret2;
|
||||
receive([p{make_shared<controller>(debug_tcp, debuggee)}](auto from,
|
||||
auto m) {
|
||||
return p->receive(move(from), move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "success")
|
||||
result = true;
|
||||
},
|
||||
"debug", move(env_)));
|
||||
}
|
93
test/endpoint_tcp.cpp
Normal file
93
test/endpoint_tcp.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "cbor/cbor.hpp"
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/endpoint.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/socket.hpp>
|
||||
#include <thespian/tcp.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
using cbor::any;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using std::make_shared;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::unexpected;
|
||||
using thespian::endpoint::tcp::listen;
|
||||
|
||||
namespace {
|
||||
|
||||
struct controller {
|
||||
handle ep_listen;
|
||||
handle ep_connect;
|
||||
size_t ping_count{};
|
||||
size_t pong_count{};
|
||||
bool success_{false};
|
||||
|
||||
explicit controller(handle ep_l) : ep_listen{move(ep_l)} {}
|
||||
|
||||
auto receive(const handle &from, const buffer &m) {
|
||||
port_t port{0};
|
||||
if (m("port", extract(port))) {
|
||||
ep_connect =
|
||||
thespian::endpoint::tcp::connect(in6addr_loopback, port).value();
|
||||
return ok();
|
||||
}
|
||||
if (m("connected")) {
|
||||
ping_count++;
|
||||
return ep_connect.send("ping", ping_count);
|
||||
}
|
||||
if (m("ping", any)) {
|
||||
pong_count++;
|
||||
return from.send("pong", pong_count);
|
||||
}
|
||||
if (m("pong", any)) {
|
||||
if (ping_count > 10) {
|
||||
return exit(ping_count == pong_count ? "success" : "closed");
|
||||
}
|
||||
ping_count++;
|
||||
return from.send("ping", ping_count);
|
||||
}
|
||||
return unexpected(m);
|
||||
}
|
||||
}; // namespace
|
||||
|
||||
} // namespace
|
||||
|
||||
auto endpoint_tcp(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
handle ep_listen = listen(in6addr_loopback, 0).value();
|
||||
auto ret = ep_listen.send("get", "port");
|
||||
if (not ret)
|
||||
return ret;
|
||||
|
||||
receive([p{make_shared<controller>(ep_listen)}](auto from, auto m) {
|
||||
return p->receive(move(from), move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "success")
|
||||
result = true;
|
||||
},
|
||||
"endpoint_tcp", move(env_)));
|
||||
}
|
104
test/endpoint_unx.cpp
Normal file
104
test/endpoint_unx.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include <thespian/endpoint.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/socket.hpp>
|
||||
#include <thespian/timeout.hpp>
|
||||
#include <thespian/unx.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using std::make_shared;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::create_timeout;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::timeout;
|
||||
using thespian::unexpected;
|
||||
using thespian::endpoint::unx::connect;
|
||||
using thespian::endpoint::unx::listen;
|
||||
using thespian::unx::mode;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
struct controller {
|
||||
handle ep_listen;
|
||||
handle ep_connect;
|
||||
size_t ping_count{};
|
||||
size_t pong_count{};
|
||||
bool success_{false};
|
||||
timeout t{create_timeout(100ms, array("connect"))};
|
||||
|
||||
explicit controller(handle ep_l) : ep_listen{move(ep_l)} {}
|
||||
|
||||
auto receive(const handle &from, const buffer &m) {
|
||||
string_view path;
|
||||
if (m("connect")) {
|
||||
auto ret = ep_listen.send("get", "path");
|
||||
if (not ret)
|
||||
return ret;
|
||||
return ok();
|
||||
}
|
||||
if (m("path", extract(path))) {
|
||||
ep_connect = connect(path).value();
|
||||
return ok();
|
||||
}
|
||||
if (m("connected")) {
|
||||
ping_count++;
|
||||
return ep_connect.send("ping");
|
||||
}
|
||||
if (m("ping")) {
|
||||
pong_count++;
|
||||
return from.send("pong");
|
||||
}
|
||||
if (m("pong")) {
|
||||
if (ping_count > 10) {
|
||||
return exit(ping_count == pong_count ? "success" : "closed");
|
||||
}
|
||||
ping_count++;
|
||||
return ep_connect.send("ping");
|
||||
}
|
||||
return unexpected(m);
|
||||
}
|
||||
}; // namespace
|
||||
|
||||
} // namespace
|
||||
|
||||
auto endpoint_unx(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
stringstream ss;
|
||||
ss << "/net/vdbonline/thespian/endpoint_t_" << getpid();
|
||||
string path = ss.str();
|
||||
return to_result(ctx.spawn_link(
|
||||
[path]() {
|
||||
link(env().proc("log"));
|
||||
handle ep_listen = listen(path).value();
|
||||
receive([p{make_shared<controller>(ep_listen)}](auto from, auto m) {
|
||||
return p->receive(move(from), move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "success")
|
||||
result = true;
|
||||
},
|
||||
"endpoint_unx", move(env_)));
|
||||
}
|
127
test/hub_filter.cpp
Normal file
127
test/hub_filter.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/hub.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
#include <map>
|
||||
|
||||
using cbor::extract;
|
||||
using cbor::type;
|
||||
using std::map;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::env_t;
|
||||
using thespian::handle;
|
||||
using thespian::hub;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::spawn_link;
|
||||
|
||||
namespace {
|
||||
|
||||
auto sub_plain(const hub &h, const handle &controller, string name) -> result {
|
||||
auto ret = h.subscribe();
|
||||
if (not ret)
|
||||
return ret;
|
||||
ret = controller.send("ready", name);
|
||||
if (not ret)
|
||||
return ret;
|
||||
receive([=](auto /*from*/, auto m) {
|
||||
string_view cmd;
|
||||
check(m("parmA", "parmB", "parmC", extract(cmd)));
|
||||
check(cmd == "continue" || cmd == "done");
|
||||
if (cmd == "done") {
|
||||
auto ret = controller.send(cmd, name);
|
||||
if (not ret)
|
||||
return ret;
|
||||
}
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto sub_filtered(const hub &h, const handle &controller, string name)
|
||||
-> result {
|
||||
auto ret = h.subscribe(
|
||||
[](auto m) { return m(type::any, type::any, type::any, "done"); });
|
||||
if (not ret)
|
||||
return ret;
|
||||
ret = controller.send("ready", name);
|
||||
if (not ret)
|
||||
return ret;
|
||||
receive([=](auto /*from*/, auto m) {
|
||||
check(m("parmA", "parmB", "parmC", "done"));
|
||||
auto ret = controller.send("done", name);
|
||||
if (not ret)
|
||||
return ret;
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
map<string, auto(*)(const hub &, const handle &, string)->result>
|
||||
subscription_defs // NOLINT
|
||||
= {
|
||||
{"sub_plain", sub_plain},
|
||||
{"sub_filtered", sub_filtered},
|
||||
};
|
||||
using submap = map<string, handle>;
|
||||
|
||||
auto controller() -> result {
|
||||
thespian::link(thespian::env().proc("log"));
|
||||
auto h = hub::create("hub").value();
|
||||
handle c = self();
|
||||
auto ret = h.subscribe();
|
||||
if (not ret)
|
||||
return ret;
|
||||
|
||||
submap subs;
|
||||
for (const auto &s : subscription_defs) {
|
||||
subs.insert({s.first, spawn_link(
|
||||
[=]() {
|
||||
s.second(h, c, s.first);
|
||||
return ok();
|
||||
},
|
||||
s.first)
|
||||
.value()});
|
||||
}
|
||||
submap not_ready = subs;
|
||||
submap done = subs;
|
||||
|
||||
receive([=](auto, auto m) mutable {
|
||||
string name;
|
||||
if (m("ready", extract(name))) {
|
||||
not_ready.erase(name);
|
||||
if (not_ready.empty()) {
|
||||
h.broadcast("parmA", "parmB", "parmC", "continue");
|
||||
h.broadcast("parmA", "parmB", "parmC", "continue");
|
||||
h.broadcast("parmA", "parmB", "parmC", "done");
|
||||
}
|
||||
} else if (m("done", extract(name))) {
|
||||
done.erase(name);
|
||||
if (done.empty())
|
||||
return h.shutdown();
|
||||
}
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto hub_filter(context &ctx, bool &result, env_t env) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
controller,
|
||||
[&](auto s) {
|
||||
if (s == "shutdown")
|
||||
result = true;
|
||||
},
|
||||
"hub_filter", move(env)));
|
||||
}
|
225
test/ip_tcp_client_server.cpp
Normal file
225
test/ip_tcp_client_server.cpp
Normal file
|
@ -0,0 +1,225 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/handle.hpp"
|
||||
#include <cbor/cbor_in.hpp>
|
||||
#include <thespian/debug.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/socket.hpp>
|
||||
#include <thespian/tcp.hpp>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using std::make_shared;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::socket;
|
||||
using thespian::spawn_link;
|
||||
using thespian::unexpected;
|
||||
using thespian::tcp::acceptor;
|
||||
using thespian::tcp::connector;
|
||||
|
||||
namespace {
|
||||
|
||||
struct client_connection {
|
||||
thespian::socket socket;
|
||||
handle connector;
|
||||
|
||||
explicit client_connection(int fd, handle connector)
|
||||
: socket{socket::create("client_connection", fd)},
|
||||
connector(move(connector)) {
|
||||
socket.read();
|
||||
}
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
int written{};
|
||||
int err{};
|
||||
string_view buf{};
|
||||
|
||||
if (m("socket", "client_connection", "read_complete", extract(buf))) {
|
||||
check(buf == "ping");
|
||||
check(err == 0);
|
||||
socket.write("pong");
|
||||
} else if (m("socket", "client_connection", "write_complete",
|
||||
extract(written))) {
|
||||
check(written == 4);
|
||||
check(err == 0);
|
||||
// socket.close(); // let server close
|
||||
} else if (m("socket", "client_connection", "closed")) {
|
||||
auto ret = connector.send("client_connection", "done");
|
||||
if (not ret)
|
||||
return ret;
|
||||
return exit("success");
|
||||
} else {
|
||||
return unexpected(m);
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto init(int fd, const handle &connector) {
|
||||
return spawn_link(
|
||||
[fd, connector]() {
|
||||
::thespian::receive(
|
||||
[p{make_shared<client_connection>(fd, connector)}](
|
||||
auto /*from*/, auto m) { return p->receive(move(m)); });
|
||||
return ok();
|
||||
},
|
||||
"client_connection");
|
||||
}
|
||||
};
|
||||
|
||||
struct client {
|
||||
connector connector;
|
||||
handle server;
|
||||
port_t server_port;
|
||||
|
||||
explicit client(handle server, port_t server_port)
|
||||
: connector{connector::create("client")}, server(move(server)),
|
||||
server_port(server_port) {
|
||||
connector.connect(in6addr_loopback, server_port);
|
||||
}
|
||||
|
||||
auto receive(const buffer &m) -> thespian::result {
|
||||
int fd{};
|
||||
if (m("connector", "client", "connected", extract(fd))) {
|
||||
return to_result(client_connection::init(fd, self()));
|
||||
}
|
||||
if (m("connector_connection", "done")) {
|
||||
return server.send("client", "done");
|
||||
}
|
||||
return unexpected(m);
|
||||
}
|
||||
|
||||
static auto init(handle server, port_t server_port) -> result {
|
||||
::thespian::receive(
|
||||
[p{make_shared<client>(server, server_port)}](auto /*from*/, auto m) {
|
||||
return p->receive(move(m));
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
};
|
||||
|
||||
struct server_connection {
|
||||
thespian::socket socket;
|
||||
handle server;
|
||||
|
||||
explicit server_connection(int fd, handle server)
|
||||
: socket{socket::create("server_connection", fd)}, server(move(server)) {
|
||||
socket.write("ping");
|
||||
}
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
int written{};
|
||||
int err{};
|
||||
string_view buf{};
|
||||
|
||||
if (m("socket", "server_connection", "write_complete", extract(written))) {
|
||||
check(written == 4);
|
||||
check(err == 0);
|
||||
socket.read();
|
||||
} else if (m("socket", "server_connection", "read_complete",
|
||||
extract(buf))) {
|
||||
check(buf == "pong");
|
||||
check(err == 0);
|
||||
socket.close();
|
||||
} else if (m("socket", "server_connection", "closed")) {
|
||||
auto ret = server.send("server_connection", "done");
|
||||
if (not ret)
|
||||
return ret;
|
||||
return exit("success");
|
||||
} else {
|
||||
return unexpected(m);
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto init(int fd, const handle &server) {
|
||||
return spawn_link(
|
||||
[fd, server]() {
|
||||
::thespian::receive(
|
||||
[p{make_shared<server_connection>(fd, server)}](
|
||||
auto /*from*/, auto m) { return p->receive(move(m)); });
|
||||
return ok();
|
||||
},
|
||||
"server_connection");
|
||||
}
|
||||
};
|
||||
|
||||
struct server {
|
||||
acceptor acceptor;
|
||||
bool client_done{false};
|
||||
bool server_connection_done{false};
|
||||
bool acceptor_closed{false};
|
||||
port_t server_port;
|
||||
|
||||
explicit server()
|
||||
: acceptor{acceptor::create("server")},
|
||||
server_port(acceptor.listen(in6addr_loopback, 0)) {}
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
int fd{};
|
||||
if (m("acceptor", "server", "accept", extract(fd))) {
|
||||
auto ret = server_connection::init(fd, self());
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
acceptor.close();
|
||||
} else if (m("acceptor", "server", "closed")) {
|
||||
acceptor_closed = true;
|
||||
} else if (m("client", "done")) {
|
||||
client_done = true;
|
||||
} else if (m("server_connection", "done")) {
|
||||
server_connection_done = true;
|
||||
} else {
|
||||
return unexpected(m);
|
||||
}
|
||||
if (acceptor_closed and client_done and server_connection_done)
|
||||
return exit("success");
|
||||
return ok();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto init() {
|
||||
link(env().proc("log"));
|
||||
auto log = env().proc("log");
|
||||
auto _ = log.send("server starting");
|
||||
auto p{make_shared<server>()};
|
||||
_ = log.send("server listening on port", p->server_port);
|
||||
auto ret = spawn_link(
|
||||
[server{self()}, server_port{p->server_port}]() {
|
||||
return client::init(server, server_port);
|
||||
},
|
||||
"ip_tcp_client");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
thespian::receive(
|
||||
[p](auto /*from*/, auto m) { return p->receive(move(m)); });
|
||||
return ok();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
auto ip_tcp_client_server(context &ctx, bool &result, env_t env) -> ::result {
|
||||
thespian::debug::enable(ctx);
|
||||
return to_result(ctx.spawn_link(
|
||||
server::init,
|
||||
[&](auto s) {
|
||||
if (s == "success") {
|
||||
result = true;
|
||||
} else {
|
||||
auto _ = env.proc("log").send("failed:", s);
|
||||
}
|
||||
},
|
||||
"ip_tcp_client_server", move(env)));
|
||||
}
|
122
test/ip_udp_echo.cpp
Normal file
122
test/ip_udp_echo.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/timeout.hpp>
|
||||
#include <thespian/udp.hpp>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using cbor::more;
|
||||
using std::make_shared;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::stringstream;
|
||||
using thespian::context;
|
||||
using thespian::create_timeout;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::udp;
|
||||
using thespian::unexpected;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
struct controller {
|
||||
port_t server_port{0};
|
||||
port_t client_port{0};
|
||||
udp client;
|
||||
udp server;
|
||||
size_t ping_count{};
|
||||
size_t pong_count{};
|
||||
bool success_{false};
|
||||
thespian::timeout server_receive_timeout{
|
||||
create_timeout(500s, array("udp", "server", "timeout"))};
|
||||
|
||||
explicit controller()
|
||||
: client{udp::create("client")}, server{udp::create("server")} {
|
||||
server_port = server.open(in6addr_loopback, server_port);
|
||||
client.open(in6addr_loopback, client_port);
|
||||
auto sent = client.sendto("ping", in6addr_loopback, server_port);
|
||||
if (sent != 4)
|
||||
abort();
|
||||
}
|
||||
controller(controller &&) = delete;
|
||||
controller(const controller &) = delete;
|
||||
|
||||
~controller() { server_receive_timeout.cancel(); }
|
||||
|
||||
auto operator=(controller &&) -> controller & = delete;
|
||||
auto operator=(const controller &) -> controller & = delete;
|
||||
|
||||
auto client_receive(const buffer &m) {
|
||||
in6_addr remote_ip{};
|
||||
port_t remote_port{};
|
||||
if (m("udp", "client", "receive", "pong", extract(remote_ip),
|
||||
extract(remote_port))) {
|
||||
check(remote_port == server_port);
|
||||
client.close();
|
||||
} else if (m("udp", "client", "closed")) {
|
||||
return exit("success");
|
||||
} else {
|
||||
return unexpected(m);
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto server_receive(const buffer &m) {
|
||||
in6_addr remote_ip{};
|
||||
port_t remote_port{};
|
||||
if (m("udp", "server", "receive", "ping", extract(remote_ip),
|
||||
extract(remote_port))) {
|
||||
auto sent = server.sendto("pong", remote_ip, remote_port);
|
||||
if (sent != 4)
|
||||
return exit("unexpected_sendto_size", sent);
|
||||
server.close();
|
||||
} else if (m("udp", "server", "timeout")) {
|
||||
return exit("timeout");
|
||||
} else if (m("udp", "server", "closed")) {
|
||||
;
|
||||
} else {
|
||||
return unexpected(m);
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
if (m("udp", "client", more))
|
||||
return client_receive(m);
|
||||
if (m("udp", "server", more))
|
||||
return server_receive(m);
|
||||
return unexpected(m);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
auto ip_udp_echo(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
receive([p{make_shared<controller>()}](auto /*from*/, auto m) {
|
||||
return p->receive(move(m));
|
||||
});
|
||||
return ok();
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "success")
|
||||
result = true;
|
||||
},
|
||||
"ip_udp_echo", move(env_)));
|
||||
}
|
81
test/metronome_test.cpp
Normal file
81
test/metronome_test.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/metronome.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using cbor::buffer;
|
||||
using std::move;
|
||||
using std::shared_ptr;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::system_clock;
|
||||
using thespian::context;
|
||||
using thespian::create_metronome;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::exit_handler;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::metronome;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::unexpected;
|
||||
|
||||
using clk = std::chrono::system_clock;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
auto initA() {
|
||||
link(env().proc("log"));
|
||||
struct state_t {
|
||||
int i{0};
|
||||
system_clock::time_point start{clk::now()};
|
||||
metronome met{create_metronome(10ms)};
|
||||
state_t() { met.start(); }
|
||||
handle log{env().proc("log")};
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
if (m("tick", i))
|
||||
++i;
|
||||
else
|
||||
return unexpected(m);
|
||||
{
|
||||
auto end = clk::now();
|
||||
auto us = duration_cast<microseconds>(end - start).count();
|
||||
auto ret = log.send("tick@", us, "µs");
|
||||
if (not ret)
|
||||
return ret;
|
||||
}
|
||||
if (i == 5) {
|
||||
auto end = clk::now();
|
||||
auto us = duration_cast<microseconds>(end - start);
|
||||
met.stop();
|
||||
return exit(us > 50000us ? "done" : "failed");
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
};
|
||||
shared_ptr<state_t> state{new state_t};
|
||||
receive([state](auto, auto m) mutable { return state->receive(m); });
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto metronome_test(context &ctx, bool &result, env_t env) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
initA,
|
||||
[&](auto s) {
|
||||
if (s == "done")
|
||||
result = true;
|
||||
},
|
||||
"metronome", move(env)));
|
||||
}
|
140
test/perf_cbor.cpp
Normal file
140
test/perf_cbor.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
using cbor::A;
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using cbor::extract;
|
||||
using cbor::type;
|
||||
using std::move;
|
||||
using std::string_view;
|
||||
using std::to_string;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::result;
|
||||
|
||||
using clk = std::chrono::system_clock;
|
||||
|
||||
namespace {
|
||||
|
||||
auto count{10000UL}; // NOLINT
|
||||
|
||||
auto time(const char *name, void (*f)()) -> void {
|
||||
auto start = clk::now();
|
||||
|
||||
for (uint64_t i{0}; i < count; ++i)
|
||||
f();
|
||||
|
||||
auto end = clk::now();
|
||||
auto us = duration_cast<microseconds>(end - start).count();
|
||||
|
||||
auto _ = env().proc("log").send(to_string((count * 1.0L) / us), name, "/µs");
|
||||
}
|
||||
|
||||
const auto ma = array("five", 5, "four", 4, array("three", 3));
|
||||
|
||||
auto test() -> result {
|
||||
link(env().proc("log"));
|
||||
auto _ = env().proc("log").send("buffer size:", ma.size());
|
||||
|
||||
time("noop", []() {});
|
||||
|
||||
time("encode_template", []() {
|
||||
buffer m{array("five", 5, "four", 4, array("three", 3))};
|
||||
check(m.size() == 21);
|
||||
});
|
||||
|
||||
time("encode_chain", []() {
|
||||
buffer b;
|
||||
b.array_header(5)
|
||||
.push("five")
|
||||
.push(5)
|
||||
.push("four")
|
||||
.push(4)
|
||||
.array_header(2)
|
||||
.push("three")
|
||||
.push(3);
|
||||
check(b.size() == 21);
|
||||
});
|
||||
|
||||
time("encode_chain_reserve", []() {
|
||||
buffer b;
|
||||
b.reserve(21);
|
||||
b.array_header(5)
|
||||
.push("five")
|
||||
.push(5)
|
||||
.push("four")
|
||||
.push(4)
|
||||
.array_header(2)
|
||||
.push("three")
|
||||
.push(3);
|
||||
check(b.size() == 21);
|
||||
});
|
||||
|
||||
time("match", []() {
|
||||
check(ma(type::string, type::number, type::string, type::number,
|
||||
type::array));
|
||||
});
|
||||
|
||||
time("match2", []() {
|
||||
check(ma(type::string, type::number, type::string, type::number,
|
||||
A(type::string, type::number)));
|
||||
});
|
||||
|
||||
time("extract_int", []() {
|
||||
int64_t i{};
|
||||
check(ma(type::string, extract(i), type::more));
|
||||
check(i == 5);
|
||||
});
|
||||
|
||||
time("extract_string", []() {
|
||||
string_view s;
|
||||
check(ma(extract(s), type::more));
|
||||
});
|
||||
|
||||
time("extract_int2", []() {
|
||||
int64_t i{};
|
||||
check(ma(type::string, type::number, type::string, type::number,
|
||||
A(type::string, extract(i))));
|
||||
check(i == 3);
|
||||
});
|
||||
|
||||
time("extract_string2", []() {
|
||||
string_view s;
|
||||
check(ma(type::string, type::number, type::string, type::number,
|
||||
A(extract(s), type::number)));
|
||||
});
|
||||
|
||||
time("extract_array", []() {
|
||||
buffer::range r;
|
||||
check(
|
||||
ma(type::string, type::number, type::string, type::number, extract(r)));
|
||||
check(r(type::string, type::number));
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto perf_cbor(context &ctx, bool &result, env_t env) -> ::result {
|
||||
if (env.is("long_run"))
|
||||
count = 10000000;
|
||||
return to_result(ctx.spawn_link(
|
||||
test,
|
||||
[&](auto s) {
|
||||
check(s == "noreceive");
|
||||
result = true;
|
||||
},
|
||||
"perf_cbor", move(env)));
|
||||
}
|
153
test/perf_hub.cpp
Normal file
153
test/perf_hub.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/hub.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
using cbor::extract;
|
||||
using std::move;
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::underlying_type_t;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::milliseconds;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::handle;
|
||||
using thespian::hub;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::spawn_link;
|
||||
using thespian::unexpected;
|
||||
|
||||
using clk = std::chrono::system_clock;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class tag {
|
||||
ready = 1,
|
||||
done = 2,
|
||||
};
|
||||
|
||||
auto tag_to_int(tag e) noexcept -> int {
|
||||
return static_cast<underlying_type_t<tag>>(e);
|
||||
}
|
||||
auto tag_from_int(int i) -> tag {
|
||||
return static_cast<tag>(static_cast<int>(i));
|
||||
}
|
||||
|
||||
auto subscriber(const hub &h, const handle &controller, const int num,
|
||||
const int messages) -> result {
|
||||
auto ret = h.subscribe();
|
||||
if (not ret)
|
||||
return ret;
|
||||
ret = controller.send(tag_to_int(tag::ready), num);
|
||||
if (not ret)
|
||||
return ret;
|
||||
int count{1};
|
||||
receive([=](auto /*from*/, auto m) mutable {
|
||||
if (!m("parmA", "parmB", "parmC", count))
|
||||
std::abort();
|
||||
if (count == messages) {
|
||||
auto ret = controller.send(tag_to_int(tag::done), num);
|
||||
if (not ret)
|
||||
return ret;
|
||||
}
|
||||
++count;
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto controller(const int subscriptions, const int messages) -> result {
|
||||
const auto log = env().proc("log");
|
||||
const auto verbose = env().is("verbose");
|
||||
auto ret = hub::create("hub");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
auto h = ret.value();
|
||||
handle c = self();
|
||||
|
||||
auto _ = log.send("subscribing", subscriptions);
|
||||
|
||||
set<int> subs;
|
||||
for (auto i{0}; i < subscriptions; ++i) {
|
||||
auto ret =
|
||||
spawn_link([=]() -> result { return subscriber(h, c, i, messages); },
|
||||
"subscriber");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
subs.insert(i);
|
||||
}
|
||||
_ = log.send("all", "spawned");
|
||||
set<int> not_ready = subs;
|
||||
set<int> done = subs;
|
||||
clk::time_point start;
|
||||
|
||||
receive([=](auto, auto m) mutable {
|
||||
int tag_{}, num{};
|
||||
if (!m(extract(tag_), extract(num)))
|
||||
return unexpected(m);
|
||||
tag tag = tag_from_int(tag_);
|
||||
switch (tag) {
|
||||
case tag::ready:
|
||||
not_ready.erase(num);
|
||||
if (not_ready.empty()) {
|
||||
if (verbose)
|
||||
_ = log.send("all", "ready");
|
||||
for (int i = 1; i <= messages; ++i)
|
||||
h.broadcast("parmA", "parmB", "parmC", i);
|
||||
start = clk::now();
|
||||
if (verbose)
|
||||
_ = log.send("broadcasting", messages);
|
||||
}
|
||||
break;
|
||||
|
||||
case tag::done:
|
||||
done.erase(num);
|
||||
if (done.empty()) {
|
||||
if (verbose) {
|
||||
clk::time_point end = clk::now();
|
||||
auto ms = duration_cast<milliseconds>(end - start).count();
|
||||
double msgs = subscriptions * messages + subscriptions;
|
||||
_ = log.send("all", "done");
|
||||
_ = log.send("time:", ms, "ms");
|
||||
_ = log.send(
|
||||
static_cast<int>((msgs / static_cast<double>(ms)) * 1000),
|
||||
"msg/s");
|
||||
}
|
||||
return h.shutdown();
|
||||
}
|
||||
}
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto perf_hub(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
const auto long_run = env_.is("long_run");
|
||||
const auto subscriptions = long_run ? 1000 : 100;
|
||||
const auto messages = long_run ? 10000 : 100;
|
||||
return to_result(ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
return controller(subscriptions, messages);
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "shutdown")
|
||||
result = true;
|
||||
},
|
||||
"controller", move(env_)));
|
||||
}
|
79
test/perf_ring.cpp
Normal file
79
test/perf_ring.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
using cbor::extract;
|
||||
using std::move;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::handle;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::spawn_link;
|
||||
|
||||
namespace {
|
||||
|
||||
auto slave(const handle &last, int n) -> result {
|
||||
handle next;
|
||||
if (n)
|
||||
next = spawn_link([=]() { return slave(last, n - 1); }, "slave").value();
|
||||
|
||||
receive([n, next, last](auto, auto m) {
|
||||
return n ? next.send_raw(move(m)) : last.send_raw(move(m));
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto controller(const int slaves) -> result {
|
||||
auto verbose = env().is("long_run");
|
||||
int loop = 10;
|
||||
handle last = self();
|
||||
auto ret = spawn_link([=]() { return slave(last, slaves); }, "slave");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
handle first = ret.value();
|
||||
auto ret2 = first.send("forward");
|
||||
if (not ret2)
|
||||
return ret2;
|
||||
receive([loop, first, verbose](auto, auto m) mutable {
|
||||
if (loop) {
|
||||
if (verbose)
|
||||
auto _ = env().proc("log").send(loop);
|
||||
--loop;
|
||||
return first.send_raw(move(m));
|
||||
}
|
||||
string_view s;
|
||||
check(m(extract(s)));
|
||||
return exit(s);
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto perf_ring(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
auto long_run = env_.is("long_run");
|
||||
auto slaves{long_run ? 100000 : 1000};
|
||||
return to_result(ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
return controller(slaves);
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "forward") {
|
||||
result = true;
|
||||
} else {
|
||||
auto _ = env_.proc("log").send("failed:", s);
|
||||
}
|
||||
},
|
||||
"perf_ring", move(env_)));
|
||||
}
|
73
test/perf_spawn.cpp
Normal file
73
test/perf_spawn.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
using cbor::extract;
|
||||
using std::move;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::link;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::spawn_link;
|
||||
using thespian::unexpected;
|
||||
|
||||
namespace {
|
||||
|
||||
auto slave(const int slaves, int spawned) -> result {
|
||||
receive([slaves, spawned](auto, auto m) mutable {
|
||||
int n{0};
|
||||
if (!m(extract(n)))
|
||||
return unexpected(m);
|
||||
if (n) {
|
||||
++spawned;
|
||||
return thespian::to_result(
|
||||
spawn_link([=]() { return slave(slaves, spawned); }, "slave")
|
||||
.value()
|
||||
.send(n - 1));
|
||||
}
|
||||
if (spawned != slaves) {
|
||||
auto _ = env().proc("log").send("spawned", spawned, "slaves !=", slaves);
|
||||
return exit("failed");
|
||||
}
|
||||
if (env().is("verbose"))
|
||||
auto _ = env().proc("log").send("spawned", slaves, "slaves");
|
||||
return exit("done");
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto controller(const int slaves) -> result {
|
||||
auto ret = spawn_link([=]() { return slave(slaves, 1); }, "slave");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
auto ret2 = ret.value().send(slaves - 1);
|
||||
if (not ret2)
|
||||
return ret2;
|
||||
receive([](auto, auto) { return exit(); });
|
||||
return ok();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto perf_spawn(context &ctx, bool &result, env_t env_) -> ::result {
|
||||
auto slaves{env_.is("long_run") ? 1000000 : 1000};
|
||||
return to_result((ctx.spawn_link(
|
||||
[=]() {
|
||||
link(env().proc("log"));
|
||||
return controller(slaves);
|
||||
},
|
||||
[&](auto s) {
|
||||
if (s == "done") {
|
||||
result = true;
|
||||
} else {
|
||||
auto _ = env_.proc("log").send(s);
|
||||
}
|
||||
},
|
||||
"perf_spawn", move(env_))));
|
||||
}
|
120
test/spawn_exit.cpp
Normal file
120
test/spawn_exit.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
|
||||
#include <thespian/instance.hpp>
|
||||
|
||||
using std::move;
|
||||
using thespian::behaviour;
|
||||
using thespian::context;
|
||||
using thespian::env;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::exit_handler;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::self;
|
||||
using thespian::spawn;
|
||||
using thespian::spawn_link;
|
||||
using thespian::to_result;
|
||||
using thespian::trap;
|
||||
using thespian::unexpected;
|
||||
|
||||
namespace {
|
||||
|
||||
auto initE() {
|
||||
check(env().str("initAsays") == "yes");
|
||||
check(env().str("initBsays") == "");
|
||||
check(env().str("initCsays") == "no");
|
||||
check(env().num("intval") == 42);
|
||||
check(!env().proc("A").expired());
|
||||
link(env().proc("A"));
|
||||
auto ret = env().proc("A").send("shutdown");
|
||||
if (not ret)
|
||||
return ret;
|
||||
receive([](auto, auto) { return ok(); });
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto initD() {
|
||||
auto ret = spawn(initE, "E");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
receive([](auto, auto m) {
|
||||
if (m("die"))
|
||||
return exit("died");
|
||||
return unexpected(m);
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto initC() {
|
||||
env().str("initCsays") = "no";
|
||||
auto ret = spawn_link(initD, "D").value().send("die");
|
||||
if (not ret)
|
||||
return to_result(ret);
|
||||
trap(true);
|
||||
receive([](auto /*from*/, auto m) {
|
||||
if (m("exit", "died"))
|
||||
return exit();
|
||||
return unexpected(m);
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto initB() {
|
||||
env().str("initBsays") = "noyoudont";
|
||||
receive([](auto from, auto m) {
|
||||
if (m("shutdown")) {
|
||||
auto ret = from.send("done");
|
||||
if (not ret)
|
||||
return ret;
|
||||
return exit();
|
||||
}
|
||||
return unexpected(m);
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
auto initA() {
|
||||
link(env().proc("log"));
|
||||
env().str("initAsays") = "yes";
|
||||
env().num("intval") = 42;
|
||||
env().proc("A") = self();
|
||||
auto ret = spawn_link(initB, "B").value().send("shutdown");
|
||||
if (not ret)
|
||||
return ret;
|
||||
spawn_link(initC, "C").value();
|
||||
receive([i{0}](auto, auto m) mutable {
|
||||
if (m("shutdown") || m("done"))
|
||||
++i;
|
||||
else
|
||||
return unexpected(m);
|
||||
if (i == 2)
|
||||
return exit("done");
|
||||
return ok();
|
||||
});
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto spawn_exit(context &ctx, bool &result, env_t env) -> ::result {
|
||||
|
||||
behaviour b;
|
||||
check(!b);
|
||||
b = []() { return ok(); };
|
||||
check(!!b);
|
||||
b = behaviour{};
|
||||
check(!b);
|
||||
|
||||
return to_result(ctx.spawn_link(
|
||||
initA,
|
||||
[&](auto s) {
|
||||
if (s == "done")
|
||||
result = true;
|
||||
},
|
||||
"spawn_exit", move(env)));
|
||||
}
|
161
test/tests.cpp
Normal file
161
test/tests.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <thespian/backtrace.h>
|
||||
#include <thespian/context.hpp>
|
||||
#include <thespian/debug.hpp>
|
||||
#include <thespian/env.hpp>
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/timeout.hpp>
|
||||
#include <thespian/trace.hpp>
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using cbor::buffer;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::lock_guard;
|
||||
using std::map;
|
||||
using std::move;
|
||||
using std::mutex;
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
using thespian::env_t;
|
||||
using thespian::handle;
|
||||
using thespian::ok;
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto operator<<(ostream &s, const cbor::buffer::value_accessor::unvisitable_type
|
||||
& /*unused*/) -> ostream & {
|
||||
return s << "unvisitable_type";
|
||||
}
|
||||
|
||||
struct logger {
|
||||
thespian::timeout t{thespian::create_timeout(60s, cbor::array("timeout"))};
|
||||
const char *name;
|
||||
mutex &trace_m;
|
||||
bool verbose;
|
||||
|
||||
explicit logger(const char *name, mutex &trace_m, bool verbose)
|
||||
: name(name), trace_m(trace_m), verbose(verbose) {}
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
if (verbose) {
|
||||
std::lock_guard<mutex> lock(trace_m);
|
||||
cout << name << ": ";
|
||||
auto dump = [&](auto val) { cout << val << " "; };
|
||||
for (const auto val : m)
|
||||
val.visit(dump);
|
||||
cout << '\n';
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
static auto start(thespian::context &ctx, const char *name, mutex &trace_m,
|
||||
bool verbose, env_t env) -> handle {
|
||||
return ctx
|
||||
.spawn(
|
||||
[name, &trace_m, verbose]() {
|
||||
thespian::receive(
|
||||
[p{make_shared<logger>(name, trace_m, verbose)}](
|
||||
auto, auto m) { return p->receive(move(m)); });
|
||||
return ok();
|
||||
},
|
||||
string("logger_") + name, move(env))
|
||||
.value();
|
||||
}
|
||||
}; // namespace
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" auto runtestcase(const char *name) -> int {
|
||||
mutex trace_m;
|
||||
|
||||
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) // NOLINT
|
||||
abort();
|
||||
|
||||
auto gdb = getenv("JITDEBUG"); // NOLINT
|
||||
|
||||
if (gdb) {
|
||||
if (strcmp(gdb, "on") != 0)
|
||||
install_debugger();
|
||||
else if (strcmp(gdb, "backtrace") != 0)
|
||||
install_backtrace();
|
||||
}
|
||||
|
||||
auto ctx = thespian::context::create();
|
||||
|
||||
map<string, testcase *> tests;
|
||||
tests["cbor_match"] = cbor_match;
|
||||
tests["debug"] = debug;
|
||||
tests["endpoint_unx"] = endpoint_unx;
|
||||
tests["endpoint_tcp"] = endpoint_tcp;
|
||||
tests["hub_filter"] = hub_filter;
|
||||
tests["ip_tcp_client_server"] = ip_tcp_client_server;
|
||||
tests["ip_udp_echo"] = ip_udp_echo;
|
||||
tests["metronome_test"] = metronome_test;
|
||||
tests["perf_cbor"] = perf_cbor;
|
||||
tests["perf_hub"] = perf_hub;
|
||||
tests["perf_ring"] = perf_ring;
|
||||
tests["perf_spawn"] = perf_spawn;
|
||||
tests["spawn_exit"] = spawn_exit;
|
||||
tests["timeout_test"] = timeout_test;
|
||||
|
||||
env_t env{};
|
||||
env_t log_env{};
|
||||
auto trace = [&](const buffer &buf) {
|
||||
lock_guard<mutex> lock(trace_m);
|
||||
cout << buf.to_json() << '\n';
|
||||
};
|
||||
log_env.on_trace(trace);
|
||||
env.on_trace(trace);
|
||||
if (getenv("TRACE")) { // NOLINT
|
||||
thespian::debug::enable(*ctx);
|
||||
env.enable_all_channels();
|
||||
log_env.enable_all_channels();
|
||||
}
|
||||
if (getenv("TRACE_LIFETIME")) { // NOLINT
|
||||
thespian::debug::enable(*ctx);
|
||||
env.enable(thespian::channel::lifetime);
|
||||
log_env.enable(thespian::channel::lifetime);
|
||||
}
|
||||
if (getenv("TRACE_EXECUTE")) { // NOLINT
|
||||
env.enable(thespian::channel::execute);
|
||||
}
|
||||
if (getenv("TRACE_SEND")) { // NOLINT
|
||||
env.enable(thespian::channel::send);
|
||||
}
|
||||
if (getenv("TRACE_RECEIVE")) { // NOLINT
|
||||
env.enable(thespian::channel::receive);
|
||||
}
|
||||
|
||||
bool long_run = getenv("LONGRUN"); // NOLINT
|
||||
bool verbose = getenv("VERBOSE"); // NOLINT
|
||||
env.is("long_run") = long_run;
|
||||
env.is("verbose") = verbose;
|
||||
|
||||
if (verbose)
|
||||
cout << '\n';
|
||||
|
||||
env.proc("log") = logger::start(*ctx, name, trace_m, verbose, move(log_env));
|
||||
|
||||
auto test = tests.find(name);
|
||||
if (test == tests.end()) {
|
||||
cerr << "invalid testcase: " << name << '\n';
|
||||
return 1;
|
||||
}
|
||||
bool result{false};
|
||||
test->second(*ctx, result, move(env));
|
||||
ctx->run();
|
||||
return result ? 0 : 1;
|
||||
}
|
2
test/tests.h
Normal file
2
test/tests.h
Normal file
|
@ -0,0 +1,2 @@
|
|||
int runtestcase(const char *name); // NOLINT
|
||||
|
30
test/tests.hpp
Normal file
30
test/tests.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <thespian/context.hpp>
|
||||
#include <thespian/env.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
|
||||
constexpr auto check(bool expression) -> void {
|
||||
if (!expression)
|
||||
std::abort();
|
||||
}
|
||||
|
||||
using testcase = auto(thespian::context &ctx, bool &result, thespian::env_t env)
|
||||
-> thespian::result;
|
||||
|
||||
testcase cbor_match;
|
||||
testcase debug;
|
||||
testcase endpoint_tcp;
|
||||
testcase endpoint_unx;
|
||||
testcase hub_filter;
|
||||
testcase ip_tcp_client_server;
|
||||
testcase ip_udp_echo;
|
||||
testcase metronome_test;
|
||||
testcase perf_cbor;
|
||||
testcase perf_hub;
|
||||
testcase perf_ring;
|
||||
testcase perf_spawn;
|
||||
testcase spawn_exit;
|
||||
testcase timeout_test;
|
8
test/tests.zig
Normal file
8
test/tests.zig
Normal file
|
@ -0,0 +1,8 @@
|
|||
const std = @import("std");
|
||||
pub const cpp = @import("tests_cpp.zig");
|
||||
pub const cbor = @import("tests_cbor.zig");
|
||||
pub const thespian = @import("tests_thespian.zig");
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
435
test/tests_cbor.zig
Normal file
435
test/tests_cbor.zig
Normal file
|
@ -0,0 +1,435 @@
|
|||
const std = @import("std");
|
||||
const cbor_mod = @import("cbor");
|
||||
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const expectEqualDeep = std.testing.expectEqualDeep;
|
||||
const expectError = std.testing.expectError;
|
||||
|
||||
const fmt = cbor_mod.fmt;
|
||||
const toJson = cbor_mod.toJson;
|
||||
const toJsonPretty = cbor_mod.toJsonPretty;
|
||||
const fromJson = cbor_mod.fromJson;
|
||||
const decodeType = cbor_mod.decodeType;
|
||||
const matchInt = cbor_mod.matchInt;
|
||||
const matchIntValue = cbor_mod.matchIntValue;
|
||||
const matchValue = cbor_mod.matchValue;
|
||||
const match = cbor_mod.match;
|
||||
const isNull = cbor_mod.isNull;
|
||||
const writeArrayHeader = cbor_mod.writeArrayHeader;
|
||||
const writeMapHeader = cbor_mod.writeMapHeader;
|
||||
const writeValue = cbor_mod.writeValue;
|
||||
const extract = cbor_mod.extract;
|
||||
const extract_cbor = cbor_mod.extract_cbor;
|
||||
|
||||
const more = cbor_mod.more;
|
||||
const any = cbor_mod.any;
|
||||
const string = cbor_mod.string;
|
||||
const number = cbor_mod.number;
|
||||
const array = cbor_mod.array;
|
||||
const map = cbor_mod.map;
|
||||
|
||||
test "cbor simple" {
|
||||
var buf: [128]u8 = undefined;
|
||||
try expectEqualDeep(
|
||||
fmt(&buf, .{ "five", 5, "four", 4, .{ "three", 3 } }),
|
||||
&[_]u8{ 0x85, 0x64, 0x66, 0x69, 0x76, 0x65, 0x05, 0x64, 0x66, 0x6f, 0x75, 0x72, 0x04, 0x82, 0x65, 0x74, 0x68, 0x72, 0x65, 0x65, 0x03 },
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor exit message" {
|
||||
var buf: [128]u8 = undefined;
|
||||
try expectEqualDeep(
|
||||
fmt(&buf, .{ "exit", "normal" }),
|
||||
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x66, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C },
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor error.OutOfMemory" {
|
||||
var buf: [128]u8 = undefined;
|
||||
try expectEqualDeep(
|
||||
fmt(&buf, .{ "exit", error.OutOfMemory }),
|
||||
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x71, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x2E, 0x4F, 0x75, 0x74, 0x4F, 0x66, 0x4D, 0x65, 0x6D, 0x6F, 0x72, 0x79 },
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor error.OutOfMemory json" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var json_buf: [128]u8 = undefined;
|
||||
const cbor = fmt(&buf, .{ "exit", error.OutOfMemory });
|
||||
try expectEqualDeep(toJson(cbor, &json_buf),
|
||||
\\["exit","error.OutOfMemory"]
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.decodeType2" {
|
||||
var buf = [_]u8{ 0x20, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
const t = try decodeType(&iter);
|
||||
try expectEqual(t.major, 1);
|
||||
try expectEqual(t.minor, 0);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.decodeType3" {
|
||||
var buf = [_]u8{ 0x03, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
const t = try decodeType(&iter);
|
||||
try expectEqual(t.major, 0);
|
||||
try expectEqual(t.minor, 3);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 small" {
|
||||
var buf = [_]u8{ 1, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
try expect(try matchInt(i64, &iter, &val));
|
||||
try expectEqual(val, 1);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 1byte" {
|
||||
var buf = [_]u8{ 0x18, 0x1A, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
try expect(try matchInt(i64, &iter, &val));
|
||||
try expectEqual(val, 26);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 2byte" {
|
||||
var buf = [_]u8{ 0x19, 0x01, 0x07, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
try expect(try matchInt(i64, &iter, &val));
|
||||
try expectEqual(val, 263);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 8byte" {
|
||||
var buf = [_]u8{ 0x1B, 0x00, 0x00, 0xEF, 0x6F, 0xC1, 0x4A, 0x0A, 0x1F, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
try expect(try matchInt(i64, &iter, &val));
|
||||
try expectEqual(val, 263263263263263);
|
||||
try expectEqual(iter[0], 0xDF);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 error.CborIntegerTooLarge" {
|
||||
var buf = [_]u8{ 0x1B, 0xA9, 0x0A, 0xDE, 0x0D, 0x4E, 0x2B, 0x8A, 0x1F, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
const result = matchInt(i64, &iter, &val);
|
||||
try expectError(error.CborIntegerTooLarge, result);
|
||||
}
|
||||
|
||||
test "cbor.matchI64 error.CborTooShort" {
|
||||
var buf = [_]u8{ 0x19, 0x01 };
|
||||
var iter: []const u8 = &buf;
|
||||
var val: i64 = 0;
|
||||
const result = matchInt(i64, &iter, &val);
|
||||
try expectError(error.CborTooShort, result);
|
||||
}
|
||||
|
||||
test "cbor.matchI64Value" {
|
||||
var buf = [_]u8{ 7, 0xDF };
|
||||
var iter: []const u8 = &buf;
|
||||
try expect(try matchIntValue(i64, &iter, 7));
|
||||
}
|
||||
|
||||
test "cbor.matchValue(i64)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var iter = fmt(&buf, 7);
|
||||
try expect(try matchValue(&iter, 7));
|
||||
}
|
||||
|
||||
test "cbor.matchValue(i64) multi" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const iter = fmt(&buf, 7);
|
||||
const iter2 = fmt(buf[iter.len..], 8);
|
||||
var iter3 = buf[0 .. iter.len + iter2.len];
|
||||
try expect(try matchValue(&iter3, 7));
|
||||
try expect(try matchValue(&iter3, 8));
|
||||
}
|
||||
|
||||
test "cbor.match(.{i64...})" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ 5, 4, 3, 123456, 234567890 };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, v));
|
||||
}
|
||||
|
||||
test "cbor.match(.{i64... more})" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ 5, 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, .{ 5, 4, 3, more }));
|
||||
}
|
||||
|
||||
test "cbor.match(.{any, i64... more})" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "cbor", 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, .{ any, 4, 3, more }));
|
||||
}
|
||||
|
||||
test "cbor.match(.{types...})" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, .{ string, number, string, number, array }));
|
||||
|
||||
try expect(!try match(m, .{ string, number, string, number }));
|
||||
try expect(!try match(m, .{ number, string, number, array, any }));
|
||||
try expect(try match(m, .{ any, number, string, number, any }));
|
||||
|
||||
try expect(try match(m, .{ "five", number, string, 4, any }));
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, array }));
|
||||
try expect(try match(m, .{ "five", 5, more }));
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, array, more }));
|
||||
try expect(!try match(m, .{ "five", 5, "four", 4, array, any }));
|
||||
try expect(!try match(m, .{ "four", 5, "five", 4, array, any }));
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
|
||||
}
|
||||
|
||||
test "cbor.nulls" {
|
||||
var buf: [128]u8 = undefined;
|
||||
try expect(isNull(fmt(&buf, .{})));
|
||||
try expect(!isNull(fmt(&buf, .{1})));
|
||||
}
|
||||
|
||||
test "cbor.stream_writer" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var stream = std.io.fixedBufferStream(&buf);
|
||||
const writer = stream.writer();
|
||||
try writeArrayHeader(writer, 5);
|
||||
try writeValue(writer, "five");
|
||||
try writeValue(writer, 5);
|
||||
try writeValue(writer, "four");
|
||||
try writeValue(writer, 4);
|
||||
try writeArrayHeader(writer, 2);
|
||||
try writeValue(writer, "three");
|
||||
try writeValue(writer, 3);
|
||||
try expect(try match(stream.getWritten(), .{ "five", 5, "four", 4, .{ "three", 3 } }));
|
||||
}
|
||||
|
||||
test "cbor.stream_object_writer" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var stream = std.io.fixedBufferStream(&buf);
|
||||
const writer = stream.writer();
|
||||
try writeMapHeader(writer, 3);
|
||||
try writeValue(writer, "five");
|
||||
try writeValue(writer, 5);
|
||||
try writeValue(writer, "four");
|
||||
try writeValue(writer, 4);
|
||||
try writeValue(writer, "three");
|
||||
try writeValue(writer, 3);
|
||||
const obj = stream.getWritten();
|
||||
var json_buf: [128]u8 = undefined;
|
||||
const json = try toJson(obj, &json_buf);
|
||||
try expectEqualDeep(json,
|
||||
\\{"five":5,"four":4,"three":3}
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.match_bool" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const m = fmt(&buf, .{ false, true, 5, "five" });
|
||||
try expect(try match(m, .{ false, true, 5, "five" }));
|
||||
try expect(!try match(m, .{ true, false, 5, "five" }));
|
||||
}
|
||||
|
||||
test "cbor.extract_match" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var t: bool = false;
|
||||
const extractor = extract(&t);
|
||||
var iter = fmt(&buf, true);
|
||||
try expect(try extractor.extract(&iter));
|
||||
try expect(t);
|
||||
iter = fmt(&buf, false);
|
||||
try expect(try extractor.extract(&iter));
|
||||
try expect(!t);
|
||||
|
||||
const m = fmt(&buf, .{ false, true, 5, "five" });
|
||||
try expect(try match(m, .{ extract(&t), true, 5, "five" }));
|
||||
try expect(!t);
|
||||
try expect(try match(m, .{ false, extract(&t), 5, "five" }));
|
||||
try expect(t);
|
||||
|
||||
var i: i64 = undefined;
|
||||
try expect(try match(m, .{ false, true, extract(&i), "five" }));
|
||||
try expect(i == 5);
|
||||
|
||||
var u: u64 = undefined;
|
||||
try expect(try match(m, .{ false, true, extract(&u), "five" }));
|
||||
try expect(u == 5);
|
||||
|
||||
var s: []const u8 = undefined;
|
||||
try expect(try match(m, .{ false, true, 5, extract(&s) }));
|
||||
try expect(std.mem.eql(u8, "five", s));
|
||||
}
|
||||
|
||||
test "cbor.extract_cbor" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
|
||||
|
||||
var sub: []const u8 = undefined;
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&sub) }));
|
||||
try expect(try match(sub, .{ "three", 3 }));
|
||||
}
|
||||
|
||||
test "cbor.extract_nested" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
|
||||
var u: u64 = undefined;
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", extract(&u) } }));
|
||||
try expect(u == 3);
|
||||
}
|
||||
|
||||
test "cbor.match_map" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, map }));
|
||||
}
|
||||
|
||||
test "cbor.extract_map_cbor" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
var map_cbor: []const u8 = undefined;
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&map_cbor) }));
|
||||
var json_buf: [256]u8 = undefined;
|
||||
const json = try toJson(map_cbor, &json_buf);
|
||||
try expectEqualDeep(json,
|
||||
\\{"three":3}
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.extract_map" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
var obj = std.json.ObjectMap.init(std.testing.allocator);
|
||||
defer obj.deinit();
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
|
||||
try expect(obj.contains("three"));
|
||||
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
|
||||
}
|
||||
|
||||
test "cbor.extract_map_map" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ .three = 3, .child = .{ .two = 2, .one = .{ 1, 2, 3, true, false, null } } } };
|
||||
const m = fmt(&buf, v);
|
||||
var a = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer a.deinit();
|
||||
var obj = std.json.ObjectMap.init(a.allocator());
|
||||
defer obj.deinit();
|
||||
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
|
||||
try expect(obj.contains("three"));
|
||||
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
|
||||
var child = obj.get("child").?.object;
|
||||
try expectEqual(child.get("two"), std.json.Value{ .integer = 2 });
|
||||
|
||||
var json_buf: [256]u8 = undefined;
|
||||
const json = try toJson(m, &json_buf);
|
||||
try expectEqualDeep(json,
|
||||
\\["five",5,"four",4,{"three":3,"child":{"two":2,"one":[1,2,3,true,false,null]}}]
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.extract_value" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
var value = std.json.Value{ .null = {} };
|
||||
var value_int = std.json.Value{ .null = {} };
|
||||
try expect(try match(m, .{ "five", 5, extract(&value), extract(&value_int), any }));
|
||||
try expectEqualDeep(value.string, "four");
|
||||
try expectEqualDeep(value_int.integer, 4);
|
||||
}
|
||||
|
||||
test "cbor.match_more_nested" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const v = .{ .{124}, .{ "three", 3 } };
|
||||
const m = fmt(&buf, v);
|
||||
try expect(try match(m, .{ .{more}, .{more} }));
|
||||
}
|
||||
|
||||
test "cbor.extract_number_limits" {
|
||||
var buf: [128]u8 = undefined;
|
||||
const bigint: u64 = 18446744073709551615;
|
||||
var u: u64 = undefined;
|
||||
var i: i64 = undefined;
|
||||
const m = fmt(&buf, bigint);
|
||||
try expect(try match(m, extract(&u)));
|
||||
try expectError(error.CborIntegerTooLarge, match(m, extract(&i)));
|
||||
}
|
||||
|
||||
test "cbor.toJson" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var json_buf: [128]u8 = undefined;
|
||||
const a = fmt(&buf, .{ "string\r\n", "\tstring\t", "\r\nstring" });
|
||||
const json = try toJson(a, &json_buf);
|
||||
try expect(try match(a, .{ "string\r\n", "\tstring\t", "\r\nstring" }));
|
||||
try expectEqualDeep(json,
|
||||
\\["string\r\n","\tstring\t","\r\nstring"]
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.toJson_object" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var json_buf: [128]u8 = undefined;
|
||||
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
|
||||
const json = try toJson(a, &json_buf);
|
||||
try expectEqualDeep(json,
|
||||
\\{"five":5,"four":4,"three":3}
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.toJsonPretty_object" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var json_buf: [128]u8 = undefined;
|
||||
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
|
||||
const json = try toJsonPretty(a, &json_buf);
|
||||
try expectEqualDeep(json,
|
||||
\\{
|
||||
\\ "five": 5,
|
||||
\\ "four": 4,
|
||||
\\ "three": 3
|
||||
\\}
|
||||
);
|
||||
}
|
||||
|
||||
test "cbor.fromJson_small" {
|
||||
var cbor_buf: [128]u8 = undefined;
|
||||
const json_buf: []const u8 =
|
||||
\\[12345]
|
||||
;
|
||||
const cbor = try fromJson(json_buf, &cbor_buf);
|
||||
try expect(try match(cbor, .{12345}));
|
||||
}
|
||||
|
||||
test "cbor.fromJson" {
|
||||
var cbor_buf: [128]u8 = undefined;
|
||||
const json_buf: []const u8 =
|
||||
\\["string\r\n","\tstring\t","\r\nstring",12345]
|
||||
;
|
||||
const cbor = try fromJson(json_buf, &cbor_buf);
|
||||
try expect(try match(cbor, .{ "string\r\n", "\tstring\t", "\r\nstring", 12345 }));
|
||||
}
|
||||
|
||||
test "cbor.fromJson_object" {
|
||||
var cbor_buf: [128]u8 = undefined;
|
||||
const json_buf: []const u8 =
|
||||
\\{"five":5,"four":4,"three":3}
|
||||
;
|
||||
const cbor = try fromJson(json_buf, &cbor_buf);
|
||||
try expect(try match(cbor, map));
|
||||
}
|
65
test/tests_cpp.zig
Normal file
65
test/tests_cpp.zig
Normal file
|
@ -0,0 +1,65 @@
|
|||
const std = @import("std");
|
||||
const c = @cImport({
|
||||
@cInclude("tests.h");
|
||||
});
|
||||
|
||||
fn testcase(name: [*c]const u8) !void {
|
||||
const result = c.runtestcase(name);
|
||||
try std.testing.expectEqual(@as(c_int, 0), result);
|
||||
}
|
||||
|
||||
test "cbor_match" {
|
||||
try testcase("cbor_match");
|
||||
}
|
||||
|
||||
test "debug" {
|
||||
try testcase("debug");
|
||||
}
|
||||
|
||||
test "endpoint_unx" {
|
||||
try testcase("endpoint_unx");
|
||||
}
|
||||
|
||||
test "endpoint_tcp" {
|
||||
try testcase("endpoint_tcp");
|
||||
}
|
||||
|
||||
test "hub_filter" {
|
||||
try testcase("hub_filter");
|
||||
}
|
||||
|
||||
test "ip_tcp_client_server" {
|
||||
try testcase("ip_tcp_client_server");
|
||||
}
|
||||
|
||||
test "ip_udp_echo" {
|
||||
try testcase("ip_udp_echo");
|
||||
}
|
||||
|
||||
test "metronome_test" {
|
||||
try testcase("metronome_test");
|
||||
}
|
||||
|
||||
test "perf_cbor" {
|
||||
try testcase("perf_cbor");
|
||||
}
|
||||
|
||||
test "perf_hub" {
|
||||
try testcase("perf_hub");
|
||||
}
|
||||
|
||||
test "perf_ring" {
|
||||
try testcase("perf_ring");
|
||||
}
|
||||
|
||||
test "perf_spawn" {
|
||||
try testcase("perf_spawn");
|
||||
}
|
||||
|
||||
test "spawn_exit" {
|
||||
try testcase("spawn_exit");
|
||||
}
|
||||
|
||||
test "timeout_test" {
|
||||
try testcase("timeout_test");
|
||||
}
|
14
test/tests_thespian.zig
Normal file
14
test/tests_thespian.zig
Normal file
|
@ -0,0 +1,14 @@
|
|||
const std = @import("std");
|
||||
const thespian = @import("thespian");
|
||||
const cbor = @import("cbor");
|
||||
|
||||
pub const unexpected = thespian.unexpected;
|
||||
const message = thespian.message;
|
||||
const error_message = thespian.error_message;
|
||||
|
||||
test "thespian.unexpected" {
|
||||
var buf: [512]u8 = undefined;
|
||||
try std.testing.expectEqual(error.Exit, unexpected(message.fmt(.{"TEST"})));
|
||||
const json = try cbor.toJson(error_message(), &buf);
|
||||
try std.testing.expectEqualStrings("[\"exit\",\"UNEXPECTED_MESSAGE: [\\\"TEST\\\"]\"]", json);
|
||||
}
|
64
test/timeout_test.cpp
Normal file
64
test/timeout_test.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "tests.hpp"
|
||||
|
||||
#include "thespian/env.hpp"
|
||||
#include "thespian/handle.hpp"
|
||||
#include <thespian/instance.hpp>
|
||||
#include <thespian/timeout.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using cbor::array;
|
||||
using cbor::buffer;
|
||||
using std::move;
|
||||
using std::shared_ptr;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::system_clock;
|
||||
using thespian::context;
|
||||
using thespian::create_timeout;
|
||||
using thespian::env_t;
|
||||
using thespian::exit;
|
||||
using thespian::exit_handler;
|
||||
using thespian::ok;
|
||||
using thespian::receive;
|
||||
using thespian::result;
|
||||
using thespian::timeout;
|
||||
using thespian::unexpected;
|
||||
|
||||
using clk = std::chrono::system_clock;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
auto initA() -> result {
|
||||
thespian::link(thespian::env().proc("log"));
|
||||
struct state_t {
|
||||
system_clock::time_point start{clk::now()};
|
||||
timeout t{create_timeout(200ms, array("timeout"))};
|
||||
|
||||
auto receive(const buffer &m) {
|
||||
if (!m("timeout"))
|
||||
return unexpected(m);
|
||||
auto end = clk::now();
|
||||
auto us = duration_cast<microseconds>(end - start);
|
||||
return exit(us > 200ms ? "done" : "failed");
|
||||
}
|
||||
};
|
||||
shared_ptr<state_t> state{new state_t};
|
||||
receive([state](auto, auto m) mutable { return state->receive(m); });
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto timeout_test(context &ctx, bool &result, env_t env) -> ::result {
|
||||
return to_result(ctx.spawn_link(
|
||||
initA,
|
||||
[&](auto s) {
|
||||
if (s == "done")
|
||||
result = true;
|
||||
},
|
||||
"timeout", move(env)));
|
||||
}
|
48
zig
Executable file
48
zig
Executable file
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
ARCH=$(uname -m)
|
||||
|
||||
BASEDIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ZIGDIR=$BASEDIR/.cache/zig
|
||||
VERSION=$(< build.zig.version)
|
||||
ZIGVER="zig-linux-$ARCH-$VERSION"
|
||||
ZIG=$ZIGDIR/$ZIGVER/zig
|
||||
|
||||
if [ "$1" == "update" ] ; then
|
||||
curl -L --silent https://ziglang.org/download/index.json | jq -r '.master | .version' > build.zig.version
|
||||
NEWVERSION=$(< build.zig.version)
|
||||
|
||||
if [ "$VERSION" != "$NEWVERSION" ] ; then
|
||||
$0 cdb
|
||||
exec $0
|
||||
fi
|
||||
echo zig version $VERSION is up-to-date
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_zig() {
|
||||
(
|
||||
mkdir -p "$ZIGDIR"
|
||||
cd "$ZIGDIR"
|
||||
TARBALL="https://ziglang.org/builds/$ZIGVER.tar.xz"
|
||||
|
||||
if [ ! -d "$ZIGVER" ] ; then
|
||||
curl "$TARBALL" | tar -xJ
|
||||
fi
|
||||
)
|
||||
}
|
||||
get_zig
|
||||
|
||||
if [ "$1" == "cdb" ] ; then
|
||||
rm -rf zig-cache
|
||||
rm -rf zig-bin
|
||||
rm -rf .cache/cdb
|
||||
|
||||
$ZIG build
|
||||
|
||||
(echo \[ ; cat .cache/cdb/* ; echo {}\]) | perl -0777 -pe 's/,\n\{\}//igs' | jq . | grep -v 'no-default-config' > compile_commands.json
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec $ZIG "$@"
|
Loading…
Add table
Reference in a new issue