Preview of main at d0a501a, 2026-11-07

trystero is a header-only port of Trystero 0.26.0’s serverless WebRTC rooms for C++20, one of the libraries of webcpp. Peers find each other over public Nostr relays and exchange messages directly, over WebRTC data channels. The wire is Trystero’s: a room joined from C++ and the same room joined from Trystero’s JavaScript are one room.

This page teaches trystero section by section: what a room is, how a program joins one in the browser and natively, how its actions carry messages, where its memory comes from, what each backend asks of its platform, the wire it speaks, and what it guarantees. Every program it shows is one of the library’s examples, which the build compiles and links on its target. None is run by the build, since each needs the public relays or a browser, so the page shows no output of theirs. The reference lists every name the headers declare.

Overview

trystero lets programs meet in a room and exchange messages directly, peer to peer, over WebRTC data channels. There is no server of the application’s own: peers find each other by publishing short signed messages on public Nostr relays, exchange the WebRTC offer and answer through them, and from then on talk over the connection the browser or the network stack established.

It ports the protocol of Trystero 0.26.0, and keeps its wire byte for byte. A room joined from C++ and the same room joined from Trystero’s JavaScript are one room: the peers see each other, and an action sent from one side arrives on the other.

The library provides:

  • room, which joins a room of an application over Nostr relays, reports the peers that join and leave, measures round trips, and leaves with Trystero’s farewell;

  • action<T>, a named channel of messages: text, JSON (boost::json::value), binary, or any of the three, with optional JSON metadata, of any size, sent inline or in chunks with backpressure;

  • one immutable password per app, part of every room’s sealed signaling key and challenge handshake; separate passwords use separate apps and ids;

  • every asynchronous operation as an Asio initiating function, so it completes with a callback, boost::asio::use_awaitable, boost::asio::deferred or any other completion token;

  • an app per program in one application and password domain, with one identity and one connection per remote peer shared by its rooms;

  • a resource for the app and an optional resource for each room, which receive their allocations (Allocation);

  • a backend interface that separates the room from the platform, and its two implementations, for the browser and for native programs.

Two backends

<webcpp/trystero.hpp> is the rooms themselves, which build on both of trystero’s targets, native and emscripten, and include neither backend. Each backend is a header of its own:

Backend Target Built on

<webcpp/trystero/browser/backend.hpp>

emscripten

the page’s WebSocket, RTCPeerConnection, RTCDataChannel, setTimeout and crypto.subtle, through embind (The browser backend)

<webcpp/trystero/native/backend.hpp>

native

libdatachannel for WebRTC, Boost.Beast for the relays’ WebSockets, OpenSSL for TLS and AES-GCM, and Asio’s timers (The native backend)

Both run the same room, and both meet Trystero JS peers. Every name is in the namespace webcpp::trystero.

Requirements

  • A C++20 compiler.

  • Boost 1.92: Asio, Container (its pmr base class), Core, Hash2, JSON and System; and Beast for the native backend.

  • libsecp256k1 0.8.0, with its schnorrsig and extrakeys modules, for the BIP-340 signatures of Nostr events: every room needs it.

  • In the browser: Emscripten 6.0.11, the version webcpp pins, and a page served from a secure context (https, or http on localhost), where crypto.subtle exists.

  • Natively: libdatachannel 0.24.6 and OpenSSL 3, and exceptions (Exceptions).

trystero raises no exception of its own: an operation that can fail completes with an error code. The rooms, the protocol and the browser backend compile without exceptions; so do the native timer, sealer and WebSockets.

Quick start

The two smallest programs that join a room: one for the browser, one native. Both use an empty app password and join the room lobby of webcpp-trystero-quick-start, so they meet each other, and any Trystero JS page that joins the same room, joinRoom({appId: 'webcpp-trystero-quick-start'}, 'lobby'), meets them both. Running the examples builds and runs them.

In the browser

A browser program keeps five things alive for as long as its page: an io_context, the browser::event_loop that drives it from the page’s JavaScript events, the browser::backend that implements the platform over the browser’s APIs, the app that owns its identity and shared connections, and the room joined through that app. Actions are made from the room by name and payload type:

#include <webcpp/trystero.hpp>
#include <webcpp/trystero/browser/backend.hpp>
#include <webcpp/trystero/browser/src.hpp>  // once per program

#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>

#include <cstdio>
#include <iostream>
#include <optional>
#include <string>

namespace asio = boost::asio;
namespace trystero = webcpp::trystero;

/** The room the page joined, and its actions. */
struct lobby {
    trystero::room room;
    trystero::action<std::string> chat = room.make_action<std::string>("chat");
    trystero::action<std::string> echo = room.make_action<std::string>("echo");
};

/** What the page keeps alive: event loop, backend, app, and the room it joined. */
struct application {
    asio::io_context io;
    trystero::browser::event_loop loop{io};
    trystero::browser::backend backend{loop};
    trystero::app app{loop.get_executor(), backend, {.app_id = "webcpp-trystero-quick-start"}};
    std::optional<lobby> joined;

    /** Joins the lobby and makes its actions; null, with the reason printed, when the app refuses
     * (a room of that id joined already, or 64 rooms). */
    lobby* join() {
        auto result = app.join("lobby");
        if (!result) {
            const std::string reason = result.error().message();
            std::printf("not joined: %s\n", reason.c_str());
            return nullptr;
        }
        return &joined.emplace(std::move(*result));
    }
};

<webcpp/trystero/browser/src.hpp> is included in exactly one source file of the program: it defines the functions the page calls into the library with.

Handlers are set once the application exists. This program greets every peer that completes the handshake, prints every chat message, and starts a coroutine that answers the echo action:

int main() {
    auto* app = new application;  // a page's program lives as long as the tab
    auto* lobby = app->join();
    if (lobby == nullptr) {
        return 1;
    }

    lobby->room.on_peer_join([lobby](std::string_view peer) {
        trystero::send_options to_peer{.targets = {trystero::peer_id(peer)}, .metadata = {}};
        lobby->chat.async_send("hello", std::move(to_peer), [](boost::system::error_code ec) {
            if (ec) {
                std::cerr << "not delivered: " << ec.message() << '\n';
            }
        });
    });
    lobby->chat.on_message([](const std::string& text, const trystero::message_info& info) {
        std::cout << info.from << ": " << text << '\n';
    });
    asio::co_spawn(app->loop.get_executor(), echo_back(lobby->echo), asio::detached);
}

The coroutine awaits messages with async_receive and answers with async_send. boost::asio::as_tuple turns the error into a value, so leaving the room ends the loop instead of throwing:

/** Answers every message of the "echo" action, one at a time. */
asio::awaitable<void> echo_back(trystero::action<std::string>& echo) {
    for (;;) {
        auto [ec, text, info] = co_await echo.async_receive(asio::as_tuple(asio::use_awaitable));
        if (ec) {
            co_return;  // the room was left
        }
        co_await echo.async_send("echo: " + text,
                                 trystero::send_options{.targets = {info.from}, .metadata = {}},
                                 asio::use_awaitable);
    }
}

main returns at once. Nothing blocks in a browser: every JavaScript event that reaches the library runs its handlers and then whatever became ready on the io_context (The browser backend).

Natively

The same app and its joined room, natively, whole: a program that greets every peer that joins and prints what it receives on chat. A native program runs its io_context itself, since there is no event loop of a page to drive it, and the native backend posts what libdatachannel’s threads report to it:

#include <webcpp/trystero/app.hpp>
#include <webcpp/trystero/native/backend.hpp>
#include <webcpp/trystero/room.hpp>

#include <boost/asio/io_context.hpp>

#include <iostream>
#include <string>
#include <utility>

namespace trystero = webcpp::trystero;

int main() {
    boost::asio::io_context io;
    trystero::native::backend backend(io.get_executor());
    trystero::app app(io.get_executor(), backend, {.app_id = "webcpp-trystero-quick-start"});
    auto joined = app.join("lobby");
    if (!joined) {
        std::cerr << "not joined: " << joined.error().message() << '\n';
        return 1;
    }
    trystero::room room = std::move(*joined);
    auto chat = room.make_action<std::string>("chat");

    room.on_peer_join([&](std::string_view peer) {
        trystero::send_options to_peer{.targets = {trystero::peer_id(peer)}, .metadata = {}};
        chat.async_send("hello from a native peer", std::move(to_peer),
                        [](boost::system::error_code) {});
    });
    chat.on_message([](const std::string& text, const trystero::message_info& info) {
        // flushed: into a pipe too, since the program runs until it is stopped
        std::cout << info.from << ": " << text << '\n' << std::flush;
    });

    io.run();  // lint-run: the program's network I/O; libdatachannel's threads post to it
}

io.run() returns only when the program is stopped: the room keeps its relays and its peers for as long as it lives.

Building

trystero is header-only, and is developed inside the webcpp superproject, as a Boost library is developed inside Boost. Its native TLS and WebRTC dependencies are built from pinned sources. Only libsecp256k1 needs an external installation:

Library Needed by Found where

libsecp256k1 0.8.0, with its schnorrsig and extrakeys modules

every room: it signs the Nostr events peers meet through

-sSECP256K1_ROOT=<dir> natively, else .local/secp256k1-native; -sSECP256K1_EMSCRIPTEN_ROOT=<dir> for its emscripten build, else .local/secp256k1-emscripten

libdatachannel 0.24.6

the native backend

/webcpp/third_party//libdatachannel, built from third_party/libs/libdatachannel

OpenSSL 3.5.9

the native backend

/webcpp/third_party//openssl, shared by the Asio, Beast and Trystero build graphs

The libsecp256k1 -s option names a directory with the library’s include/ and lib/, on b2’s command line; the environment is never read, so that a variable a shell keeps from another day never replaces .local/ without a word. Without it, the library is looked for in the directory of .local/ that the table names, where webcpp’s continuous integration installs it, when that directory holds the library’s header, and else, natively outside Windows, in /usr/include or /usr/local/include, where a system install is legitimate. A relative -s is read from b2’s working directory. A -s that names a directory without the library’s header stops the build, naming it. A library found nowhere leaves trystero out: its tests and examples are skipped, the build says so once, naming the library and where it looked, and every other library builds; with webcpp-require-external=on, as the continuous integration builds, it stops the build instead. With MSVC, libsecp256k1 uses CMake’s libsecp256k1.lib, linked statically with SECP256K1_STATIC defined. trystero’s build.jam declares that external dependency so:

local secp256k1-root = [ webcpp.external trystero : libsecp256k1
    : SECP256K1_ROOT : secp256k1-native : secp256k1.h : native ] ;
local secp256k1-emscripten-root = [ webcpp.external trystero : libsecp256k1
    : SECP256K1_EMSCRIPTEN_ROOT : secp256k1-emscripten : secp256k1.h : emscripten ] ;
lib secp256k1 : : <name>secp256k1 <toolset>emscripten
    <search>$(secp256k1-emscripten-root)/lib
  : : <include>$(secp256k1-emscripten-root)/include ;
lib secp256k1 : : <name>libsecp256k1 <toolset>msvc
    <search>$(secp256k1-root)/lib
  : : <include>$(secp256k1-root)/include <define>SECP256K1_STATIC ;
lib secp256k1 : : <name>secp256k1 <search>$(secp256k1-root)/lib
  : : <include>$(secp256k1-root)/include ;
# Searched libraries are used only through usage requirements.
explicit secp256k1 ;

In a b2 project

A b2 project uses the rooms and the browser backend through /webcpp/trystero//trystero, which adds trystero’s headers, Boost’s, libsecp256k1 and Boost.JSON’s definitions, compiled once per variant, and, on emscripten, embind and BOOST_ASIO_DISABLE_THREADS, since the page has no threads:

alias trystero : : <conditional>@trystero-dependencies
  : : <include>include <library>/boost//headers <library>/webcpp//boost_json
  <library>/webcpp/config//config <library>secp256k1
  <toolset>emscripten:<define>BOOST_ASIO_DISABLE_THREADS
  <toolset>emscripten:<linkflags>-lembind ;

embind is linked as a flag, -lembind, rather than with b2’s own <embind>on: that feature is not free, and b2 ignores a feature that is not free among usage requirements, so a program that used it would not link.

A native program uses /webcpp/trystero//native, which adds libdatachannel and the same source-built OpenSSL used by Asio and Beast:

alias native : : <conditional>@native-dependencies
  : : <library>trystero <library>/webcpp/third_party//libdatachannel ;

The native backend uses worker threads, so each consumer declares <threading>multi. Its OpenSSL dependency is the matching threaded variant; a single-threaded native backend is rejected before compilation.

The native backend’s WebRTC also needs exceptions (Exceptions), and b2 lets no usage requirement override a build’s exception-handling=off, so each program and test that uses it declares <exception-handling>on itself, as the examples do:

webcpp.link native_quick_start : native_quick_start.cpp
  : <library>/webcpp/trystero//native <threading>multi <exception-handling>on : native ;

and as trystero’s own tests of the native backend’s connections do:

webcpp.run native_peer_connection : native_peer_connection_test.cpp
  : <library>/webcpp/trystero//native <threading>multi <exception-handling>on : native ;
webcpp.run native_room : native_room_test.cpp
  : <library>/webcpp/trystero//native <threading>multi <exception-handling>on : native ;

A browser program is linked for the web, with a memory that grows:

webcpp.link quick_start : quick_start.cpp
  : <linkflags>-sENVIRONMENT=web <linkflags>-sALLOW_MEMORY_GROWTH : emscripten ;

The emscripten toolset

The emscripten target is b2’s emscripten toolset, registered against Emscripten 6.0.11 from emsdk, as a user-config.jam declares it, where $(emsdk) is the emsdk’s directory and $(node) the node that runs a program:

local emsdk = /path/to/emsdk ;
local node = /path/to/node ;
using emscripten : : $(emsdk)/upstream/emscripten/em++ : <nodejs>$(node) ;

With it, libsecp256k1 installed and the pinned source submodules initialized, from the root of a webcpp checkout, these build the tests and the examples, natively and on emscripten:

b2 libs/trystero/test libs/trystero/example
b2 toolset=emscripten libs/trystero/test libs/trystero/example

Preparing the dependencies

libsecp256k1 is built twice: natively, and with Emscripten for the browser. Its small tables keep the browser’s download small:

git clone --branch v0.8.0 https://github.com/bitcoin-core/secp256k1.git
options="-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
  -DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON -DSECP256K1_ENABLE_MODULE_EXTRAKEYS=ON \
  -DSECP256K1_ECMULT_WINDOW_SIZE=4 -DSECP256K1_ECMULT_GEN_KB=2 \
  -DSECP256K1_BUILD_TESTS=OFF -DSECP256K1_BUILD_EXHAUSTIVE_TESTS=OFF \
  -DSECP256K1_BUILD_BENCHMARK=OFF"

cmake -S secp256k1 -B build-secp256k1-native $options \
  -DCMAKE_INSTALL_PREFIX=$PWD/webcpp/.local/secp256k1-native
cmake --build build-secp256k1-native && cmake --install build-secp256k1-native

emcmake cmake -S secp256k1 -B build-secp256k1-emscripten $options \
  -DCMAKE_INSTALL_PREFIX=$PWD/webcpp/.local/secp256k1-emscripten
cmake --build build-secp256k1-emscripten && cmake --install build-secp256k1-emscripten

where webcpp is the checkout of the superproject: each is installed in the directory of .local/ where the build looks for it.

Initialize the native dependencies from the superproject root:

git submodule update --init --recursive third_party/libs/openssl third_party/libs/libdatachannel

Install CMake 3.21 or newer, Perl and Make (native Windows Perl and nmake with MSVC). B2 builds OpenSSL, libdatachannel and its pinned nested libjuice, usrsctp and libsrtp sources for the configured compiler, standard library, runtime and build variant. Upstream sources are verified and copied to a disposable build directory; the checkout is never patched. The resulting headers and separate static archives stay in B2’s variant directory, and the final link contains exactly one OpenSSL archive pair.

libdatachannel’s own WebSocket, examples and tests are disabled; media transport remains enabled. System OpenSSL and installed libdatachannel binaries are never fallback providers. A combined Asio/Beast/Trystero application uses the same native <threading>multi configuration for all three libraries.

Running the examples

The three examples need the public relays or a browser, so the build links them and never runs them: native_quick_start.cpp, natively; quick_start.cpp, a .js and a .wasm to embed in a page; and the chat, a page whose room is C++ (A page and its program). The quick starts meet in the room lobby of the app webcpp-trystero-quick-start, over the public relays; the chat joins the app and the room its user types, the room lobby of the app webcpp-trystero-chat unless the user changes them, and meets the quick starts when the user types theirs.

The native quick start is built as a program of its own, and runs until it is stopped:

b2 libs/trystero/example//native_quick_start-program
`find bin/libs/trystero/example -name native_quick_start-program -type f`

The chat is a page and its program, which the target site copies into a directory of its own under the build directory, out of the tree, and prints. The page is served from localhost, where crypto.subtle exists:

b2 toolset=emscripten libs/trystero/example/chat//site
python3 -m http.server 8000 -d `find bin/libs/trystero/example/chat -name site -type d`

At http://localhost:8000, joining the app webcpp-trystero-quick-start, in place of the page’s webcpp-trystero-chat, and the room lobby brings the native quick start in as a peer within seconds: its greeting appears in the chat, and what the page sends is printed in its terminal. On another toolset, site stops the build, saying that the page is built on emscripten alone.

Rooms and actions

A room is what Trystero’s joinRoom returns, and an action what its makeAction returns: this section is the C++ form of both.

Joining

An app is this program in one application and password domain: one peer id, one immutable password, and one connection to each remote peer, which every room of the app it shares with that peer uses, as a Trystero JS page shares them. Constructing it takes the executor every handler and completion runs on, the backend, which must outlive it, and its configuration, app_config, moved. A room is joined with join, which returns a boost::system::result<room> to test, or an error: error::already_joined while a room of that id is joined in the app, and error::too_many_rooms when 64 rooms count, since a peer drops a connection that advertises more. Joining an id in its 99 ms farewell finishes that farewell first and joins afresh.

join and the app’s other methods run where its executor runs. An app and all its rooms use that executor; a multithreaded io_context needs a strand. Destroying or moving over the app handle releases it; its rooms keep the app’s state alive and continue. Two app objects, even with the same app id, are two peers. Rooms with different passwords use separate apps with distinct self ids. A room has no password override; changing the domain password requires a new app. No registry or identity is kept in static storage.

app_config member What it is

app_id

The application’s id, Trystero’s appId: rooms of different apps never meet.

self_id

One id for every room of the app; empty, 20 random characters drawn once. An id longer than 64 bytes is replaced and reported. This id links the app’s rooms: rooms that must not be linked to one program use an app each (What remains).

password

The immutable password shared by every room of this app, copied into the app’s resource. It contributes to each room’s sealed signaling key and challenge handshake. Empty, the app is passwordless. Different passwords require separate apps and distinct self ids; they share no connections, presence or tokens (What remains).

relay_urls

The Nostr relays. Empty, relay_redundancy of Trystero’s 29 defaults, shuffled with the app id so every peer picks the same ones. Their configuration is the app’s; sockets and the Nostr signing key remain per room.

relay_redundancy

The number of default relays to use, 5.

ice_servers

The STUN and TURN servers; empty, default_ice_servers(). An unusable URL, or a TURN server without a user name and credential, is dropped and reported to app::on_error with error::invalid_configuration.

idle_timeout

How long a connection with no rooms stays after a departure, 123,333 ms; 0 closes it immediately. A negative value becomes 0 and is reported. A connection whose last binding was kicked closes at once.

log

One sink for the app and its rooms, called with a category (app, nostr, signal, webrtc, room or action) and a sentence composed in the app’s resource. Empty, no text is composed.

memory

The app’s resource, std::pmr::get_default_resource() by default; it must be non-null (Allocation).

app_config is move-only because its log sink is a handler: pass it moved, or build it in place. Each room adds room_options:

room_options member What it is

handshake_timeout

How long a binding has to finish the handshake, 10 s.

max_receive_bytes

The most bytes the room reserves for incoming transfers across peers, 256 MiB.

memory

The room’s own resource; null uses the app’s resource.

Moving from room_config

room(executor, backend, room_config, room_id) and room_config are gone: a room is joined through an app.

Previous use New use

Standalone room construction

Construct app with the executor, backend and app_config, then call join with the room id and room_options; test the returned result.

room_config::{app_id, self_id, password, relay_urls, relay_redundancy, ice_servers}

app_config

room_config::log

app_config::log: one sink for the app and its rooms, with sentences in the app’s resource

room_config::{handshake_timeout, max_receive_bytes}

room_options

room_config::memory

app_config::memory, with room_options::memory for a room’s own resource

Configuration reports from room::on_error, with an empty peer id

app::on_error; room::on_error reports problems with peers

Two rooms with separate peer ids to meet Trystero JS in both

Two calls to join on one app

Two peers in one process

Two app objects

Rooms with different passwords, or rooms that must not be linked to one program

Separate apps with distinct self ids; set each password in its app_config

Changing a room’s password by leaving and joining again

Construct a new app with the new domain password and a distinct self id

Peers

on_peer_join and on_peer_leave set what is called with each peer whose handshake completed, and with each joined peer that leaves; peers() lists the joined peers. on_error sets what is called with each problem with one peer that the room handled itself, as Trystero’s onPeerError: a peer with signals under another password, a malformed room proof, a handshake that timed out, a connection the backend refused. It is called with an empty peer id for a count of signals that did not open with the room key (Bounded signaling). Each handler replaces the one set before, and every view it receives is valid for the call.

async_ping measures the round trip to a joined peer, from sending @_ping to receiving its @_pong.

async_leave says goodbye to every peer, Trystero’s @_leave, gives the farewell 99 ms, as Trystero waits, then detaches its bindings and closes its relay sockets, and every pending operation completes with error::room_left. Destroying a room leaves at once, without the 99 ms, and so does assigning another room over a joined one: every peer is told, and every pending operation completes with error::room_left, even while an action of it lives.

A kick or a peer’s @_leave ends only that room’s binding: the app’s other rooms keep using the connection. When it stays live, rebinding waits one step after the end, initially the larger of handshake_timeout and 10 s, plus 1 s, doubling after each further end to at most 5 minutes. A completed handshake or the connection’s end resets the count. After its own kick the lower id refuses bindings until that time and keeps frames; the higher id drops frames until it sends presence, but accepts the lower id’s presence. A binding also requires the peer to have advertised the token itself. A peer on a protected app’s shared connection already authenticated with that app’s password through the initial sealed negotiation. A peer that knows it can still send a wrong room proof: only the lower id reports that proof as wrong_password; the higher id answers and sees a departure. Malformed challenge data is rejected by its receiver too. This room handshake retains Trystero JS 0.26.0’s one-way wire behavior.

Actions

An action is a named channel of messages, wire compatible with Trystero’s makeAction. Its name has 1 to 32 UTF-8 bytes and no NUL; with another, every send fails with error::invalid_action_name. Its type says how messages travel and how they are received:

Type Trystero format Received as

std::string

text

a copy of the bytes

std::pmr::string

text

a copy of the bytes, in the room’s resource

std::string_view

text

a view of the frame it arrived in, valid during the handler

boost::json::value

JSON

parsed, in the room’s JSON storage

trystero::bytes

binary

a copy of the bytes

trystero::pmr_bytes

binary

the transfer’s buffer, moved when resources match, otherwise copied once into the room’s resource

std::span<const std::uint8_t>

binary

a view of the frame, valid during the handler

trystero::payload

any

a std::variant of std::string, boost::json::value and bytes, by format

A message of another format than the action’s type is not delivered to it. Several actions of different types may share a name: one receives as views, another sends by moving strings, as the chat does (A page and its program).

Messages arrive at the handler on_message sets, with a message_info: the sender’s id and the metadata it passed. Without a handler they queue, up to 1024 per action, the oldest dropped first, for async_receive, which needs an owning type: a view would outlive the message. Frames of an action that arrived before it was made are kept, 64 per peer for 120 s, and delivered to it once it is.

async_send sends to send_options::targets, or to every joined peer, with optional JSON metadata. A payload that fits one 16 KiB frame with its header and metadata travels inline; a larger one is offered, accepted by the receiver within 120 s, and sent in chunks of at most 16,370 bytes. A frame or a chunk is handed to the data channel only while the channel buffers at most 64 KiB, waiting at most 10 s for it to drain. async_send completes once each target’s local data channel has taken the inline frame, or the last chunk of the transfer the receiver accepted, with the first failure if any, as Trystero’s send resolves: nothing confirms that the receiver has the message. A receiver takes one transfer per peer at a time, accepts it only for an action with a receiver, and reserves at most max_receive_bytes across peers.

Completion tokens and ownership

Every asynchronous operation is an Asio initiating function. With a callback it calls it on the room’s executor; with boost::asio::use_awaitable it resumes a coroutine; with boost::asio::deferred it returns an operation started later.

Because a deferred operation starts after the call, the arguments of an asynchronous operation are owned: async_send takes its value by value, moved when the type owns its storage, copied once at the call into the room’s resource when it is a view, and serialized at once when it is a boost::json::value; send_options owns its strings; and async_ping copies the peer id at the call. Everything else that only reads text takes a std::string_view.

Errors

Operations complete with a boost::system::error_code in the category webcpp.trystero, of the enumeration error:

Value Meaning

room_left

the room was left while the operation was pending

no_such_peer

the target is not a joined peer

peer_disconnected

the peer went away before the operation finished

invalid_action_name

an action’s name is not 1 to 32 UTF-8 bytes without NUL

metadata_too_large

the metadata does not fit one frame

transfer_rejected

the receiver refused the transfer, or a message had another format than the awaited type

transfer_timed_out

the receiver neither accepted nor refused within 120 s

channel_stalled

the data channel stopped draining for 10 s

wrong_password

a peer’s signals or handshake used another password, or a handshake message had the wrong shape

handshake_timed_out

a connected peer did not finish the handshake in time

handshake_failed

reserved: nothing reports it today

backend_failure

the browser or the native stack refused an operation

invalid_configuration

the app dropped an unusable configuration value and continues without it

already_joined

a room of that id is already joined in the app

too_many_rooms

the app already holds 64 rooms, including farewells

Allocation

A webcpp library lets its user choose where its allocations come from. trystero does it through std::pmr, header-only, with an app resource and an optional resource for each room.

The app’s resource and the room’s

  • app_config::memory, std::pmr::get_default_resource() by default, receives the app’s state, configuration copy including its password, self id, registry, shared connections, advertised tokens, kept frames, drain order and presence frames. Peer connections, data channels and received frames use it even while a room negotiates them. The logger, sink and every sentence of the app and its rooms use this resource too. It must be non-null, checked by BOOST_ASSERT at app construction.

  • room_options::memory, the app’s resource when null, receives the room’s state, bindings and their drain waiters, negotiation records, rebinding counts and backoffs, sealed texts, relays, actions, queues, outgoing frames, transfers, JSON storage and handlers. The signing context is made there with secp256k1_context_preallocated_create.

  • Adoption replaces negotiation handlers with the app’s; ending a negotiation clears the room’s handlers. Detachment fails and frees a binding’s drain waiters and removes its registrations before returning. After a room ends, the app retains no allocation in that room’s resource.

  • A received frame starts in the app’s resource. A pmr_bytes payload moves from its message buffer when that buffer and the room use the same resource; otherwise it is copied once into the room’s resource. A chunked transfer assembles its buffer in the room’s resource. Received std::pmr::string text is always copied once into the room’s resource. JSON is parsed in the room’s own JSON storage; the app parses no JSON.

All resource access runs on the app’s executor. A resource need not be thread-safe when that executor runs on one thread; several io_context threads require a strand.

The allocation test builds every part of app_config in its counted resource before the counting window, including strings, relay URLs, ICE servers and log sink; app_config_in and options_in name both resources:

/** An app's configuration, every part of it in `memory`, made before the window. */
trystero::app_config app_config_in(counting_resource& memory, std::string_view self_id,
                                   std::string_view relay, std::size_t& lines) {
    return trystero::app_config{
        .app_id = std::pmr::string("allocation", &memory),
        .self_id = std::pmr::string(self_id, &memory),
        .password = std::pmr::string("a password", &memory),
        .relay_urls =
            std::pmr::vector<std::pmr::string>({std::pmr::string(relay, &memory)}, &memory),
        .ice_servers = std::pmr::vector<trystero::backend::ice_server>(&memory),
        .log =
            {
                std::allocator_arg,
                std::pmr::polymorphic_allocator<>(&memory),
                [&lines](std::string_view /*category*/, std::string_view /*text*/) { ++lines; },
            },
        .memory = &memory,
    };
}

/** A room's options, naming `memory` explicitly. */
trystero::room_options options_in(counting_resource& memory) {
    return trystero::room_options{.memory = &memory};
}

How long the resource lives

The app’s resource must outlive its state: the app handle, every room of it, retained values allocated there and every pending operation. A room’s own resource must outlive that room, its retained std::pmr::string, pmr_bytes and boost::json::value values, and its pending operations. Destroying a handle closes its work but queued completions can still free memory. Declare both app_config::memory and room_options::memory resources before the io_context, then construct the backend, app and rooms. Drain or destroy the io_context before destroying either resource. In the browser, keep them until all pending promises settle: the page’s event_loop registrations retain their handlers, so in practice resources live for the page’s life.

No check in debug builds catches the misuse: trystero holds a raw pointer and cannot see the resource end, and the use comes after the room is gone, from an operation of Asio’s or of the browser’s. A resource that asserts nothing is out when it is destroyed, as the test’s counting resources do, is the check, and it is the user’s.

A room allocates and frees on every event, a frame, a parsed text, a post or a log line, so a resource that reuses what is freed, a pool such as std::pmr::unsynchronized_pool_resource, suits a long-lived room; a std::pmr::monotonic_buffer_resource grows with every event and suits a short-lived one, or a test.

Completion handlers

A completion handler is the user’s, and is allocated as its own associated allocator says: the room never binds its allocator over it. boost::asio::bind_allocator gives one the room’s resource. The allocation test makes the allocator of a resource:

const auto in = [](counting_resource& memory) {
    return std::pmr::polymorphic_allocator<>(&memory);
};

and binds it over the completions of a transfer and of a ping, so that they too are allocated in the sender’s resource:

a.make_action<trystero::pmr_bytes>("file").async_send(
    trystero::pmr_bytes(100'000, 7, &a_memory),
    boost::asio::bind_allocator(in(a_memory), [](boost::system::error_code /*ec*/) {}));
a.async_ping(z.self_id(),
             boost::asio::bind_allocator(
                 in(a_memory),
                 [&pings](boost::system::error_code ec,
                          std::chrono::milliseconds /*rtt*/) { pings += ec ? 0 : 1; }));

When the room erases a completion handler’s type (an any_completion_handler), it records whether the handler’s associated executor is the room’s, which only the concrete type tells without allocating; such a completion runs from an operation the room posts to its executor, allocated with the handler’s allocator. One bound to another executor is handed to that executor by Asio, whose type-erased executor wraps it with std::allocator.

Containers and shared objects

Every container trystero declares is a std::pmr one: std::pmr::string (and peer_id), std::pmr::vector (and backend::bytes, protocol::bytes and pmr_bytes), std::pmr::deque, std::pmr::map, std::pmr::set, std::pmr::unordered_map and std::pmr::unordered_set, built over their owner’s resource; app_config, room_options, send_options, message_info and backend::ice_server hold them too. Two types stay the user’s choice of std::allocator: trystero::bytes, a std::vector<std::uint8_t>, and trystero::payload, a variant of std::string, boost::json::value and bytes. A received bytes, or the binary of a payload, is a copy of the frame, which starts in the app’s resource.

A struct that holds std::pmr containers takes the room’s std::pmr::polymorphic_allocator<> in its constructor, and a container of such structs is given it when it makes one, with try_emplace(key, allocator). A std::pmr container is never copied without an allocator: its copy constructor takes the default resource, so a copy names the resource, std::pmr::string(text, allocator). Moving keeps the resource.

Shared objects are made with std::allocate_shared over std::pmr::polymorphic_allocator<>, never with std::make_shared, std::make_unique or new; a timer is a std::shared_ptr for that reason.

Handlers

std::function takes no allocator since C++17, so trystero holds every handler it keeps in handler<R(Args...)>, a move-only callable that allocates through a std::pmr::polymorphic_allocator<>. Constructed with handler(std::allocator_arg, allocator, f), it holds f inline when f fits in three pointers and is nothrow-movable, and in storage of the allocator otherwise. It replaces every std::function of the backend interface (the handlers of its timer, WebSocket, data channel, peer connection and sealer), of the room and of the log’s sink. Constructed from f alone it uses the default resource; the app moves app_config::log into its resource with the allocator-extended move. A move assignment carries the source’s allocator with its target.

Asio, the backends, Boost.JSON and the protocol

Asio. Every post, dispatch, timer wait and any_completion_handler the room makes carries boost::asio::bind_allocator over its resource. A post through boost::asio::any_io_executor is wrapped by Asio in a function object allocated with std::allocator, from Asio’s per-thread recycling cache, which calls std::aligned_alloc on a miss (measured: two per post, in every round of five posts, natively and on emscripten). So the room posts through the executor of an io_context when its executor is one, where Asio allocates the operation as the handler’s allocator says; on another executor, as the browser’s event_loop executor, the wrapper stays Asio’s.

The backends. Each method of backend::factory that makes an object takes a std::pmr::memory_resource* as its last parameter. Peer connections, data channels and received frames use the app’s resource; relay sockets, sealers and room timers use the room’s. Objects and their handlers allocate through that supplied resource.

Boost.JSON allocates through json_resource, a boost::container::pmr::memory_resource that forwards to a std::pmr::memory_resource, header-only: the global resources of boost::container::pmr are compiled, and webcpp’s CI installs Boost’s headers alone. The room’s JSON storage is made once, when the room is constructed, since a value a handler keeps may outlive the room. Text is parsed by a boost::json::parser over that storage, never by boost::json::parse, whose temporaries beyond a stack buffer come from the default resource, and JSON is written by a boost::json::serializer whose stack is the resource.

The protocol (protocol/) takes a std::pmr::memory_resource* as the last parameter of every function that returns what it allocates, with the default resource as its default, and returns std::pmr types. protocol::nostr::verify uses secp256k1_context_static and allocates nothing.

What stays outside the resource

  • libdatachannel’s and OpenSSL’s own allocations, with the std::string and rtc::Description libdatachannel’s API takes and returns;

  • what emscripten::val holds, in the JavaScript heap;

  • the counter of the room’s JSON storage, which Boost.JSON allocates with operator new, once per room;

  • Asio’s own once-per-context allocations: its services and the scheduler’s first operation;

  • the wrapper Asio puts around a post through an executor that is not the executor of an io_context;

  • the native backend’s posts from libdatachannel’s threads, which carry Asio’s default allocator, since the resource is touched only on the room’s executor;

  • Boost.Beast’s and Asio’s state inside the native WebSocket: its stream, its resolver’s results, its coroutines’ frames;

  • the payload types std::string, bytes and payload, with std::allocator, the user’s choice;

  • on Emscripten, the C library’s select(), which Asio’s reactor calls on every wait of an io_context with a timer or a socket, and which callocs an array on every call.

The browser’s event_loop and the test’s memory relays are shared by every room of a page or of a test: they allocate the entries of their registrations and their relays through the resource they are constructed with, and a registered callback stays in its own resource, a room’s for a room’s object.

The proof

test/allocation_test.cpp, on both targets, counts the global operator new, which it replaces, and the C library’s allocation functions, where the platform lets a program see them: glibc’s are replaced and forward to __libc_*, Emscripten’s dlmalloc has weak definitions the test replaces, and macOS reports each to malloc_logger; MSVC’s are not counted, nor any under AddressSanitizer, whose own allocator reports to none of these. Only the thread main runs on is counted.

  • Two rooms over the memory relays exchange a payload, a std::pmr::string, a pmr_bytes and a boost::json::value in turn, while both counts see none and the counting resources of the two configurations see the rooms’.

  • Two rooms with a password, a log sink and the relays’ state in a resource of its own are made, join, exchange a payload that travels in chunks, measure a round trip, one says goodbye, both are destroyed and the io_context is drained. From the first construction to the drain, operator new sees exactly the two rooms’ JSON storage counters and the C library nothing. Once the rooms are destroyed their resources still have memory out, the operations they left pending, and none once the io_context has drained.

  • Two apps with two rooms each exchange binary payloads, one room using the app’s resource and one its own. Size-specific counters prove one payload copy into the distinct room resource, and no extra copy when resources match. From construction through the drain the global counter sees only four JSON storage counters, and every resource ends with nothing out.

The counting resources also count a return of memory they never gave, a std::pmr object freed through the wrong resource, which must be none. Asio’s once-per-context allocations are made before anything is counted; without them the count is 8, not 2. On Emscripten the test also replaces select() with one that keeps its array on the stack, so that the count sees the rooms’ allocations rather than the C library’s wait. The test is declared in test/Jamfile:

webcpp.run allocation : allocation_test.cpp : <toolset>emscripten:<linkflags>-sALLOW_MEMORY_GROWTH ;

The browser backend

In the browser, browser::backend implements the backend interface over the page’s own WebSocket, RTCPeerConnection, RTCDataChannel, setTimeout and crypto.subtle, through embind’s emscripten::val, and browser::event_loop runs the room’s executor from the page’s events. One of each serves every app and room of the page. Apps with different passwords use distinct self ids and share no connection or presence.

Driving Asio from the page’s event loop

In a browser nothing may block, and the page’s JavaScript event loop is the only source of events. An io_context never runs on its own there: browser::event_loop drives it without ever calling run().

  • Every JavaScript event that reaches the library, a socket’s message, a settled promise, an ICE candidate or a timer, is dispatched to its handler, and is followed by io_context::poll(), which runs everything that became ready.

  • Work given to the loop’s executor, browser::executor, while no JavaScript event is being handled posts to the io_context and schedules one poll() in a microtask.

  • The loop holds a work guard on the io_context, so a poll() never leaves it stopped.

So nothing runs while nothing happens, and a handler runs as soon as its event does. Two alternatives were rejected: poll() from emscripten_set_main_loop costs 60 wake-ups a second and stops in hidden tabs, where requestAnimationFrame does; poll() on a fixed interval costs wake-ups and up to an interval of latency. The backend’s timers are setTimeout, so their expiry is a JavaScript event like any other.

How JavaScript reaches C++

The page calls one function, trystero_dispatch, which <webcpp/trystero/browser/src.hpp> exports. A JavaScript handler is that function bound to the event loop, a registration and a slot. An object registers a callback with the loop and removes it when it is destroyed, so a late event for a destroyed object finds nothing; a destroyed loop marks every handler it made as dead.

Text that crosses into JavaScript is decoded straight from the WebAssembly memory a view points into, by a TextDecoder the loop keeps, and text that comes back is encoded by a TextEncoder straight into a string of the object’s resource: no std::string is made to carry either.

No JavaScript exception enters WebAssembly

The browser backend makes every call into JavaScript that can throw, a constructor, JSON.parse, a send, a WebRTC or Web Crypto method, through one function defined once in browser/src.hpp with EM_JS, which runs it inside try and returns a value or the exception as a value; the C++ turns that into the error its interface has. Every event of the page enters C++ through a function of the same file that catches what still escapes, puts WebAssembly’s stack pointer and the loop’s depth back as they were before the call, reports it and lets the loop poll again. With exceptions, browser::dispatch catches a C++ exception first, which frees it: one that reached JavaScript would keep its object for good. So a program that uses the browser backend may be built with exceptions or without them, and a peer’s malformed message never stops the page’s loop.

What a page bounds

  • A data channel message that is not an ArrayBuffer, as a text message a peer sends, or that is larger than any room frame (protocol::max_room_frame_bytes), is dropped before it is read: neither can be a frame.

  • A relay’s text longer than 1 MiB (protocol::max_relay_message_bytes) is not copied into WebAssembly: the socket closes, and reports the close.

  • A text that would take the socket’s bufferedAmount beyond 4 MiB (protocol::max_queued_relay_bytes), as when a relay stops reading, drops the socket, which reports one close at once. A page’s WebSocket cannot ping, so the browser has no idle timeout, which the native backend has (The relays).

  • A page makes at most browser::backend::max_peer_connections (256) peer connections across all apps and rooms it serves, counted from their construction until the garbage collector frees them, as Chromium counts them against its own 500 (measured with headless Chrome: the 501st throws "Cannot create so many PeerConnections", and a closed one counts until a collection). Beyond, the rooms report each negotiation they cannot start, and the rest of Chromium’s count stays free for what the page makes without a room.

Peer connections, data channels and received frames use the app’s resource; relay sockets and sealed signaling texts use the room’s. Presence or reuse binds another room without making another peer connection.

What a page needs

  • A secure context: https, or http on localhost or 127.0.0.1. Elsewhere crypto.subtle, which seals the signaling, does not exist, and every sealing operation fails with error::backend_failure.

  • No threads: Emscripten’s threads need a cross-origin isolated page, so trystero uses none. /webcpp/trystero//trystero defines BOOST_ASIO_DISABLE_THREADS on emscripten, and the loop drives Asio on the page’s one thread.

  • A program that lives as long as the page: the event loop must outlive every room and object it serves.

  • Boost.JSON’s definitions, which a program links from /webcpp//boost_json, which /webcpp/trystero//trystero brings and which builds on emscripten as on every target.

A page and its program

The chat example is a page whose room is C++: the program joins and sends, and the page only draws. It keeps the loop, the backend and the room for the page’s life, and makes the action chat twice: once as std::string_view, to receive a message as a view of its frame, and once as std::string, to send by moving the text:

struct chat {
    boost::asio::io_context io;
    trystero::browser::event_loop loop{io};
    trystero::browser::backend backend{loop};
    std::unique_ptr<trystero::app> app;  // made at the first join, with that join's app id
    std::unique_ptr<trystero::room> room;
    // The action "chat" twice: received as views of the frame, and sent by moving the text.
    std::unique_ptr<trystero::action<std::string_view>> messages;
    std::unique_ptr<trystero::action<std::string>> outgoing;
};

The page calls join with the app and the room its user typed, and the program joins and sets its handlers; a received message goes from its frame straight into a JavaScript string:

void join_room(const std::string& app_id, const std::string& room_id) {
    auto& c = the_chat();
    if (c.room) {
        return;
    }
    if (!c.app) {
        trystero::app_config config;
        config.app_id = app_id;
        config.log = [](std::string_view category, std::string_view text) {
            ui("log", category, text);
        };
        c.app =
            std::make_unique<trystero::app>(c.loop.get_executor(), c.backend, std::move(config));
    }
    auto joined = c.app->join(room_id);
    if (!joined) {
        ui("log", std::string_view("chat"),
           std::string_view("not joined: " + joined.error().message()));
        return;
    }
    c.room = std::make_unique<trystero::room>(std::move(*joined));
    c.messages = std::make_unique<trystero::action<std::string_view>>(
        c.room->make_action<std::string_view>("chat"));
    c.outgoing =
        std::make_unique<trystero::action<std::string>>(c.room->make_action<std::string>("chat"));
    c.room->on_peer_join([](std::string_view peer) { ui("peerJoined", peer); });
    c.room->on_peer_leave([](std::string_view peer) { ui("peerLeft", peer); });
    // A view of the received frame: the text goes from the frame straight into a JavaScript string.
    c.messages->on_message([](std::string_view text, const trystero::message_info& info) {
        ui("message", std::string_view(info.from), text, false);
    });
    ui("joined", c.room->self_id(), std::string_view(room_id));
}

send shows the message at once, and reports a failure in the page’s log:

void send_message(std::string text) {
    auto& c = the_chat();
    if (!c.room || text.empty()) {
        return;
    }
    ui("message", c.room->self_id(), std::string_view(text), true);
    c.outgoing->async_send(std::move(text), [](boost::system::error_code ec) {
        if (ec) {
            ui("log", std::string_view("chat"), std::string_view("not delivered: " + ec.message()));
        }
    });
}

The two functions are what the program exports to the page, through embind:

EMSCRIPTEN_BINDINGS(chat_example) {
    emscripten::function("join", &join_room);
    emscripten::function("send", &send_message);
}

Any Trystero JS page, or any other page of the chat, that joins the same app and room is in the same chat (Running the examples).

The native backend

Natively, native::backend implements the backend interface over libdatachannel for WebRTC, Boost.Beast for the relays’ WebSockets, OpenSSL for TLS and AES-GCM, and Asio’s timers. A native room is the same room as a browser’s or Trystero JS’s: they meet over the same relays and exchange the same frames. One backend serves every app and room of the program, with separate app objects and distinct self ids for different passwords, beside the executor it runs on, and a native program runs that executor’s io_context itself, blocking on it or polling it (Natively).

Exceptions

trystero raises no exception of its own, but libdatachannel reports errors by throwing, and the native backend’s data channel and peer connection catch what it throws on refused input and complete with error::backend_failure. So they need exceptions: without them, or with WEBCPP_NO_EXCEPTIONS or WEBCPP_TRYSTERO_NO_EXCEPTIONS defined, native::data_channel, native::peer_connection and native::backend, which makes their objects, do not exist, and code that uses them does not compile. The native timer, sealer and WebSockets, and the rest of trystero, are unchanged.

<webcpp/trystero/config.hpp>, which every header includes first, defines WEBCPP_TRYSTERO_NO_EXCEPTIONS when webcpp has no exceptions (WEBCPP_HAS_EXCEPTIONS is not defined: the compiler has none, or the program defined WEBCPP_NO_EXCEPTIONS); a developer may define it for trystero alone in a build that has them. Each program and test that uses the native backend’s WebRTC declares <exception-handling>on itself, whatever the build asks (In a b2 project). One function of the backend throws: its constructor, a boost::system::system_error when OpenSSL cannot make the TLS context or load the system’s certificate authorities.

Threads

libdatachannel delivers its events on threads of its own. The backend never runs a handler there: each event copies what it carries and is posted to the room’s executor, through a weak reference, so an event that arrives after its object is gone does nothing. Every handler of the room and every completion runs on the executor, as in the browser. An application that runs the io_context on several threads gives the app a strand, shared by all its rooms.

A received data channel message is copied once, out of libdatachannel’s std::vector<std::byte> into the app’s resource, on the executor. A view handler reads it there. A binary payload moves when its message buffer and the room use the same resource, and is copied once otherwise; chunked transfers assemble their buffer in the room’s resource. A text message, as a peer can send, or a binary one larger than any room frame, is dropped on libdatachannel’s thread: neither can be a frame.

Valid binary frames that arrive before the message handler is installed wait in a FIFO in the app’s resource. The queue holds at most 64 frames for at most 10 seconds from its first frame. Overflow or expiry closes the channel; closing or destroying it discards the pending frames. The existing native timer invalidates an expiry already queued when the queue drains or closes. Installing a receiver posts delivery on the executor with the app’s allocator. It never calls the receiver inline, and delivery checks the current handler and channel before each frame, so a handler may replace itself or close the channel. A message already queued ahead of that delivery drains older frames first when a receiver is available.

If a transport closes during adoption, before the room obtains a binding, the room forgets that negotiation and restarts announcements. Its later close event cannot leave signaling marked connected with no binding.

What the backend asks of libdatachannel

  • Automatic negotiation is off, so an offer or an answer exists only when the signaling asks for one, as in the browser.

  • Every object libdatachannel hands out is held by the backend: libdatachannel keeps only weak references.

  • Local candidates leave as the JSON of an RTCIceCandidateInit, the form Trystero JS adds with addIceCandidate.

  • The rtc::PeerConnection object and its control block are made in the app’s resource; what that object allocates inside libdatachannel is libdatachannel’s (What stays outside the resource).

Its configuration

native::backend_config is what the backend makes its peer connections with, beside the app’s ice_servers:

Member What it is

bind_address

The local address every peer connection binds to, as "127.0.0.1"; empty means every interface but the loopback. Bound to the loopback, the peers of one machine connect with no other network, which a test wants; a peer bound there reaches no other machine.

max_peer_connections_per_minute

The most peer connections the backend makes in any minute, for every room in every app it serves together, 256: beyond, it makes none, and a room reports each negotiation it cannot start. A room makes at most 64 in any 57 s however its peers behave (Bounded signaling), so the default lets four rooms do so at once. A connection still counts against its negotiating room’s 57 s window after adoption by the app; presence and reuse make none.

relay_limits

What the relays’ WebSockets tolerate of a relay that stops reading or answering, native::websocket_limits (The relays).

The relays

The relays are reached over Boost.Beast: ws:// in the clear, wss:// over TLS with SNI and host name verification. The backend verifies relays against the system’s certificate authorities; tls_context() gives the boost::asio::ssl::context to an application with authorities of its own. A relay whose certificate does not verify is unreachable, and the room retries it later as it would any other. The WebSockets are Beast’s rather than libdatachannel’s own, so the relays run on the room’s executor like everything else.

A relay is a third party, and the backend bounds what one can make the room hold:

  • A text longer than 1 MiB (protocol::max_relay_message_bytes) closes the connection before it is copied, as in the browser.

  • A relay that stops reading is dropped once the text held unsent for it would pass 4 MiB (websocket_limits::max_queued_bytes, which is protocol::max_queued_relay_bytes on both backends).

  • A relay that stops answering is dropped once idle_timeout (30 s) passes with its keep-alive ping unanswered: halfway through, the connection sends a ping. The timeout counts bytes received, and a ping waits for the write in flight, so a relay that reads under about 2 KB/s is closed during a 64 KiB text. Signaling texts are 2 to 4 KB, so a real room meets it only with a relay slower than about 200 B/s.

Each drop reports one close, and the room reconnects with its backoff (Discovery over Nostr). A page has no idle timeout: its WebSocket cannot ping (What a page bounds).

The protocol

A room keeps Trystero 0.26.0’s wire byte for byte. The protocol’s pure functions are in <webcpp/trystero/protocol/>; their tests compare them, byte for byte, with values Trystero JS computed itself (Testing). What a Trystero JS peer or a page sees never changes: the topic prefixes Trystero@ and Trystero:, the handshake’s __trystero_pw, every constant and message layout, and the default relays in their order.

Discovery over Nostr

Part What it is

Root topic

Trystero’s sha1 of Trystero@<app>@<room>: each SHA-1 byte in base 36, unpadded, concatenated

Peer topic

the same of Trystero@<app>@<room>@<peer id>, where a peer receives its offers, answers and candidates

Event

kind 20000 plus the sum of the topic’s UTF-16 code units modulo 10000, ephemeral; the topic in an x tag; signed with BIP-340

Subscription

one per relay, to the root topic and the peer’s own topic

Announcement

{"peerId":...,"nonce":...} on the root topic: at once, then after 233, 533, 1333 and 5333 ms, then every 60 s

Relays

the configured ones, or 5 of Trystero’s 29 defaults, shuffled with sin(seed++) * 10000 seeded by the app id

A relay that answers rate-limited: is skipped for 60 s, doubling to 15 minutes while the replies persist; one that answers blocked:, restricted:, auth-required: or pow: is retired. A lost relay is reconnected after a random share of 3.3 s, doubling to 60 s, and the delay comes back to 3.3 s only once a connection stayed open for 60 s, so that a relay that closes the room at once after each open is tried less and less often.

Signaling

Of two peers, the one with the lower id offers, on the relay that carried the other’s announcement. The higher one answers an announcement by announcing itself on the other’s peer topic, so the lower one offers: at most once a second per peer, and once in 5.3 s per relay for the whole room, Trystero’s announce interval, so that a flood of low ids does not have the relays rate-limit the room. Offers, answers and candidates are sealed with AES-GCM under the SHA-256 of <password>:<app>:<room>, using the immutable app_config::password, empty for a passwordless app, with a 16-byte IV, as the text <iv>$<ciphertext>: the IV’s bytes in decimal joined by commas, and the ciphertext with its tag in base64. Candidates follow their offer or answer, trickled. A negotiation ends when the data channel opens: the app adopts the connection, and signals from a bound peer are ignored. A live connection is reused for another room only after the peer advertised that room’s token itself on the connection.

The data channel

Every message is a room frame: 1, the length of the room token (16 bits, big-endian), the token, the hexadecimal SHA-256 of Trystero:<app>:<room>, then an action frame. A presence frame, 2, present or not, the token’s length and the token, announces or withdraws the room.

Presence advertises every room of the app on each connection. It records which rooms the remote peer joined and binds matching local rooms; a withdrawal detaches only that room. The connection routes by token:

Frame What happens

An envelope that does not decode

The connection ends for every room.

presence(1, T), new token

Record it; a 65th distinct token ends the connection. Bind the matching local room when its binding conditions permit.

presence(1, T), known token

Bind the matching unbound local room when permitted.

presence(0, T)

Forget the token and its kept frames, and end its binding as a departure.

Room frame, token bound

Deliver it to that room’s action wire.

Room frame after the higher id’s own kick, before it sends presence

Drop it: it belongs to the ended binding.

Room frame for another room of the app, not bound to this connection

Keep it, at most 64 frames per connection, all expiring 10 s after the first. Bind only when the peer advertised the token and no backoff runs.

Room frame for any other token

Drop it silently; keep the connection.

On binding, kept frames arrive before later frames; a kept @_leave discards itself and everything kept before it for that token. There is no buffer for a bound room awaiting its handler: delivery is installed in the same call that makes its binding.

Waiting sends take turns by binding on each channel drain, preserving each binding’s waiter order and the 10 s stall bound. Chunked transfers send one chunk per drain, giving waiting sends from other rooms a turn. Inline sends at a buffered amount of at most 64 KiB go straight to the channel: they have no trystero rate bound, so a flood of pings can delay every room’s traffic to that peer, and that peer only.

Action frames are Trystero’s action wire, version 2: an inline frame carries the payload whole; a larger payload is an offer, accepted or refused by the receiver, then chunks of at most 16,370 bytes, in order. Integers and float64 values are big-endian, as JavaScript’s DataView writes them.

Frame Bytes

inline (kind 0)

2, the kind and the format shifted by four, the name (32 bytes, NUL padded), the metadata’s length (uint16), the metadata, the payload

offer (kind 1)

the inline header, the transfer’s id (uint32), the payload’s size (float64), the metadata

chunk (kind 2)

2, 2, the transfer’s id (uint32), the offset (float64), the bytes

accepted (kind 3)

2, 3, the transfer’s id (uint32)

rejected (kind 4)

2, 4, the transfer’s id (uint32), the reason (UTF-8, at most 200 bytes)

cancelled (kind 5)

2, 5, the transfer’s id (uint32)

The payload’s format is text (0), JSON (1) or binary (2). Internal actions run the room:

Action What it carries

@_hsdata

the password handshake: the lower id sends a challenge, and the other answers with the hexadecimal SHA-256 of <challenge>:<password>:<app>:<room>

@_hsready

the sender’s side of the handshake passed; a peer joins when both sides sent it

@_ping, @_pong

round trips

@_leave

the sender leaves the room

The media actions, @_stream, @_track and @_signal, are not ported (Differences from Trystero): a frame of one is ignored.

Bounded signaling

Announcements are not sealed, so anyone who knows the app and room ids can announce any number of peer ids, and anyone who reads the relays can send any signal in any peer’s name. Trystero JS bounds none of this; trystero bounds what each costs a room.

  • Nothing a peer sends changes a negotiation before it opens with the room key: an offer, an answer or a candidate that does not open is counted, reported once per negotiation, and changes nothing else. A flood of fresh ids makes each a negotiation, so the reports are paced: the first of a quiet 10 s goes to on_error at once, with its peer, and the others of those 10 s in one report, with no peer and their number.

  • A room holds at most detail::signaling::max_unconnected_peers (32) negotiations with peers it is not connected to. A negotiation whose offer and answer expire without a connection is dropped, an idle one gives its place to a new peer, and a new peer beyond the bound is refused and logged once.

  • A room makes at most max_connections_per_window (64) connections in any 57 s, however soon they end, and a page at most 256 (What a page bounds); the native backend makes at most 256 a minute (Its configuration).

  • A signal’s fields are bounded in protocol/signal.hpp: the peer id (64 bytes; Trystero’s are 20), the offer id (64 bytes), a sealed offer or answer (64 KiB) and a sealed candidate (16 KiB, above the 8192 bytes of plaintext Trystero takes).

  • A relay is a third party too: its texts are bounded at 1 MiB, what the room holds unsent for it at 4 MiB, and, natively, its silence at 30 s (The relays).

Bound Scope Value and consequence

Joined rooms

App

64, counting farewells until withdrawal; join returns error::too_many_rooms beyond it.

Advertised room tokens

Connection

64 distinct tokens; a 65th ends the connection.

Kept frames for unbound local rooms

Connection

64, at most 64 x 16,515 bytes; expire together 10 s after the first. Other tokens retain nothing.

Room frame and token

Connection

16,515 bytes, token 1 to 128 bytes; an invalid envelope ends the connection.

No bindings

Connection

idle_timeout after a departure, 123,333 ms by default, 0 closes immediately; closed at once if its last binding was kicked.

Handshake

Room and peer

handshake_timeout, 10 s; failure kicks that binding only.

Earlier challenges

Room and peer

Last 8 since the last completed handshake; a correct answer to any passes.

Pending action frames

Room and peer

64 for 120 s; one incoming transfer per peer, max_receive_bytes across peers, and 1024 queued messages per action.

After a kick or the peer’s @_leave, a live connection is rebound one step after that binding ended. The initial step is the larger of handshake_timeout and 10 s, plus 1 s (11 s by default), doubling at each further end to 5 minutes. Only a completed handshake or the connection’s end resets it. After its own kicks the lower id refuses bindings until its backoff ends, ignores signals and keeps frames; the higher id drops frames until it next sends presence and accepts the lower id’s presence at once. The room sends presence again on reuse or scheduled rebinding, making the other side bind without a relay response. Announcements restart only when no live connection remains. A forged announcement never binds a peer that has not advertised that token itself.

A connection counts against its negotiating room’s 64-per-57-s window even after the app adopts it; presence and reuse make no connections. Fresh ids that connect and depart keep at most 64 x ceil(idle_timeout / 57 s) idle connections per room, 192 by default, within the backend’s own bound. Leaving a room with many peers retains no more connections than the app already held. Kept frames and tokens cost at most about 1 MiB and 8 KiB per connection; each of at most 64 bound rooms retains its own peer bounds. A flood spends its room’s queues, transfers and reservations; routing never holds one room’s frame for another room.

With the lower id an invalid room response proof causes at most six kicks and reports in the first ten minutes, then one per 5 minutes. The higher id can be rebound by a lower-id peer at that peer’s pace, but reports at most one handshake timeout per handshake_timeout, and does not verify a response proof. Malformed challenges are rejected by their receiver and can also report wrong_password; their report rate follows the malicious peer’s repeated bindings, rather than the timeout bound.

Replayed offers and answers

A sealed offer or answer holds neither its offer id nor a peer id (Trystero’s wire), so one recorded from the relays still opens when anyone resends it in its sender’s name, and so does the room’s own, sent back to it. A room remembers, by the SHA-256 of their opened text, the last 1024 offers and answers it made or acted on (detail::signaling::remembered_descriptions), and refuses one that comes to it as a peer’s: it changes nothing and is logged. It makes or acts on at most two per connection it makes, so each is remembered for at least an offer’s lifetime (57 s), and in a quiet room for its whole life. So a room refuses a sealed offer or answer it acted on, resent, and one it made itself, sent back as a peer’s offer or answer.

What remains

Sharing preserves Trystero JS 0.26.0’s wire. Each C++ app is one password domain: it copies one password at construction, and all its rooms use it. The initial sealed signaling authenticates that password before the data channel is adopted. Later room binding reuses this authenticated connection. There are no per-room overrides, and different passwords require separate apps with distinct self ids. This protects admission to the C++ app’s rooms without changing the room handshake on the wire.

A watcher not connected to the app, without the key, can keep two peers apart. A sealed offer or answer the room neither made nor acted on still opens and is acted on. Such texts are not scarce: every peer seals a fresh offer for any id announced above its own, and announcements need no key. So anyone who reads and publishes on the relays, without the key, can keep any two peers of a room apart from their first meeting for as long as it keeps at it, each attempt costing the other peer a connection of its window. A watcher not connected to the app cannot read or alter anything the peers exchange, join the room, or touch peers already joined; a joined peer that drops is a new peer again. The room refuses only what it made or already acted on. Only a wire that seals the offer id and both peer ids closes this, which Trystero would have to share (reported upstream).

A flooder can delay new peers. Joined peers, memory and connections stay safe under a flood, but a flooder who keeps sending new ids takes each place, or each connection of the window, as it frees, so new peers can be kept out for as long as the flood lasts, each place up to one offer’s lifetime (57 s) at a time. A joined peer whose connection drops comes back as a new peer, with the same id, and is kept out the same way: the room has no renegotiation that would keep its place. Closing it needs announcements that prove the room key, a wire change Trystero would have to share.

A passwordless app’s rooms are open to its connected peers. Presence gives a peer connected through any room the token of every room of that app. Relay events reveal their topics in the clear, and announcements reveal the app’s peer id. The token alone binds the existing connection, and the empty app password goes straight to @_hsready, so that peer can join another room without knowing its id. A watcher not connected to the app still needs the room id to derive its topics and signaling key. Protected rooms belong to another app with a password and a distinct self id. Its presence, tokens and connections never reach the passwordless app.

The upstream one-way handshake remains. The responder to a room challenge answers and passes its side; only the lower id verifies the password proof. A Trystero JS page that itself mixes different passwords on one app’s shared connection can therefore admit a peer to its own higher-id room without that room’s password, and can expose a challenge response for offline guessing. The C++ app-domain boundary does not repair such a JS page. Configure all rooms of one JS app with one password, and use separate page contexts for different password domains. The wire has not gained bilateral room proofs.

A domain member can send an invalid room proof. A peer already connected to a protected C++ app knows its domain password from the initial sealed negotiation. It can nevertheless answer a later room’s challenge incorrectly or send malformed handshake data. The room kicks that binding and applies the documented backoff; only the lower id checks and reports a wrong response. These checks isolate faulty peers within an authenticated domain, rather than admitting another password domain.

One identity links the app’s rooms. Each room announces the same peerId in clear, and connected peers see it across shared rooms. Relay observers and peers can link those rooms to one program. Separate apps provide separate identities and connections for rooms that must remain unlinked. A forged announcement in a connected peer’s name binds nothing: only the peer itself, on its encrypted connection, can advertise the token. It causes no report or rebinding count against that innocent peer.

Two transient cases remain with a Trystero JS lower id. If it binds by reuse during the higher C++ id’s dropping window, its challenge is dropped and its handshake can time out once. With the default 10 s timeout the next rebinding joins by at most 43 s after the first kick plus one-way delay, with no false password error on either side. If a JS page’s timer that expires a stale answer runs later than the 1 s margin before presence arrives, JS can flush the old answer into a new handshake and report a false password error. One-minute timer lag can repeat this at the 11, 22 and 44 s steps, at most three times, before the 88 s step exceeds that lag. The C++ side reports nothing false in either case.

Design

Layers

Layer Headers Knows

Protocol

protocol/*.hpp

pure functions over bytes and text: no I/O, no platform

Backend interface

backend.hpp

the WebSocket, peer connection, data channel, timer and AES-GCM sealer a platform provides

App

app.hpp, detail/app_impl.hpp, detail/connection.hpp

one identity, the room registry, shared connections and their routing

Room

room.hpp, detail/*.hpp

the Nostr strategy, signaling, bindings, action wire and room API

Browser backend

browser/*.hpp

emscripten::val over the browser’s APIs, and the event loop

Native backend

native/*.hpp

libdatachannel, Boost.Beast, OpenSSL and Asio’s timers

Everything above the backend interface is the core, which builds on both targets: the same room runs over the browser’s APIs, over libdatachannel, and over the memory relays of the tests (Testing). The core never includes a backend, libdatachannel, OpenSSL or Emscripten: a boundary of the library’s own, which webcpp’s lint checks, keeps it so.

Connections shared by the rooms of an app

Object Owns Lives Resource

app

A reference to detail::app_impl, counted as one user

While the user keeps its handle

None

detail::app_impl

Configuration and immutable password, self id, logger, room registry by token and id, connections and user count

While any shared reference lives; its last user closes its connections

App’s

detail::connection

One peer’s connection and channel, advertised tokens, kept frames, idle timer and plain pointers to bindings and their drain order

From adoption until its end

App’s

detail::binding

One room’s use of a connection: a weak reference, drain waiters and delivery to its room

Until detachment, owned by its room’s peer state

Room’s

room

A reference to detail::room_impl

While the user keeps its handle; destruction leaves

None

detail::room_impl

Signaling, strategy, actions, peers, bindings, handlers, rebinding counts, backoffs and a shared reference to the app

Until its state and pending operations end

Room’s, the app’s by default

  • A room holds the app strongly; the app reaches rooms weakly. A binding holds its connection weakly, and the connection holds plain pointers to bindings. Detachment removes those pointers and every drain registration before returning. Ending a room removes its registry entry, so the app keeps no allocation of that room’s resource after it ends, and no cycle keeps the objects alive.

  • Sharing is scoped by the app the user constructs, with no registry outside it and no static state keyed by the backend. Two apps with the same application id in one process are two peers. Each app has one immutable password. Different passwords use separate apps with distinct self ids, so their connections, tokens and presence are never reused.

  • The action wire continues to write its room token and send through a channel_link. The binding implements that link: each room keeps its own actions, transfers, queues and bounds.

Why abstract classes for the backend

The backend is a handful of abstract classes rather than template parameters. Each of its operations is a call into JavaScript or into a network stack, so a virtual call costs nothing measurable, while the room’s code stays free of templates over the platform and of #ifdef, and the test backend and the browser backend are interchangeable at run time. Every handler a backend invokes runs on the room’s executor, after the call that caused it has returned, never inside it: the room calls these operations from inside handlers whose state a completion run there would change under them.

Why the browser’s own WebRTC

libdatachannel’s WebAssembly port, datachannel-wasm, has no media and hides the RTCPeerConnection it wraps. Binding the browser’s objects directly through emscripten::val is smaller than that wrapper, reports every error, and leaves the browser’s whole WebRTC stack (capture, codecs, playout) within reach for media.

Text is a view, a copy is a decision

A parameter that only reads text is a std::string_view, bytes are a std::span, and a string is used where something must own the text: a map key, a member, a capture of a handler that runs later.

  • The digests the protocol takes are fed the parts of their input: nothing is concatenated to hash it.

  • Signals and relay messages are read as views into the parsed document; the JSON the room writes goes into one reserved buffer, escaped by Boost.JSON’s serializer, and a Nostr event’s id is hashed over those same bytes.

  • A frame is written once, inside its room envelope, and shared by every target; a received frame becomes the buffer of the message delivered from it, and a view handler reads it in place.

  • A sentence of the log is composed only when there is a sink.

The asynchronous operations are the exception, for the reason Completion tokens and ownership gives.

Optional native dependencies

libdatachannel is under the MPL 2.0 and OpenSSL under the Apache License 2.0. Both are needed only by the native backend, which a browser program never includes: /webcpp/trystero//trystero depends on neither, and /webcpp/trystero//native adds them, as OpenSSL is optional for Asio.

Licensing

The library is distributed under the Boost Software License. It ports Trystero’s protocol and was written with Trystero’s source open, so Trystero’s MIT notice comes with it (Acknowledgements).

Guarantees

What trystero promises, and the test that holds each promise, which fails when the promise breaks. A guarantee keeps its number, and the anchor named after it, so a comment of the code or of a test cites it by that anchor: (doc: #trystero-invariant-3) sends its reader to the third. b2 libs/trystero/doc, which builds this page, fails on a citation that names no anchor of it. The tests are those of Testing.

  1. The wire is Trystero 0.26.0’s. A C++ peer, in the browser or native, and Trystero JS peers in one room join, exchange text, JSON and binary messages, files both ways with metadata, and pings, leave, and form a mesh, with either side offering. Held by the interop lane, interop_browser on emscripten and interop_native natively, against Trystero 0.26.0 itself in headless Chrome.

  2. Every protocol value equals Trystero JS’s. Topics, room tokens, the relay shuffle, sealed text, Nostr event ids, inline and chunked action frames, control frames, envelopes and presence are what Trystero’s own functions compute from the same inputs, byte for byte. Held by protocol, against the values the oracle lane has Trystero 0.26.0 write, and by the oracle lane, which fails when Trystero would write other values than those committed; and by native_sealer, which opens what Trystero JS sealed with Web Crypto.

  3. A peer with another password never joins a protected C++ app’s rooms. Its initial signals do not open under the app’s password, so it obtains no shared connection into that domain. Every room of that app uses the same immutable password. This promise concerns the C++ app’s admission; an upstream JS page that mixes passwords retains its one-way handshake exposure (What remains). Held by room (the_same_password_joins_and_another_does_not), native_room (another_password_never_joins), and password-domain tests of shared and interop.

  4. A payload larger than a frame travels in chunks, both ways. It is offered, accepted and sent in chunks of at most 16,370 bytes, and arrives whole, from either peer. Held by room (large_payloads_travel_in_chunks_both_ways, 100 KB each way), and the file transfers of the interop lane.

  5. Every allocation of an app and its rooms goes through their resources, but what What stays outside the resource lists: from its construction until its io_context has drained, the global operator new sees only the room’s JSON storage counter, and the C library nothing. Held by allocation, natively and on emscripten.

  6. No unit test reaches beyond the loopback. The rooms of the unit tests meet over the memory relays, the native tests’ peer connections bind to 127.0.0.1 and gather their candidates there alone, and the WebSocket test talks to a server of its own on 127.0.0.1. Held by the container lanes, gcc-14 and clang-linux-18: every unit test, built with GCC 14 and with clang 18 against libstdc++, passes in a Linux container that has no network but the loopback (docker run with the network none).

  7. A room refuses a replayed or reflected offer or answer. A sealed offer or answer it made, or acted on, that comes back to it as a peer’s changes nothing (Replayed offers and answers). Held by room (a_replayed_answer_is_refused, a_replayed_offer_is_refused, a_reflected_offer_is_refused_as_an_answer, a_reflected_offer_is_refused_as_an_offer and a_reflected_answer_is_refused_as_an_answer), and the replay and reflection scenarios of the interop lane.

  8. A relay that stops reading is dropped. Once the text held unsent for it would pass 4 MiB, the connection is dropped, and reports one close, after which the room reconnects with its backoff, as it does to any relay that closes. Held by native_websocket (a_relay_that_stops_reading_is_dropped_at_the_queue_bound, a close before 2000 texts of 64 KiB, and one only) natively, and browser_websocket_bound on emscripten, with exceptions and without them, which also checks that the socket never holds more than 4 MiB; the reconnection by room (a_relay_that_closes_at_once_is_tried_less_and_less_often).

  9. One connection per remote peer per app is shared by its rooms. Each room keeps its own membership and actions on that connection. Held by shared, cases 1 to 3, and the interop’s first shared scenario.

  10. A room’s departure leaves the app’s other rooms joined. They continue sending over the same connection. Held by shared, case 8, and shared interop scenario 5, with either side leaving.

  11. Different passwords share no C++ app connection or presence. Protected rooms use a separate app with a distinct self id; a peer of a passwordless app gains no binding to that protected app by echoing tokens. Held by the password-domain cases of shared and shared interop scenario 4.

Testing

Testing a program that uses trystero

A room needs relays and peers, and a test needs neither the network nor another machine. The backend interface is the seam: a program’s tests run its rooms over a backend of their own, in memory, as trystero’s own tests run whole rooms over test/support/memory_backend.hpp, a backend whose Nostr relays forward ephemeral events to matching subscriptions, whose peer connections pair through their descriptions, whose data channels deliver in order, and whose sealer opens only what was sealed under the same key, each event posted to the io_context. It is in the namespace webcpp::trystero::test, outside the library’s public headers, and a program may copy it.

The memory network identifies connection ends with std::size_t ids. on_made records construction and ICE servers, and peer_of identifies the paired end. A filter delivers, drops or holds frames; a held frame holds all later frames from its sending end behind it, preserving channel order until release. Deferred open calls open each end separately; inject and fail supply malformed frames and transport failure.

With channel_capacity enabled, the channel queues bytes and drains up to drain_per_post per post. It delivers a frame only after its last byte drains, and buffered reports the exact unsent amount. Set virtual_clock before making timers: advance fires due timers in order and runs ready work between them; step and poll_ready give tests finer control. Timeouts, backoffs and late frames are tested without wall-clock sleeps. Optional fault hooks allocate outside the measured resources only when enabled; the allocation test leaves them disabled.

A test of the native backend’s own WebRTC binds its connections to the loopback, native::backend_config::bind_address set to "127.0.0.1", and names a STUN server where nothing answers, stun:127.0.0.1:9, in place of Trystero’s public ones: the loopback’s host candidate is then the only one, and it is enough.

How trystero is tested

trystero’s tests are under test/, each declared in test/Jamfile, and each guarantee of Guarantees names the tests that hold it. They run on two targets: 75 tests in all, 54 natively and 60 on emscripten, under node. The protocol, the rooms and the allocation test run on both:

webcpp.run protocol : protocol_test.cpp : <testing.input-file>fixtures/expected/expected.json ;
webcpp.run room : room_test.cpp ;
# Rooms of one app that share one connection per remote peer; once as the
# build asks, once without exceptions, on both targets; and, natively, once
# with the standard libraries' checks, so that a kept frame that kicks during
# a binding, or a waiter failed during a connection's end, fails at once when
# it touches what it removed.
webcpp.run shared : shared_test.cpp ;
webcpp.run shared_no_exceptions : shared_test.cpp : <exception-handling>off ;
webcpp.run shared_hardened : shared_test.cpp
  : <define>_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE <define>_GLIBCXX_ASSERTIONS
  : native ;
# The memory backend's hooks, which the tests of shared connections drive.
webcpp.run memory_backend : memory_backend_test.cpp ;
# The shared connection alone, over a memory pair.
webcpp.run connection : connection_test.cpp ;
# The state an app and its rooms share, alone, over the memory backend.
webcpp.run app_impl : app_impl_test.cpp ;
webcpp.run allocation : allocation_test.cpp : <toolset>emscripten:<linkflags>-sALLOW_MEMORY_GROWTH ;
Test What it checks

the public headers

Each compiled alone, on each target that builds it: the core on both, the native backend natively, against libdatachannel and OpenSSL, and the browser backend on emscripten.

config, config_developer_defined, config_all_off

<webcpp/trystero/config.hpp> defines WEBCPP_TRYSTERO_NO_EXCEPTIONS exactly when webcpp has no exceptions (WEBCPP_HAS_EXCEPTIONS is not defined: the compiler has none, or the program defined WEBCPP_NO_EXCEPTIONS), and keeps it when a developer defines it for trystero alone in a build that has them.

protocol

The protocol against values Trystero JS 0.26.0 computed itself: topics, room tokens, the relay shuffle, sealed text, Nostr event ids, inline and chunked action frames, control frames, envelopes and presence, and the bounds of a signal’s fields.

room

Whole rooms in one process, over the memory relays: every payload format, 100 KB transfers both ways, ping, passwords right and wrong, leaving and returning, a room moved over a joined one, ICE servers checked as WebRTC checks them, floods of announcements and of forged, replayed and reflected signals, a relay that closes at once or rate-limits, a three-peer mesh, coroutines and view handlers.

app_impl

Configuration copying and validation, resource ownership, weak room registration, shared adoption, stale replacement, reentrant adoption, last-user closure and quiet destruction with pending work.

connection

Shared transport routing, advertised token and kept-frame limits, binding detachment, departures and kicks, idle expiry and drain ordering.

memory_backend

Virtual timer ordering, cancellation, held-frame order, deferred opens, injection and failures, and byte-accurate buffering and drain.

shared, shared_no_exceptions, shared_hardened

Multiple apps and rooms with one connection per remote peer, adoption races, presence and reuse, room isolation, departures, idle connections, password-domain isolation, invalid room proofs, transient handshake recovery, fairness, bounds and resource ownership. The suite also runs without exceptions and with standard-library hardening; sanitizers check lifetimes and detachment.

allocation

Every allocation of two apps and their rooms goes through their resources (The proof).

native_sealer, native_websocket, native_timer

The native backend, part by part, natively: the sealer opens what Trystero JS sealed with Web Crypto; the WebSocket, against a Beast server in the same process, opens, sends in order, closes once, and drops a relay that sends too much, stops reading or stops answering, while one that answers its pings stays; the timer never fires once cancelled, restarted or destroyed.

native_peer_connection, native_room

Two libdatachannel connections wired to each other, each handler on the executor, and whole native rooms over real libdatachannel connections, bound to 127.0.0.1. A controlled executor holds real channel callbacks to prove pre-handler FIFO delivery, a 65th message ahead of the posted flush, handler replacement and destruction, the 64-frame and 10-second bounds, and allocation and release through the channel’s resource.

browser_event_loop, browser_websocket, browser_websocket_bound, browser_websocket_late_events, browser_sealer, browser_data_channel, browser_peer_connection

The browser backend, on emscripten under node, which has the page’s WebSocket, Web Crypto and microtasks but no WebRTC: the loop and its recovery from exceptions that escape a callback, the WebSocket’s URLs, its bound on what a relay holds unsent and its silence once dropped, the sealer’s completions, a data channel’s refusal of what is no frame, and the page’s peer connections, made or refused, and their census.

The native backend’s connections need exceptions whatever the build asks (Exceptions), and each browser test is also built without exceptions, as a program that uses the browser backend may be:

webcpp.run browser_event_loop : browser_event_loop_test.cpp : : emscripten ;
webcpp.run browser_websocket : browser_websocket_test.cpp : : emscripten ;
webcpp.run browser_websocket_bound : browser_websocket_bound_test.cpp : : emscripten ;
webcpp.run browser_websocket_late_events : browser_websocket_late_events_test.cpp : : emscripten ;
webcpp.run browser_sealer : browser_sealer_test.cpp : : emscripten ;
webcpp.run browser_data_channel : browser_data_channel_test.cpp : : emscripten ;
webcpp.run browser_peer_connection : browser_peer_connection_test.cpp : : emscripten ;
webcpp.run browser_event_loop_no_exceptions : browser_event_loop_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_websocket_no_exceptions : browser_websocket_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_websocket_bound_no_exceptions : browser_websocket_bound_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_websocket_late_events_no_exceptions : browser_websocket_late_events_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_sealer_no_exceptions : browser_sealer_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_data_channel_no_exceptions : browser_data_channel_test.cpp
  : <exception-handling>off : emscripten ;
webcpp.run browser_peer_connection_no_exceptions : browser_peer_connection_test.cpp
  : <exception-handling>off : emscripten ;

From the root of a webcpp checkout, with the three libraries of Building found, each of these builds and runs the tests, natively, under AddressSanitizer and UndefinedBehaviorSanitizer, and on emscripten:

b2 libs/trystero/test
b2 libs/trystero/test address-sanitizer=norecover undefined-sanitizer=norecover
b2 toolset=emscripten libs/trystero/test

The 3 examples are linked and never run, since each needs the public relays or a browser. b2 compares timestamps, so a result that counts is built from scratch, with b2 -a.

The oracle and the interop lanes

Two lanes of trystero’s own, declared in test/oracle/Jamfile, check it against Trystero itself, pinned at 0.26.0 by test/oracle/package.json and package-lock.json, with Node:

webcpp.original node ;
webcpp.cases protocol : oracle.mjs : ../fixtures/cases : ../fixtures/expected ;
webcpp.lane oracle : cases-protocol ;

oracle, which names no target, runs Trystero’s own functions over the inputs of test/fixtures/cases/protocol.json and checks that they still write test/fixtures/expected/expected.json byte for byte, the values the protocol and native_sealer tests reproduce; update-expected is its only writer.

webcpp.drive interop_browser : interop/peer.cpp
  : <linkflags>-sENVIRONMENT=web <linkflags>-sALLOW_MEMORY_GROWTH <webcpp-drive-timeout>2400
  : interop/run.mjs : --browser : emscripten ;
webcpp.drive interop_native : interop/native_peer.cpp
  : <library>/webcpp/trystero//native <threading>multi <exception-handling>on <webcpp-drive-timeout>2400
    <define>_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE
    <define>_GLIBCXX_ASSERTIONS
  : interop/run.mjs : --native : native ;
webcpp.lane interop : interop_browser interop_native : native emscripten ;

interop, natively and on emscripten, meets each C++ peer, the native one as a process and the browser’s in headless Chrome, with Trystero JS peers in Chrome, over an in-memory Nostr relay on 127.0.0.1 that verifies every event’s id and signature as a public relay does. Its scenarios: the C++ peer offering; Trystero JS offering, with a password; a Trystero JS peer that also sends malformed candidates, or text messages on its data channel; a first relay to which no WebSocket can be made; a C++ peer given a TURN server without credentials; a watcher without the key that answers every offer with garbage in the other peer’s name, that resends a sealed answer and offer it recorded, and that sends the C++ peer its own sealed offer back, which the C++ peer must refuse; a flood of announcements and of unsealed offers from fresh peer ids; a relay text longer than 1 MiB; a C++ peer and a Trystero JS page each in two rooms of one app, sharing one connection and one peer id per app; a wrong password; and a mesh of three. The native peer meets three more: a room moved over the joined one, which must leave it as destroying it does; a relay that ends the connection just as the peer writes, which twelve peers in turn must survive; and the driver’s own check that a peer killed by a signal is named at once.

The shared-connection scenarios run in both lanes:

  1. Two rooms exchange chat and 100 KB files both ways on one adopted, connected transport.

  2. A third room joins later through presence on that transport.

  3. Rooms the JS page joined first bind when the C++ app joins them later.

  4. A peer can enter another room of a passwordless app by token, while a protected app with another password and self id shares no connection or presence with it, in either id order.

  5. Either side leaves one room while the other keeps exchanging, making no connection.

  6. Leaving every room closes the transport after the configured idle time.

  7. An authenticated peer sending wrong room proofs in both id orders gives paced lower-id reports while the other room keeps exchanging over the same connection.

  8. A 5,000-message flood in one room leaves pings in another completing within 2 s.

  9. A held handshake frame recovers in both id orders after one timeout, without a false password report; the other room keeps exchanging.

The C++ peers accept room2Delay and room3Delay in milliseconds; the JS page also accepts room2Delay. These delayed joins establish the first room before a shared-binding scenario begins; the proof-pacing scenario first authenticates the shared password domain, then corrupts room proofs after the initial announcement burst.

The adoption scenarios allow crossed duplicates to retry, but fail with connection counts if either side constructs more than four. Transient recovery accounts for JS reuse during the higher id’s dropping window (What remains).

Every peer gathers its ICE candidates on the machine alone: the C++ peers get stun:127.0.0.1:9 in place of Trystero’s public STUN servers, the native peer binds its connections to 127.0.0.1, and the Trystero JS peers have no ICE server. Chrome is the one CHROME names, else on macOS Playwright’s chrome-headless-shell; the continuous integration names Chrome for Testing’s chrome-headless-shell, at the version webcpp pins. Each driver is bounded by 2,400 s, beyond its scenarios’ own limits, so that a scenario that does not complete fails by name with what each peer saw.

b2 -a libs/trystero/test/oracle//oracle
b2 -a --dump-tests --out-xml=native.trystero.interop.xml libs/trystero/test/oracle//interop
b2 -a --dump-tests --out-xml=emscripten.trystero.interop.xml toolset=emscripten libs/trystero/test/oracle//interop

The container lanes

Two lanes of the continuous integration, gcc-14 and clang-linux-18, build and run the unit tests with GCC 14 and with clang 18 against libstdc++ in a Linux container, Ubuntu 24.04, with docker run and the network none, which leaves the loopback alone: every test passes, which shows that none reaches beyond the machine (guarantee 6). libsecp256k1 is installed before running the container. B2 builds the pinned OpenSSL, libdatachannel and nested source dependencies inside the container with the lane’s compiler, runtime and static link configuration. These builds need no network access.

The common OpenSSL provider

The native provider-integration lane builds and runs one application that uses Asio TLS, Beast and Trystero’s native backend together. It compares the compiled OpenSSL version with the loaded implementation and constructs TLS and WebRTC state. The run is bounded to 45 seconds and uses the threaded release profile. The superproject’s source dependency tests also check that the final Unix executable has no dynamic OpenSSL or libdatachannel provider, that each static archive occurs once and that dependent archives precede OpenSSL in the link order.

The test matrix

The continuous integration runs trystero’s tests in lanes, one toolset each, and its own lanes, the oracle once and the interop natively and on emscripten, as native.trystero.interop and emscripten.trystero.interop, plus the native provider-integration lane for the common TLS provider. Each lane builds from scratch and records what b2 built and ran; webcpp’s tools/report/report.py merges the files of every lane into a matrix, where each cell says whether that program passed in that lane, or how it failed. The matrix is published with the documentation, at https://webcpporg.github.io/report/.

Reference

Every name trystero declares, in the namespace webcpp::trystero and those nested in it, from the Doc Comments of its headers: each class, function, type and enumerator with its declaration, what it does, what it asks of its caller and how it fails, and the part of Trystero 0.26.0 it ports. The other sections teach the library; this one is where a name is looked up.

Macros

Name Description

WEBCPP_TRYSTERO_NO_EXCEPTIONS

Defined when trystero is built without exceptions: when webcpp has none (the compiler has none, or the developer defined WEBCPP_NO_EXCEPTIONS for every webcpp library), or when the developer defines it alone, to disable trystero’s in a build that has them.

webcpp::trystero

A callable that allocates through a memory resource: what trystero holds every handler in, where std::function, which takes no allocator since C++17, would allocate with operator new.

Namespaces

Name Description

backend

The seam between the room and a platform: what a target must provide for a room to run on it. The browser provides it over emscripten::val (webcpp/trystero/browser), a native target over libdatachannel, Boost.Beast and OpenSSL (webcpp/trystero/native), and the tests over memory (test/support).

browser

The browser target: the backend interface over the page’s own WebSocket, RTCPeerConnection, RTCDataChannel, setTimeout and crypto.subtle, through emscripten::val. It needs a secure context (https, or http on localhost), where crypto.subtle exists.

native

The native target: the backend interface over libdatachannel for WebRTC, Boost.Beast for the relays’ WebSockets, OpenSSL for TLS and AES-GCM, and Asio for timers. Everything it reports runs on the executor it is given; the application runs that executor’s context, blocking on it or polling it.

protocol

Types

Name Description

action

A named channel of messages of type T within a room, wire compatible with what Trystero’s makeAction returns.

app

This program in one app: one peer id, and one connection per remote peer, shared by the rooms it joins, as a Trystero JS page shares them.

app_config

What an app is made with: the part of Trystero’s joinRoom configuration that every room of the app shares, its peer id among it, and the one connection per remote peer its rooms share.

error_category_impl

The category of error, named "webcpp.trystero", whose messages are fixed texts.

handler

A move-only callable of a signature, whose target is allocated through a memory resource; see the partial specialization handler<R(Args...)>.

json_resource

A boost::container::pmr::memory_resource that allocates from a std::pmr::memory_resource.

message_info

Who sent a message, and the metadata it carried, the second and third arguments of Trystero’s action handler, in the receiving room’s resource.

room

A room joined over Trystero’s Nostr strategy, what Trystero’s joinRoom returns.

room_options

What a room of an app is joined with: the part of Trystero’s joinRoom configuration that is the room’s own.

send_options

Where an action’s message goes and what it carries beside its payload, the target and the metadata of Trystero’s send.

Type Aliases

Name Description

bytes

Binary data allocated with std::allocator, an action’s binary payload when the user chose standard containers.

payload

What an action carries when its type is not fixed: Trystero’s text, JSON or binary payload.

peer_id

A peer’s id in a room, Trystero’s selfId and the ids getPeers lists: 20 characters of protocol::id_alphabet unless the configuration names one.

pmr_bytes

Binary data in a memory resource, an action’s binary payload in the room’s resource.

Enums

Name Description

error

What an operation of the library completes with when it does not succeed.

Functions

Name Description

default_ice_servers

Trystero’s default STUN servers, its defaultIceServers: Google’s and Cloudflare’s.

error_category

The category of every error code.

make_error_code

An error code of error_category, which boost::system::error_code finds by argument-dependent lookup.

operator==

Returns: &a == &b || a.is_equal(b).

operator!=

Returns: !(a == b).

operator<

webcpp::trystero::backend

The seam between the room and a platform: what a target must provide for a room to run on it. The browser provides it over emscripten::val (webcpp/trystero/browser), a native target over libdatachannel, Boost.Beast and OpenSSL (webcpp/trystero/native), and the tests over memory (test/support).

Description

Tip: every handler a backend invokes runs on the room’s executor, one at a time, and may destroy the object that invoked it. A backend whose events arrive on other threads (libdatachannel) posts them there first.

Every handler runs after the call that caused it has returned, never inside it: a completion (done) of a peer_connection or a sealer, and a websocket’s close when no socket can be made, are posted even when their result is known at once. The room calls these operations from inside handlers whose state a completion run there would change under them.

Text crosses this interface as std::string_view, valid for the duration of the call or of the handler: a backend reads what it is given before returning, and a handler copies what it keeps. The one exception is a data channel message, handed over by value: the receiver keeps the frame, and taking ownership of it is what spares a copy.

Tip: every method that makes an object takes the room’s memory resource last, and the backend allocates the object, its handlers and what it hands to the room (a frame, a text) through it, on the room’s executor only.

Types

Name Description

data_channel

A WebRTC data channel, as the RTCDataChannel a Trystero peer exchanges its frames over.

factory

Makes everything above, and gives a room its randomness and its clocks: one per page in the browser, one per application natively, one per test.

ice_server

A STUN or TURN server, an RTCIceServer of the iceServers Trystero gives RTCPeerConnection.

peer_connection

A WebRTC peer connection, as the RTCPeerConnection of a Trystero peer, whose negotiation the signaling drives: it makes an offer or an answer only when asked to.

sealer

AES-GCM under one room key, in Trystero’s sealed text form (protocol/signal.hpp), what Trystero’s encrypt and decrypt do with crypto.subtle.

timer

A one-shot timer, what the setTimeout calls of Trystero become.

websocket

A WebSocket to a Nostr relay, as the WebSocket Trystero’s relay manager opens.

Type Aliases

Name Description

bytes

A data channel frame, in the memory resource of the room it is for.

Enums

Name Description

connection_state

The state of a peer connection, as RTCPeerConnection reports it.

Using Declarations

Name Description

error_code

The error code every completion of a backend reports, empty on success.

webcpp::trystero::backend::bytes

A data channel frame, in the memory resource of the room it is for.

Synopsis

using bytes = std::pmr::vector<uint8_t>;

webcpp::trystero::backend::data_channel

A WebRTC data channel, as the RTCDataChannel a Trystero peer exchanges its frames over.

Synopsis

class data_channel;

Types

Name Description

handlers

What a data channel reports, each on the room’s executor.

Member Functions

Name Description

~data_channel [destructor] [virtual]

Destroys the channel, which closes it without reporting the close.

buffered_amount [virtual]

The bytes queued to send, its bufferedAmount.

close [virtual]

Closes the channel, which the handler close reports.

is_open [virtual]

Whether the channel is open, its readyState "open".

send [virtual]

Sends a binary message; one sent while the channel is not open is dropped.

set_handlers [virtual]

Replaces the handlers; a handler may replace them during its own call.

Static Data Members

Name Description

low_threshold

Trystero’s backpressure threshold, its bufferedAmountLowThreshold: senders wait while more than 64 KiB is buffered.

Derived Classes

Name Description

data_channel

A data channel over the page’s RTCDataChannel, its messages received as ArrayBuffer objects.

data_channel

A data channel of libdatachannel, the native RTCDataChannel.

See Also

"Design", in the guide.

webcpp::trystero::backend::data_channel::handlers

What a data channel reports, each on the room’s executor.

Synopsis

struct handlers;

Data Members

Name Description

buffered_amount_low

The buffered amount fell to low_threshold or below after being above it.

close

The channel closed; called once.

message

A binary message arrived, handed over in the room’s resource.

open

The channel opened.

webcpp::trystero::backend::data_channel::handlers::buffered_amount_low

The buffered amount fell to low_threshold or below after being above it.

Synopsis

handler<void()> buffered_amount_low;

webcpp::trystero::backend::data_channel::handlers::close

The channel closed; called once.

Synopsis

webcpp::trystero::backend::data_channel::handlers::message

A binary message arrived, handed over in the room’s resource.

Synopsis

webcpp::trystero::backend::data_channel::handlers::open

The channel opened.

Synopsis

webcpp::trystero::backend::data_channel::~data_channel

Destroys the channel, which closes it without reporting the close.

Synopsis

constexpr
virtual
~data_channel() = default;

webcpp::trystero::backend::data_channel::buffered_amount

The bytes queued to send, its bufferedAmount.

Synopsis

[[nodiscard]]
virtual
std::size_t
buffered_amount() const = 0;

Return Value

The number of bytes sent and not yet transmitted.

Note

The return value should not be discarded.

webcpp::trystero::backend::data_channel::close

Closes the channel, which the handler close reports.

Synopsis

virtual
void
close() = 0;

webcpp::trystero::backend::data_channel::is_open

Whether the channel is open, its readyState "open".

Synopsis

[[nodiscard]]
virtual
bool
is_open() const = 0;

Return Value

Whether a message sent now would be sent.

Note

The return value should not be discarded.

webcpp::trystero::backend::data_channel::send

Sends a binary message; one sent while the channel is not open is dropped.

Synopsis

virtual
void
send(std::span<uint8_t const> frame) = 0;

Parameters

Name Description

frame

The message, read during the call.

webcpp::trystero::backend::data_channel::set_handlers

Replaces the handlers; a handler may replace them during its own call.

Synopsis

virtual
void
set_handlers(handlers h) = 0;

Parameters

Name Description

h

The new handlers; an empty one is not called.

webcpp::trystero::backend::data_channel::low_threshold

Trystero’s backpressure threshold, its bufferedAmountLowThreshold: senders wait while more than 64 KiB is buffered.

Synopsis

inline constexpr static
std::size_t low_threshold = 0xffff;

webcpp::trystero::backend::factory

Makes everything above, and gives a room its randomness and its clocks: one per page in the browser, one per application natively, one per test.

Synopsis

class factory;

Description

Each object it makes is allocated, with its handlers and what it hands to the room, in the memory resource it is given, the room’s: that resource must outlive the object and every operation the object leaves pending.

Member Functions

Name Description

~factory [destructor] [virtual]

Destroys the factory, which must outlive every room that uses it.

connect [virtual]

Opens a WebSocket to a relay.

make_peer_connection [virtual]

Makes a peer connection that gathers its candidates through servers.

make_sealer [virtual]

Makes a sealer under a room’s key.

make_timer [virtual]

Makes a timer.

now_ms [virtual]

A monotonic clock in milliseconds, for rate limits and timeouts, as performance.now().

random [virtual]

Fills out with bytes from a cryptographically secure source: they become secret keys and nonces.

unix_seconds [virtual]

Wall-clock seconds since the Unix epoch, for Nostr’s created_at and since.

Derived Classes

Name Description

backend

The browser’s factory: one per page, beside the page’s event_loop.

backend

The native factory: one per application, beside the executor it runs on.

See Also

"Allocation", in the guide.

webcpp::trystero::backend::factory::~factory

Destroys the factory, which must outlive every room that uses it.

Synopsis

constexpr
virtual
~factory() = default;

webcpp::trystero::backend::factory::connect

Opens a WebSocket to a relay.

Synopsis

virtual
std::shared_ptr<websocket>
connect(
    std::string_view url,
    websocket::handlers h,
    std::pmr::memory_resource* memory) = 0;

Return Value

The WebSocket, which reports a close when it cannot connect.

Parameters

Name Description

url

The relay’s ws:// or wss:// URL, read during the call.

h

What the WebSocket reports.

memory

The room’s resource, which the WebSocket, its handlers and its texts are allocated in.

webcpp::trystero::backend::factory::make_peer_connection

Makes a peer connection that gathers its candidates through servers.

Synopsis

virtual
std::shared_ptr<peer_connection>
make_peer_connection(
    std::span<ice_server const> servers,
    std::pmr::memory_resource* memory) = 0;

Return Value

The connection, or null when the platform refuses to make one: the room then reports the negotiation with that peer as failed, through room::on_error.

Parameters

Name Description

servers

The STUN and TURN servers, read during the call.

memory

The room’s resource, which the connection, its channels, its handlers and its texts are allocated in.

webcpp::trystero::backend::factory::make_sealer

Makes a sealer under a room’s key.

Synopsis

virtual
std::shared_ptr<sealer>
make_sealer(
    std::array<uint8_t, 32> key,
    std::pmr::memory_resource* memory) = 0;

Return Value

The sealer.

Parameters

Name Description

key

The raw AES-256 key, as protocol::seal_key derives it.

memory

The room’s resource, which the sealer, its texts and its completions are allocated in.

webcpp::trystero::backend::factory::make_timer

Makes a timer.

Synopsis

virtual
std::shared_ptr<timer>
make_timer(std::pmr::memory_resource* memory) = 0;

Return Value

The timer, not started.

Parameters

Name Description

memory

The room’s resource, which the timer and its waits are allocated in.

webcpp::trystero::backend::factory::now_ms

A monotonic clock in milliseconds, for rate limits and timeouts, as performance.now().

Synopsis

virtual
double
now_ms() = 0;

Return Value

The current time, in milliseconds from an origin of the backend’s.

webcpp::trystero::backend::factory::random

Fills out with bytes from a cryptographically secure source: they become secret keys and nonces.

Synopsis

virtual
void
random(std::span<uint8_t> out) = 0;

Parameters

Name Description

out

The bytes to fill.

webcpp::trystero::backend::factory::unix_seconds

Wall-clock seconds since the Unix epoch, for Nostr’s created_at and since.

Synopsis

virtual
int64_t
unix_seconds() = 0;

Return Value

The current time, in whole seconds.

webcpp::trystero::backend::ice_server

A STUN or TURN server, an RTCIceServer of the iceServers Trystero gives RTCPeerConnection.

Synopsis

struct ice_server;

Description

A room copies those of its configuration into its own resource.

Data Members

Name Description

credential

The TURN server’s credential; empty for a STUN server.

urls

The server’s URLs, as "stun:stun.l.google.com:19302" or "turn:host:3478".

username

The TURN server’s user name; empty for a STUN server.

See Also

"Rooms and actions", in the guide.

webcpp::trystero::backend::ice_server::credential

The TURN server’s credential; empty for a STUN server.

Synopsis

std::pmr::string credential;

webcpp::trystero::backend::ice_server::urls

The server’s URLs, as "stun:stun.l.google.com:19302" or "turn:host:3478".

Synopsis

std::pmr::vector<std::pmr::string> urls;

webcpp::trystero::backend::ice_server::username

The TURN server’s user name; empty for a STUN server.

Synopsis

std::pmr::string username;

webcpp::trystero::backend::peer_connection

A WebRTC peer connection, as the RTCPeerConnection of a Trystero peer, whose negotiation the signaling drives: it makes an offer or an answer only when asked to.

Synopsis

class peer_connection;

Types

Name Description

handlers

What a peer connection reports, each on the room’s executor.

Member Functions

Name Description

~peer_connection [destructor] [virtual]

Destroys the connection, which closes it without reporting the close.

add_ice_candidate [virtual]

Adds a remote ICE candidate, like addIceCandidate().

close [virtual]

Closes the connection and its channels.

create_data_channel [virtual]

Opens a data channel, like createDataChannel().

has_remote_description [virtual]

Whether a remote description was applied, which a candidate needs before it is added.

set_handlers [virtual]

Replaces the handlers; a handler may replace them during its own call.

set_local_description [virtual]

Creates the offer or the answer the signaling state calls for and applies it, like setLocalDescription().

set_remote_description [virtual]

Applies the remote peer’s offer or answer, like setRemoteDescription().

Derived Classes

Name Description

peer_connection

A peer connection over the page’s RTCPeerConnection.

peer_connection

A peer connection of libdatachannel, the native RTCPeerConnection, with its negotiation driven by the signaling.

See Also

"Design", in the guide.

webcpp::trystero::backend::peer_connection::handlers

What a peer connection reports, each on the room’s executor.

Synopsis

struct handlers;

Data Members

Name Description

data_channel

The remote peer opened a data channel.

ice_candidate

A local ICE candidate, the JSON of an RTCIceCandidateInit, valid for the call.

state

The connection’s state changed.

webcpp::trystero::backend::peer_connection::handlers::data_channel

The remote peer opened a data channel.

webcpp::trystero::backend::peer_connection::handlers::ice_candidate

A local ICE candidate, the JSON of an RTCIceCandidateInit, valid for the call.

Synopsis

webcpp::trystero::backend::peer_connection::handlers::state

The connection’s state changed.

webcpp::trystero::backend::peer_connection::~peer_connection

Destroys the connection, which closes it without reporting the close.

Synopsis

constexpr
virtual
~peer_connection() = default;

webcpp::trystero::backend::peer_connection::add_ice_candidate

Adds a remote ICE candidate, like addIceCandidate().

Synopsis

virtual
void
add_ice_candidate(
    std::string_view candidate_json,
    handler<void(boost::system::error_code)> done) = 0;

Parameters

Name Description

candidate_json

The JSON of an RTCIceCandidateInit, read during the call.

done

Called once it is added, or with error::backend_failure when it is refused.

webcpp::trystero::backend::peer_connection::close

Closes the connection and its channels.

Synopsis

virtual
void
close() = 0;

webcpp::trystero::backend::peer_connection::create_data_channel

Opens a data channel, like createDataChannel().

Synopsis

virtual
std::shared_ptr<data_channel>
create_data_channel(std::string_view label) = 0;

Return Value

The channel, made in the connection’s resource, or null when the platform refuses to open one, as on a closed connection.

Parameters

Name Description

label

The channel’s label, read during the call.

webcpp::trystero::backend::peer_connection::has_remote_description

Whether a remote description was applied, which a candidate needs before it is added.

Synopsis

[[nodiscard]]
virtual
bool
has_remote_description() const = 0;

Return Value

Whether the connection has a remoteDescription.

Note

The return value should not be discarded.

webcpp::trystero::backend::peer_connection::set_handlers

Replaces the handlers; a handler may replace them during its own call.

Synopsis

virtual
void
set_handlers(handlers h) = 0;

Parameters

Name Description

h

The new handlers; an empty one is not called.

webcpp::trystero::backend::peer_connection::set_local_description

Creates the offer or the answer the signaling state calls for and applies it, like setLocalDescription().

Synopsis

Parameters

Name Description

done

Called with the description’s type ("offer" or "answer") and its SDP, views valid for the call, or with error::backend_failure and empty views.

webcpp::trystero::backend::peer_connection::set_remote_description

Applies the remote peer’s offer or answer, like setRemoteDescription().

Synopsis

virtual
void
set_remote_description(
    std::string_view type,
    std::string_view sdp,
    handler<void(boost::system::error_code)> done) = 0;

Parameters

Name Description

type

"offer" or "answer", read during the call.

sdp

The description’s SDP, read during the call.

done

Called once it is applied, or with error::backend_failure when it is refused.

webcpp::trystero::backend::sealer

AES-GCM under one room key, in Trystero’s sealed text form (protocol/signal.hpp), what Trystero’s encrypt and decrypt do with crypto.subtle.

Synopsis

class sealer;

Member Functions

Name Description

~sealer [destructor] [virtual]

Destroys the sealer.

open [virtual]

Opens a sealed text.

seal [virtual]

Seals a text under a fresh random IV.

Derived Classes

Name Description

sealer

AES-GCM through crypto.subtle, under a key imported once, as Trystero’s encrypt and decrypt do.

sealer

AES-GCM under one room key, through OpenSSL, in Trystero’s sealed text form, what Trystero’s encrypt and decrypt do with crypto.subtle.

See Also

"The protocol", in the guide.

webcpp::trystero::backend::sealer::~sealer

Destroys the sealer.

Synopsis

constexpr
virtual
~sealer() = default;

webcpp::trystero::backend::sealer::open

Opens a sealed text.

Synopsis

virtual
void
open(
    std::string_view sealed,
    handler<void(boost::system::error_code, std::string_view)> done) = 0;

Parameters

Name Description

sealed

The sealed text, read during the call.

done

Called with the plaintext, a view valid for the call, or with an error and an empty view: trystero::error::wrong_password when the text was sealed under another key or is not a sealed text.

webcpp::trystero::backend::sealer::seal

Seals a text under a fresh random IV.

Synopsis

virtual
void
seal(
    std::string_view plaintext,
    handler<void(boost::system::error_code, std::string_view)> done) = 0;

Parameters

Name Description

plaintext

The text, read during the call.

done

Called with the sealed text, a view valid for the call, or with error::backend_failure and an empty view.

webcpp::trystero::backend::timer

A one-shot timer, what the setTimeout calls of Trystero become.

Synopsis

class timer;

Description

Starting it again replaces the pending expiry, and destroying it cancels it.

Member Functions

Name Description

~timer [destructor] [virtual]

Destroys the timer, which cancels a pending expiry.

cancel [virtual]

Cancels the pending expiry, if any, whose handler is then never called.

start [virtual]

Calls fire on the room’s executor once delay has passed, in place of a pending expiry.

Derived Classes

Name Description

timer

A timer over the page’s setTimeout.

timer

An Asio timer whose waits are allocated in the room’s resource.

See Also

"Design", in the guide.

webcpp::trystero::backend::timer::~timer

Destroys the timer, which cancels a pending expiry.

Synopsis

constexpr
virtual
~timer() = default;

webcpp::trystero::backend::timer::cancel

Cancels the pending expiry, if any, whose handler is then never called.

Synopsis

virtual
void
cancel() = 0;

webcpp::trystero::backend::timer::start

Calls fire on the room’s executor once delay has passed, in place of a pending expiry.

Synopsis

virtual
void
start(
    std::chrono::milliseconds delay,
    handler<void()> fire) = 0;

Parameters

Name Description

delay

How long to wait.

fire

What to call when the timer expires; never called once it is cancelled.

webcpp::trystero::backend::websocket

A WebSocket to a Nostr relay, as the WebSocket Trystero’s relay manager opens.

Synopsis

class websocket;

Types

Name Description

handlers

What a WebSocket reports, each on the room’s executor.

Member Functions

Name Description

~websocket [destructor] [virtual]

Destroys the WebSocket, which closes it without reporting the close.

close [virtual]

Closes the connection, which the handler close reports.

send [virtual]

Sends a text message; one sent before the connection opens or after it closes is dropped.

Derived Classes

Name Description

websocket

A WebSocket over the page’s WebSocket.

See Also

"Design", in the guide.

webcpp::trystero::backend::websocket::handlers

What a WebSocket reports, each on the room’s executor.

Synopsis

struct handlers;

Data Members

Name Description

close

The connection closed, or failed to open: called once, also when it never opened.

message

A text message arrived; the view is valid for the duration of the call.

open

The connection opened: messages can be sent.

webcpp::trystero::backend::websocket::handlers::close

The connection closed, or failed to open: called once, also when it never opened.

Synopsis

webcpp::trystero::backend::websocket::handlers::message

A text message arrived; the view is valid for the duration of the call.

webcpp::trystero::backend::websocket::handlers::open

The connection opened: messages can be sent.

Synopsis

webcpp::trystero::backend::websocket::~websocket

Destroys the WebSocket, which closes it without reporting the close.

Synopsis

constexpr
virtual
~websocket() = default;

webcpp::trystero::backend::websocket::close

Closes the connection, which the handler close reports.

Synopsis

virtual
void
close() = 0;

webcpp::trystero::backend::websocket::send

Sends a text message; one sent before the connection opens or after it closes is dropped.

Synopsis

virtual
void
send(std::string_view text) = 0;

Parameters

Name Description

text

The message, read during the call.

webcpp::trystero::backend::connection_state

The state of a peer connection, as RTCPeerConnection reports it.

Synopsis

enum class connection_state : int;

Members

Name Description

fresh

Nothing has happened yet: "new".

connecting

The connection is being established: "connecting", or ICE’s "checking".

connected

The connection works: "connected", or ICE’s "completed".

disconnected

The connection was lost, perhaps for a moment: "disconnected".

failed

The connection failed: "failed".

closed

The connection was closed: "closed".

webcpp::trystero::backend::connection_state::fresh

Nothing has happened yet: "new".

Synopsis

fresh

webcpp::trystero::backend::connection_state::connecting

The connection is being established: "connecting", or ICE’s "checking".

Synopsis

connecting

webcpp::trystero::backend::connection_state::connected

The connection works: "connected", or ICE’s "completed".

Synopsis

connected

webcpp::trystero::backend::connection_state::disconnected

The connection was lost, perhaps for a moment: "disconnected".

Synopsis

disconnected

webcpp::trystero::backend::connection_state::failed

The connection failed: "failed".

Synopsis

failed

webcpp::trystero::backend::connection_state::closed

The connection was closed: "closed".

Synopsis

closed

webcpp::trystero::backend::error_code

The error code every completion of a backend reports, empty on success.

Synopsis

using boost::system::error_code;

webcpp::trystero::browser

The browser target: the backend interface over the page’s own WebSocket, RTCPeerConnection, RTCDataChannel, setTimeout and crypto.subtle, through emscripten::val. It needs a secure context (https, or http on localhost), where crypto.subtle exists.

Description

Tip: its objects, their handlers and the frames and texts they hand to the room are in the room’s resource; what emscripten::val holds is in the JavaScript heap, and the page’s event_loop keeps its registrations in its own resource.

Types

Name Description

backend

The browser’s factory: one per page, beside the page’s event_loop.

data_channel

A data channel over the page’s RTCDataChannel, its messages received as ArrayBuffer objects.

event_loop

The bridge between the page’s JavaScript event loop and an Asio io_context, which never runs on its own in a browser: it is polled after every JavaScript event that enters C++, and when work is posted from outside one.

executor

An Asio executor over an event_loop: what it is given runs on the loop’s io_context.

peer_connection

A peer connection over the page’s RTCPeerConnection.

sealer

AES-GCM through crypto.subtle, under a key imported once, as Trystero’s encrypt and decrypt do.

timer

A timer over the page’s setTimeout.

websocket

A WebSocket over the page’s WebSocket.

Functions

Name Description

depth

The function JavaScript calls, trystero_depth, before it enters C++ through trystero_dispatch: the depth trystero_recover restores if the call escapes.

dispatch

The one function JavaScript calls, trystero_dispatch, which browser/src.hpp exports.

recover

The function JavaScript calls, trystero_recover, when an exception escaped trystero_dispatch: the loop recovers, as event_loop::recover says.

operator==

Whether two executors are of one loop.

operator!=

Whether two executors are of different loops.

Using Declarations

Name Description

val

A JavaScript value, embind’s emscripten::val.

webcpp::trystero::browser::backend

The browser’s factory: one per page, beside the page’s event_loop.

Synopsis

class backend
    : public trystero::backend::factory

Description

It makes peer connections, data channels and received frames in app_config::memory, and WebSockets, sealers and room timers in the room’s effective room_options::memory. Objects, handlers and registrations use the resource supplied to their factory method. What emscripten::val holds is in the JavaScript heap. It needs a secure context, where crypto.subtle exists.

Base Classes

Name Description

trystero::backend::factory

Makes everything above, and gives a room its randomness and its clocks: one per page in the browser, one per application natively, one per test.

Member Functions

Name Description

backend [constructor]

Constructs the page’s backend.

connect [virtual]

Opens a WebSocket to a relay, with the page’s WebSocket.

make_peer_connection [virtual]

Makes a peer connection, with the page’s RTCPeerConnection.

make_sealer [virtual]

Makes a sealer, over crypto.subtle.

make_timer [virtual]

Makes a timer, over setTimeout.

now_ms [virtual]

Milliseconds from performance.now().

random [virtual]

Fills out with the page’s entropy, which Emscripten draws from crypto.getRandomValues.

unix_seconds [virtual]

Seconds since the Unix epoch, from Date.now().

Static Data Members

Name Description

max_peer_connections

The most RTCPeerConnection objects the page’s rooms hold, every backend of the page together, counted from their construction until the garbage collector frees them: half of the 500 Chromium allows a page, which it counts the same way, so that a closed connection still counts until a collection the page cannot ask for.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::backend::backend

Constructs the page’s backend.

Synopsis

explicit
backend(event_loop& loop);

Parameters

Name Description

loop

The page’s loop, which must outlive the backend and every object it makes.

webcpp::trystero::browser::backend::connect

Opens a WebSocket to a relay, with the page’s WebSocket.

Synopsis

virtual
std::shared_ptr<trystero::backend::websocket>
connect(
    std::string_view url,
    trystero::backend::websocket::handlers h,
    std::pmr::memory_resource* memory) override;

Return Value

The WebSocket.

Parameters

Name Description

url

The relay’s URL, read during the call.

h

What the WebSocket reports.

memory

The room’s resource, which the WebSocket, its handlers and its texts are allocated in.

webcpp::trystero::browser::backend::make_peer_connection

Makes a peer connection, with the page’s RTCPeerConnection.

Synopsis

virtual
std::shared_ptr<trystero::backend::peer_connection>
make_peer_connection(
    std::span<trystero::backend::ice_server const> servers,
    std::pmr::memory_resource* memory) override;

Return Value

The connection, or null when the page holds max_peer_connections, or when the browser refuses to make one, as peer_connection::create says.

Parameters

Name Description

servers

The STUN and TURN servers, read during the call.

memory

The app’s resource, which the connection, its channels, its handlers and its texts are allocated in.

webcpp::trystero::browser::backend::make_sealer

Makes a sealer, over crypto.subtle.

Synopsis

virtual
std::shared_ptr<trystero::backend::sealer>
make_sealer(
    std::array<uint8_t, 32> key,
    std::pmr::memory_resource* memory) override;

Return Value

The sealer.

Parameters

Name Description

key

The raw AES-256 key.

memory

The room’s resource, which the sealer, its texts and its completions are allocated in.

webcpp::trystero::browser::backend::make_timer

Makes a timer, over setTimeout.

Synopsis

virtual
std::shared_ptr<trystero::backend::timer>
make_timer(std::pmr::memory_resource* memory) override;

Return Value

The timer, not started.

Parameters

Name Description

memory

The room’s resource, which the timer and its handler are allocated in.

webcpp::trystero::browser::backend::now_ms

Milliseconds from performance.now().

Synopsis

virtual
double
now_ms() override;

Return Value

The current time, in milliseconds since the page’s time origin.

webcpp::trystero::browser::backend::random

Fills out with the page’s entropy, which Emscripten draws from crypto.getRandomValues.

Synopsis

virtual
void
random(std::span<uint8_t> out) override;

Parameters

Name Description

out

The bytes to fill.

webcpp::trystero::browser::backend::unix_seconds

Seconds since the Unix epoch, from Date.now().

Synopsis

virtual
int64_t
unix_seconds() override;

Return Value

The current time, in whole seconds.

webcpp::trystero::browser::backend::max_peer_connections

The most RTCPeerConnection objects the page’s rooms hold, every backend of the page together, counted from their construction until the garbage collector frees them: half of the 500 Chromium allows a page, which it counts the same way, so that a closed connection still counts until a collection the page cannot ask for.

Synopsis

inline constexpr static
std::size_t max_peer_connections = 256;

Description

Tip: a room makes at most 64 connections in any 57 s, so floods of four rooms reach this within a minute when no collection runs; the page then makes none until one does, and its rooms report each negotiation they cannot start, while the rest of Chromium’s count stays free for what the page makes without a room.

webcpp::trystero::browser::data_channel

A data channel over the page’s RTCDataChannel, its messages received as ArrayBuffer objects.

Synopsis

class data_channel
    : public backend::data_channel

Description

A message that is not an ArrayBuffer, as a text one a peer sends, or that is larger than any room frame (protocol::max_room_frame_bytes), is dropped before it is read: neither can be a frame, and reading a text as bytes would make a typed array of the length its text says.

Base Classes

Name Description

backend::data_channel

A WebRTC data channel, as the RTCDataChannel a Trystero peer exchanges its frames over.

Member Functions

Name Description

data_channel [constructor]

Wraps a channel of the page, and takes over its events.

~data_channel [destructor] [virtual]

Destroys the wrapper, which closes the channel without reporting the close.

buffered_amount [virtual]

The bytes queued to send.

close [virtual]

Closes the channel, which the handler close reports.

is_open [virtual]

Whether the channel is open.

send [virtual]

Sends a binary message, copied into a new Uint8Array, when the channel is open; one the channel refuses, as one larger than its peer accepts, is dropped.

set_handlers [virtual]

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::data_channel::data_channel

Wraps a channel of the page, and takes over its events.

Synopsis

data_channel(
    event_loop& loop,
    emscripten::val channel,
    std::pmr::memory_resource* memory);

Parameters

Name Description

loop

The page’s loop, which must outlive the channel.

channel

The RTCDataChannel.

memory

The app’s resource, which the channel’s handlers, registration and received frames are allocated in.

webcpp::trystero::browser::data_channel::~data_channel

Destroys the wrapper, which closes the channel without reporting the close.

Synopsis

virtual
~data_channel() override;

webcpp::trystero::browser::data_channel::buffered_amount

The bytes queued to send.

Synopsis

[[nodiscard]]
virtual
std::size_t
buffered_amount() const override;

Return Value

Its bufferedAmount.

Note

The return value should not be discarded.

webcpp::trystero::browser::data_channel::close

Closes the channel, which the handler close reports.

Synopsis

virtual
void
close() override;

webcpp::trystero::browser::data_channel::is_open

Whether the channel is open.

Synopsis

[[nodiscard]]
virtual
bool
is_open() const override;

Return Value

Whether its readyState is "open".

Note

The return value should not be discarded.

webcpp::trystero::browser::data_channel::send

Sends a binary message, copied into a new Uint8Array, when the channel is open; one the channel refuses, as one larger than its peer accepts, is dropped.

Synopsis

virtual
void
send(std::span<uint8_t const> frame) override;

Parameters

Name Description

frame

The message, read during the call.

webcpp::trystero::browser::data_channel::set_handlers

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

Synopsis

virtual
void
set_handlers(handlers h) override;

Parameters

Name Description

h

The new handlers, kept in the channel’s resource.

webcpp::trystero::browser::event_loop

The bridge between the page’s JavaScript event loop and an Asio io_context, which never runs on its own in a browser: it is polled after every JavaScript event that enters C++, and when work is posted from outside one.

Synopsis

class event_loop;

Description

JavaScript reaches C++ through handler(id, slot): a function bound to this loop, a registration and a slot, passed to trystero_dispatch (browser/src.hpp). An object registers a callback with add and removes it with remove when it is destroyed, so a late event for a destroyed object finds nothing. A loop must outlive the page’s use of it; its destructor marks every handler it made as dead.

Tip: a loop serves every room of the page, so the entries of its registrations are in the resource it is constructed with; a registered callback stays in its own allocator’s resource, a room’s for a room’s object, until it is removed or, for a promise, until the promise settles.

Type Aliases

Name Description

callback

What a registration calls: with the slot the event came through, and the event.

Member Functions

Name Description

event_loop [constructor]

Constructors

~event_loop [destructor]

Destroys the loop, and marks every handler it made as dead: their events are dropped.

operator= [deleted]

Not copy-assignable: the page’s functions name this loop by its address.

add

Registers a callback, which JavaScript reaches through handler.

depth

How many calls into C++ are running: 0 between events, 1 inside a callback.

dispatch

Runs a registration’s callback for a JavaScript event, then everything that became ready.

get_executor

An executor of this loop, what a room in the browser is constructed with.

handler

A JavaScript function of one argument that delivers it to registration id, slot slot.

io

The io_context the loop polls.

poll

Runs every ready handler; what they post runs too.

recover

Brings the loop back after an exception escaped a call into C++ that entered at depth: the calls it unwound no longer count, and a poll runs the work that is ready.

remove

Removes a registration: an event for it from now on finds nothing.

schedule_poll

Schedules a poll() in a microtask unless one is pending or a poll is running, which will see the work.

text

A JavaScript string of UTF-8 text, decoded by a TextDecoder straight from the wasm memory the view points into.

utf8

utf8 overloads

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::event_loop::callback

What a registration calls: with the slot the event came through, and the event.

webcpp::trystero::browser::event_loop::event_loop

Constructors

Synopses

Not copyable: the page’s functions name this loop by its address.

event_loop(event_loop const&) = delete;

Constructs a loop over io, which it keeps from running out of work.

explicit
event_loop(
    boost::asio::io_context& io,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Parameters

Name Description

io

The io_context the loop polls, which must outlive the loop.

memory

The resource the entries of the registrations are allocated in; it must outlive the loop.

webcpp::trystero::browser::event_loop::event_loop

Not copyable: the page’s functions name this loop by its address.

Synopsis

event_loop(event_loop const&) = delete;

webcpp::trystero::browser::event_loop::event_loop

Constructs a loop over io, which it keeps from running out of work.

Synopsis

explicit
event_loop(
    boost::asio::io_context& io,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Parameters

Name Description

io

The io_context the loop polls, which must outlive the loop.

memory

The resource the entries of the registrations are allocated in; it must outlive the loop.

webcpp::trystero::browser::event_loop::~event_loop

Destroys the loop, and marks every handler it made as dead: their events are dropped.

Synopsis

~event_loop();

webcpp::trystero::browser::event_loop::operator=

Not copy-assignable: the page’s functions name this loop by its address.

Synopsis

event_loop&
operator=(event_loop const&) = delete;

webcpp::trystero::browser::event_loop::add

Registers a callback, which JavaScript reaches through handler.

Synopsis

int
add(callback f);

Description

The callback stays in its own allocator’s resource (a room’s, for a room’s object); only the registration’s entry is the loop’s.

Return Value

The registration’s id, which handler and remove take.

Parameters

Name Description

f

The callback.

webcpp::trystero::browser::event_loop::depth

How many calls into C++ are running: 0 between events, 1 inside a callback.

Synopsis

[[nodiscard]]
int
depth() const noexcept;

Return Value

The depth.

Note

The return value should not be discarded.

webcpp::trystero::browser::event_loop::dispatch

Runs a registration’s callback for a JavaScript event, then everything that became ready.

Synopsis

void
dispatch(
    int id,
    int slot,
    emscripten::val event);

Description

An event the page delivers while C++ is already running, inside a call C++ made into JavaScript (a WebSocket that fires close from inside close(), as node’s does), is posted rather than run, so that a handler never runs inside the call that caused it.

Parameters

Name Description

id

The registration’s id; an unknown one runs nothing.

slot

The slot the event came through.

event

The event.

webcpp::trystero::browser::event_loop::get_executor

An executor of this loop, what a room in the browser is constructed with.

Synopsis

executor
get_executor() noexcept;

Return Value

The executor.

webcpp::trystero::browser::event_loop::handler

A JavaScript function of one argument that delivers it to registration id, slot slot.

Synopsis

[[nodiscard]]
emscripten::val
handler(
    int id,
    int slot) const;

Description

An exception that escapes the call, which no operation of trystero raises, is reported to the page (reportError) and does not stop the loop: the loop recovers, as recover says, and the function returns.

Return Value

The function, which calls trystero_dispatch with this loop, id, slot and its argument (browser/src.hpp).

Note

The return value should not be discarded.

Parameters

Name Description

id

The registration’s id.

slot

A number the callback receives, which tells its events apart.

webcpp::trystero::browser::event_loop::io

The io_context the loop polls.

Synopsis

boost::asio::io_context&
io() noexcept;

Return Value

The io_context it was constructed with.

webcpp::trystero::browser::event_loop::poll

Runs every ready handler; what they post runs too.

Synopsis

void
poll();

webcpp::trystero::browser::event_loop::recover

Brings the loop back after an exception escaped a call into C++ that entered at depth: the calls it unwound no longer count, and a poll runs the work that is ready.

Synopsis

void
recover(int depth);

Description

Tip: without WebAssembly exceptions no destructor runs while an exception unwinds, so the depth a call raised stays raised; the page’s function reads the depth before it enters C++ and gives it back here, so that a nested entry that escapes leaves the depth of the call still running below it, never less. The page’s function also puts WebAssembly’s stack pointer back where it was before the call, which no epilogue of the unwound frames did, but what those frames owned on the heap stays allocated: 312 bytes per escape, measured, for a frame that held a string of 300. With exceptions, trystero_dispatch catches a C++ exception itself, which frees it and runs every destructor, and recovers the same way.

Parameters

Name Description

depth

The depth when the call entered, as depth() read it; never below 0.

webcpp::trystero::browser::event_loop::remove

Removes a registration: an event for it from now on finds nothing.

Synopsis

void
remove(int id);

Parameters

Name Description

id

The registration’s id.

webcpp::trystero::browser::event_loop::schedule_poll

Schedules a poll() in a microtask unless one is pending or a poll is running, which will see the work.

Synopsis

void
schedule_poll();

webcpp::trystero::browser::event_loop::text

A JavaScript string of UTF-8 text, decoded by a TextDecoder straight from the wasm memory the view points into.

Synopsis

[[nodiscard]]
emscripten::val
text(std::string_view utf8) const;

Description

No std::string is made to carry it across, as emscripten::val(std::string) would.

Return Value

The JavaScript string.

Note

The return value should not be discarded.

Parameters

Name Description

utf8

The text, read during the call.

webcpp::trystero::browser::event_loop::utf8

utf8 overloads

Synopses

The UTF-8 bytes of a JavaScript string, encoded by a TextEncoder and copied straight into a string of memory.

std::pmr::string
utf8(
    emscripten::val const& text,
    std::pmr::memory_resource* memory) const;

The UTF-8 bytes of a JavaScript string, as the other overload makes them, when they are at most max_bytes; nothing is copied into WebAssembly otherwise.

std::optional<std::pmr::string>
utf8(
    emscripten::val const& text,
    std::pmr::memory_resource* memory,
    std::size_t max_bytes) const;

Return Value

  • The text, or an empty one when the string cannot be encoded.

  • The text, or nothing when it is longer or cannot be encoded.

Parameters

Name Description

text

The JavaScript string.

memory

The resource the copy is allocated in.

max_bytes

The most bytes the text may hold.

webcpp::trystero::browser::event_loop::utf8

The UTF-8 bytes of a JavaScript string, encoded by a TextEncoder and copied straight into a string of memory.

Synopsis

std::pmr::string
utf8(
    emscripten::val const& text,
    std::pmr::memory_resource* memory) const;

Description

No std::string is made to carry them across, as emscripten::val::as<std::string>() would.

Return Value

The text, or an empty one when the string cannot be encoded.

Parameters

Name Description

text

The JavaScript string.

memory

The resource the copy is allocated in.

webcpp::trystero::browser::event_loop::utf8

The UTF-8 bytes of a JavaScript string, as the other overload makes them, when they are at most max_bytes; nothing is copied into WebAssembly otherwise.

Synopsis

std::optional<std::pmr::string>
utf8(
    emscripten::val const& text,
    std::pmr::memory_resource* memory,
    std::size_t max_bytes) const;

Description

The string’s length is checked first, in UTF-16 units, each at least one byte, so that a longer string is never encoded; then the encoded bytes are.

Return Value

The text, or nothing when it is longer or cannot be encoded.

Parameters

Name Description

text

The JavaScript string.

memory

The resource the copy is allocated in.

max_bytes

The most bytes the text may hold.

webcpp::trystero::browser::executor

An Asio executor over an event_loop: what it is given runs on the loop’s io_context.

Synopsis

class executor;

Description

Giving it work while no JavaScript event is being handled schedules one poll() in a microtask, so posted work runs without a polling loop and without the application calling poll(). A room in the browser is constructed with it, through boost::asio::any_io_executor.

Member Functions

Name Description

executor [constructor]

Constructs an executor of loop.

execute

Posts f to the loop’s io_context, and schedules a poll unless one is coming.

query

The execution context, the property Asio queries.

require

The executor with blocking.never required, which it already is.

Static Member Functions

Name Description

query

The blocking property, which Asio queries: execute never runs the work inside the call.

Friends

Name Description

webcpp::trystero::browser::operator!=

Whether two executors are of different loops.

webcpp::trystero::browser::operator==

Whether two executors are of one loop.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::executor::executor

Constructs an executor of loop.

Synopsis

explicit
executor(event_loop& loop) noexcept;

Parameters

Name Description

loop

The loop, which must outlive the executor and every copy of it.

webcpp::trystero::browser::executor::execute

Posts f to the loop’s io_context, and schedules a poll unless one is coming.

Synopsis

template<class F>
void
execute(F&& f) const;

Template Parameters

Name Description

F

A nullary callable.

Parameters

Name Description

f

The work, allocated as its associated allocator says.

webcpp::trystero::browser::executor::query

The execution context, the property Asio queries.

Synopsis

[[nodiscard]]
boost::asio::execution_context&
query(boost::asio::execution::context_t) const noexcept;

Return Value

The loop’s io_context.

Note

The return value should not be discarded.

webcpp::trystero::browser::executor::require

The executor with blocking.never required, which it already is.

Synopsis

[[nodiscard]]
executor
require(boost::asio::execution::blocking_t::never_t) const noexcept;

Return Value

A copy of this executor.

Note

The return value should not be discarded.

webcpp::trystero::browser::executor::query

The blocking property, which Asio queries: execute never runs the work inside the call.

Synopsis

constexpr
static
boost::asio::execution::blocking_t::never_t
query(boost::asio::execution::blocking_t) noexcept;

Return Value

blocking.never.

webcpp::trystero::browser::peer_connection

A peer connection over the page’s RTCPeerConnection.

Synopsis

class peer_connection
    : public backend::peer_connection
    , public std::enable_shared_from_this<peer_connection>

Description

The state it reports merges its connectionState and its iceConnectionState.

Base Classes

Name Description

backend::peer_connection

A WebRTC peer connection, as the RTCPeerConnection of a Trystero peer, whose negotiation the signaling drives: it makes an offer or an answer only when asked to.

std::enable_shared_from_this<peer_connection>

Member Functions

Name Description

peer_connection [constructor]

Wraps a connection of the page, and takes over its events; create makes one.

~peer_connection [destructor] [virtual]

Destroys the wrapper, which closes the connection without reporting the close.

add_ice_candidate [virtual]

Adds a remote ICE candidate, with addIceCandidate().

close [virtual]

Closes the connection and its channels.

create_data_channel [virtual]

Opens a data channel with createDataChannel().

has_remote_description [virtual]

Whether a remote description was applied.

native_handle

The browser’s own RTCPeerConnection, what media will be added to.

set_handlers [virtual]

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

set_local_description [virtual]

Creates and applies the offer or the answer the signaling state calls for, with setLocalDescription().

set_remote_description [virtual]

Applies the remote peer’s offer or answer, with setRemoteDescription().

Static Member Functions

Name Description

create

Makes an RTCPeerConnection with servers as its iceServers.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::peer_connection::peer_connection

Wraps a connection of the page, and takes over its events; create makes one.

Synopsis

peer_connection(
    event_loop& loop,
    emscripten::val connection,
    std::pmr::memory_resource* memory);

Parameters

Name Description

loop

The page’s loop, which must outlive the connection.

connection

The RTCPeerConnection.

memory

The app’s resource, which the connection’s handlers, registration, channels and texts are allocated in.

webcpp::trystero::browser::peer_connection::~peer_connection

Destroys the wrapper, which closes the connection without reporting the close.

Synopsis

virtual
~peer_connection() override;

webcpp::trystero::browser::peer_connection::add_ice_candidate

Adds a remote ICE candidate, with addIceCandidate().

Synopsis

virtual
void
add_ice_candidate(
    std::string_view candidate_json,
    handler<void(boost::system::error_code)> done) override;

Description

The text comes from a peer: one JSON.parse refuses is dropped, as a candidate the browser refuses is.

Parameters

Name Description

candidate_json

The JSON of an RTCIceCandidateInit, read during the call.

done

Called once the candidate is added, or with error::backend_failure when the text is not JSON or the browser refuses the candidate.

webcpp::trystero::browser::peer_connection::close

Closes the connection and its channels.

Synopsis

virtual
void
close() override;

webcpp::trystero::browser::peer_connection::create_data_channel

Opens a data channel with createDataChannel().

Synopsis

virtual
std::shared_ptr<backend::data_channel>
create_data_channel(std::string_view label) override;

Return Value

The channel, made in the connection’s resource, or null when the browser refuses to open one, as on a closed connection.

Parameters

Name Description

label

The channel’s label, read during the call.

webcpp::trystero::browser::peer_connection::has_remote_description

Whether a remote description was applied.

Synopsis

virtual
bool
has_remote_description() const override;

Return Value

Whether its remoteDescription is not null.

webcpp::trystero::browser::peer_connection::native_handle

The browser’s own RTCPeerConnection, what media will be added to.

Synopsis

emscripten::val const&
native_handle() const;

Return Value

The connection.

See Also

"Roadmap", in the guide.

webcpp::trystero::browser::peer_connection::set_handlers

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

Synopsis

virtual
void
set_handlers(handlers h) override;

Parameters

Name Description

h

The new handlers, kept in the connection’s resource.

webcpp::trystero::browser::peer_connection::set_local_description

Creates and applies the offer or the answer the signaling state calls for, with setLocalDescription().

Synopsis

virtual
void
set_local_description(handler<void(boost::system::error_code, std::string_view, std::string_view)> done) override;

Parameters

Name Description

done

Called once the promise settles, with the localDescription’s type and SDP, or with error::backend_failure when it is rejected or the connection is gone.

webcpp::trystero::browser::peer_connection::set_remote_description

Applies the remote peer’s offer or answer, with setRemoteDescription().

Synopsis

virtual
void
set_remote_description(
    std::string_view type,
    std::string_view sdp,
    handler<void(boost::system::error_code)> done) override;

Parameters

Name Description

type

"offer" or "answer", read during the call.

sdp

The description’s SDP, read during the call.

done

Called once the promise settles, or with error::backend_failure when it is rejected.

webcpp::trystero::browser::peer_connection::create

Makes an RTCPeerConnection with servers as its iceServers.

Synopsis

static
std::shared_ptr<peer_connection>
create(
    event_loop& loop,
    std::span<backend::ice_server const> servers,
    std::pmr::memory_resource* memory);

Return Value

The connection, or null when the browser refuses to make one: it throws for a server it cannot use, and when the page holds as many connections as it allows. The page’s census counts the connection made, until the garbage collector frees it.

Parameters

Name Description

loop

The page’s loop, which must outlive the connection.

servers

The STUN and TURN servers, read during the call.

memory

The app’s resource, which the connection, its handlers, registration, channels and texts are allocated in.

webcpp::trystero::browser::sealer

AES-GCM through crypto.subtle, under a key imported once, as Trystero’s encrypt and decrypt do.

Synopsis

class sealer
    : public backend::sealer
    , public std::enable_shared_from_this<sealer>

Description

The text is copied into JavaScript during the call; an operation that arrives while the key is still being imported waits with a copy of its text, in the room’s resource. Without crypto.subtle, outside a secure context, every operation fails.

Base Classes

Name Description

backend::sealer

AES-GCM under one room key, in Trystero’s sealed text form (protocol/signal.hpp), what Trystero’s encrypt and decrypt do with crypto.subtle.

std::enable_shared_from_this<sealer>

Type Aliases

Name Description

done_handler

What a seal or an open completes with: an error, or the text, a view valid for the call.

Member Functions

Name Description

sealer [constructor]

Constructs a sealer under a room’s key, which it imports on its first operation.

open [virtual]

Opens a sealed text, with crypto.subtle.decrypt.

seal [virtual]

Seals a text under a fresh random IV, with crypto.subtle.encrypt.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::sealer::done_handler

What a seal or an open completes with: an error, or the text, a view valid for the call.

webcpp::trystero::browser::sealer::sealer

Constructs a sealer under a room’s key, which it imports on its first operation.

Synopsis

sealer(
    event_loop& loop,
    std::array<uint8_t, 32> key,
    std::pmr::memory_resource* memory);

Parameters

Name Description

loop

The page’s loop, which must outlive the sealer’s pending promises.

key

The raw AES-256 key, protocol::seal_key’s.

memory

The room’s resource, which its texts, its waiting operations and its completions are allocated in.

webcpp::trystero::browser::sealer::open

Opens a sealed text, with crypto.subtle.decrypt.

Synopsis

virtual
void
open(
    std::string_view sealed,
    done_handler done) override;

Parameters

Name Description

sealed

The sealed text, read during the call.

done

Called with the plaintext, or with error::wrong_password when the text was sealed under another key or is not a sealed text, and error::backend_failure when the key cannot be imported.

webcpp::trystero::browser::sealer::seal

Seals a text under a fresh random IV, with crypto.subtle.encrypt.

Synopsis

virtual
void
seal(
    std::string_view plaintext,
    done_handler done) override;

Parameters

Name Description

plaintext

The text, read during the call.

done

Called with the sealed text, or with error::backend_failure when the key cannot be imported or the encryption fails.

webcpp::trystero::browser::timer

A timer over the page’s setTimeout.

Synopsis

class timer
    : public backend::timer

Base Classes

Name Description

backend::timer

A one-shot timer, what the setTimeout calls of Trystero become.

Member Functions

Name Description

timer [constructor]

Constructs a timer, not started, registered with the loop.

~timer [destructor] [virtual]

Destroys the timer, which clears a pending setTimeout.

cancel [virtual]

Clears the pending setTimeout, if any, whose handler is then never called.

start [virtual]

Calls fire once delay has passed, through setTimeout, in place of a pending expiry.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::timer::timer

Constructs a timer, not started, registered with the loop.

Synopsis

timer(
    event_loop& loop,
    std::pmr::memory_resource* memory);

Parameters

Name Description

loop

The page’s loop, which must outlive the timer.

memory

The room’s resource, which the timer’s registration and handler are allocated in.

webcpp::trystero::browser::timer::~timer

Destroys the timer, which clears a pending setTimeout.

Synopsis

virtual
~timer() override;

webcpp::trystero::browser::timer::cancel

Clears the pending setTimeout, if any, whose handler is then never called.

Synopsis

virtual
void
cancel() override;

webcpp::trystero::browser::timer::start

Calls fire once delay has passed, through setTimeout, in place of a pending expiry.

Synopsis

virtual
void
start(
    std::chrono::milliseconds delay,
    handler<void()> fire) override;

Parameters

Name Description

delay

How long to wait.

fire

What to call when the timer expires; never called once it is cancelled.

webcpp::trystero::browser::websocket

A WebSocket over the page’s WebSocket.

Synopsis

class websocket
    : public backend::websocket

Description

A text longer than protocol::max_relay_message_bytes is not copied into WebAssembly: the socket closes, and reports the close. A text that would take the socket’s bufferedAmount beyond protocol::max_queued_relay_bytes, as when a relay stops reading, drops the socket, which reports one close at once. A page has no idle timeout: its WebSocket cannot ping.

Base Classes

Name Description

backend::websocket

A WebSocket to a Nostr relay, as the WebSocket Trystero’s relay manager opens.

Member Functions

Name Description

websocket [constructor]

Opens a WebSocket to url.

~websocket [destructor] [virtual]

Destroys the WebSocket, which closes it without reporting the close.

close [virtual]

Closes the socket, which the handler close reports; one never made has reported it.

send [virtual]

Sends a text message when the socket is open, its readyState 1; otherwise, or when the socket refuses it, drops it.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::websocket::websocket

Opens a WebSocket to url.

Synopsis

websocket(
    event_loop& loop,
    std::string_view url,
    handlers h,
    std::pmr::memory_resource* memory);

Description

A URL the page’s WebSocket refuses with an exception (one that does not parse, another scheme, a fragment, mixed content, a policy of the page’s) makes no socket: the handler close reports it after the call, as it reports a relay that cannot be reached.

Parameters

Name Description

loop

The page’s loop, which must outlive the WebSocket.

url

The relay’s URL, read during the call.

h

What the WebSocket reports.

memory

The room’s resource, which the WebSocket’s handlers, registration and received texts are allocated in.

webcpp::trystero::browser::websocket::~websocket

Destroys the WebSocket, which closes it without reporting the close.

Synopsis

virtual
~websocket() override;

webcpp::trystero::browser::websocket::close

Closes the socket, which the handler close reports; one never made has reported it.

Synopsis

virtual
void
close() override;

webcpp::trystero::browser::websocket::send

Sends a text message when the socket is open, its readyState 1; otherwise, or when the socket refuses it, drops it.

Synopsis

virtual
void
send(std::string_view text) override;

Description

A text that would take the bytes the socket holds unsent beyond protocol::max_queued_relay_bytes drops the socket instead, which reports its close.

Parameters

Name Description

text

The message, read during the call.

webcpp::trystero::browser::depth

The function JavaScript calls, trystero_depth, before it enters C++ through trystero_dispatch: the depth trystero_recover restores if the call escapes.

Synopsis

int
depth(
    emscripten::val const& token,
    uintptr_t loop);

Return Value

The loop’s depth, or 0 for a destroyed loop.

Parameters

Name Description

token

The loop’s token, whose alive is false once the loop is destroyed.

loop

The loop’s address, as a number.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::dispatch

The one function JavaScript calls, trystero_dispatch, which browser/src.hpp exports.

Synopsis

void
dispatch(
    emscripten::val const& token,
    uintptr_t loop,
    int id,
    int slot,
    emscripten::val event);

Description

Events for a destroyed loop are dropped. With exceptions, a C++ exception that escapes the callback stops here: it is reported to the page, as event_loop::handler says, and the loop recovers.

Parameters

Name Description

token

The loop’s token, whose alive is false once the loop is destroyed.

loop

The loop’s address, as a number.

id

The registration’s id.

slot

The slot the event came through.

event

The event.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::recover

The function JavaScript calls, trystero_recover, when an exception escaped trystero_dispatch: the loop recovers, as event_loop::recover says.

Synopsis

void
recover(
    emscripten::val const& token,
    uintptr_t loop,
    int depth);

Description

Calls for a destroyed loop do nothing.

Parameters

Name Description

token

The loop’s token, whose alive is false once the loop is destroyed.

loop

The loop’s address, as a number.

depth

The loop’s depth when the call that escaped entered, as trystero_depth read it.

See Also

"The browser backend", in the guide.

webcpp::trystero::browser::operator==

Whether two executors are of one loop.

Synopsis

bool
operator==(
    executor const& a,
    executor const& b) noexcept;
Note

This function is a hidden friend of executor, declared only inside that class: it can be found only through argument-dependent lookup.

Return Value

Whether they run work on the same loop.

Parameters

Name Description

a

An executor.

b

Another.

webcpp::trystero::browser::operator!=

Whether two executors are of different loops.

Synopsis

bool
operator!=(
    executor const& a,
    executor const& b) noexcept;
Note

This function is a hidden friend of executor, declared only inside that class: it can be found only through argument-dependent lookup.

Return Value

Whether they run work on different loops.

Parameters

Name Description

a

An executor.

b

Another.

webcpp::trystero::browser::val

A JavaScript value, embind’s emscripten::val.

Synopsis

using emscripten::val;

webcpp::trystero::native

The native target: the backend interface over libdatachannel for WebRTC, Boost.Beast for the relays’ WebSockets, OpenSSL for TLS and AES-GCM, and Asio for timers. Everything it reports runs on the executor it is given; the application runs that executor’s context, blocking on it or polling it.

Types

Name Description

backend

The native factory: one per application, beside the executor it runs on.

backend_config

What a native backend makes its peer connections with, beside the app’s app_config::ice_servers.

data_channel

A data channel of libdatachannel, the native RTCDataChannel.

peer_connection

A peer connection of libdatachannel, the native RTCPeerConnection, with its negotiation driven by the signaling.

sealer

AES-GCM under one room key, through OpenSSL, in Trystero’s sealed text form, what Trystero’s encrypt and decrypt do with crypto.subtle.

timer

An Asio timer whose waits are allocated in the room’s resource.

websocket_limits

What a native WebSocket tolerates of a relay that stops reading or answering.

Functions

Name Description

connect_websocket

Connects to a Nostr relay over Boost.Beast: ws:// in the clear, wss:// over TLS with tls.

webcpp::trystero::native::backend

The native factory: one per application, beside the executor it runs on.

Synopsis

class backend
    : public trystero::backend::factory

Description

It makes peer connections, data channels and received frames in app_config::memory, and WebSockets, sealers and room timers in the room’s effective room_options::memory. Objects and their handlers use the resource supplied to their factory method. What libdatachannel and OpenSSL allocate stays theirs. Its peer connections need exceptions: without them (WEBCPP_TRYSTERO_NO_EXCEPTIONS) it does not exist.

Base Classes

Name Description

trystero::backend::factory

Makes everything above, and gives a room its randomness and its clocks: one per page in the browser, one per application natively, one per test.

Member Functions

Name Description

backend [constructor]

A backend whose TLS verifies relays against the system’s certificate authorities.

connect [virtual]

Opens a WebSocket to a relay over Boost.Beast, with connect_websocket.

make_peer_connection [virtual]

Makes a peer connection of libdatachannel, bound to backend_config::bind_address.

make_sealer [virtual]

Makes a sealer over OpenSSL’s AES-256-GCM.

make_timer [virtual]

Makes an Asio timer on the backend’s executor.

now_ms [virtual]

Milliseconds from the standard library’s steady clock.

random [virtual]

Fills out from OpenSSL’s random generator; the program aborts when it has no secure randomness.

tls_context

The TLS configuration of the relays’ connections, for an application with authorities of its own.

unix_seconds [virtual]

Seconds since the Unix epoch, from the system’s wall clock.

See Also

"The native backend", in the guide.

webcpp::trystero::native::backend::backend

A backend whose TLS verifies relays against the system’s certificate authorities.

Synopsis

explicit
backend(
    boost::asio::any_io_executor ex,
    backend_config config = {});

Exceptions

Name Thrown on

std::exception

A boost::system::system_error when OpenSSL cannot make the TLS context or load the system’s certificate authorities.

Parameters

Name Description

ex

The executor everything the backend reports runs on.

config

What its peer connections are made with; the default binds them to no address.

webcpp::trystero::native::backend::connect

Opens a WebSocket to a relay over Boost.Beast, with connect_websocket.

Synopsis

virtual
std::shared_ptr<trystero::backend::websocket>
connect(
    std::string_view url,
    trystero::backend::websocket::handlers h,
    std::pmr::memory_resource* memory) override;

Return Value

The WebSocket.

Parameters

Name Description

url

The relay’s ws:// or wss:// URL, read during the call.

h

What the WebSocket reports.

memory

The room’s resource, which the WebSocket, its handlers and its queued texts are allocated in.

webcpp::trystero::native::backend::make_peer_connection

Makes a peer connection of libdatachannel, bound to backend_config::bind_address.

Synopsis

virtual
std::shared_ptr<trystero::backend::peer_connection>
make_peer_connection(
    std::span<trystero::backend::ice_server const> servers,
    std::pmr::memory_resource* memory) override;

Return Value

The connection, or null when the backend made backend_config::max_peer_connections_per_minute in the last minute, or when libdatachannel refuses to make one, by throwing.

Parameters

Name Description

servers

The STUN and TURN servers, read during the call.

memory

The app’s resource, which the connection, its channels, its handlers and its texts are allocated in.

webcpp::trystero::native::backend::make_sealer

Makes a sealer over OpenSSL’s AES-256-GCM.

Synopsis

virtual
std::shared_ptr<trystero::backend::sealer>
make_sealer(
    std::array<uint8_t, 32> key,
    std::pmr::memory_resource* memory) override;

Return Value

The sealer.

Parameters

Name Description

key

The raw AES-256 key.

memory

The room’s resource, which the sealer, its texts and its completions are allocated in.

webcpp::trystero::native::backend::make_timer

Makes an Asio timer on the backend’s executor.

Synopsis

virtual
std::shared_ptr<trystero::backend::timer>
make_timer(std::pmr::memory_resource* memory) override;

Return Value

The timer, not started.

Parameters

Name Description

memory

The room’s resource, which the timer and its waits are allocated in.

webcpp::trystero::native::backend::now_ms

Milliseconds from the standard library’s steady clock.

Synopsis

virtual
double
now_ms() override;

Return Value

The current time, in milliseconds since the clock’s epoch.

webcpp::trystero::native::backend::random

Fills out from OpenSSL’s random generator; the program aborts when it has no secure randomness.

Synopsis

virtual
void
random(std::span<uint8_t> out) override;

Parameters

Name Description

out

The bytes to fill.

webcpp::trystero::native::backend::tls_context

The TLS configuration of the relays’ connections, for an application with authorities of its own.

Synopsis

boost::asio::ssl::context&
tls_context();

Return Value

The context every wss:// connection of the backend is made with.

webcpp::trystero::native::backend::unix_seconds

Seconds since the Unix epoch, from the system’s wall clock.

Synopsis

virtual
int64_t
unix_seconds() override;

Return Value

The current time, in whole seconds.

webcpp::trystero::native::backend_config

What a native backend makes its peer connections with, beside the app’s app_config::ice_servers.

Synopsis

struct backend_config;

Data Members

Name Description

bind_address

The local address every peer connection binds to, as "127.0.0.1"; empty means every interface but the loopback.

max_peer_connections_per_minute

The most peer connections the backend makes in any minute, for every app and room it serves together: beyond, it makes none, and a room reports each negotiation it cannot start.

relay_limits

What the relays’ WebSockets tolerate of a relay that stops reading or answering.

See Also

"The native backend", in the guide.

webcpp::trystero::native::backend_config::bind_address

The local address every peer connection binds to, as "127.0.0.1"; empty means every interface but the loopback.

Synopsis

std::pmr::string bind_address = = {};

Description

Bound to the loopback, the peers of one machine connect with no other network, which a test wants; a peer bound there reaches no other machine.

webcpp::trystero::native::backend_config::max_peer_connections_per_minute

The most peer connections the backend makes in any minute, for every app and room it serves together: beyond, it makes none, and a room reports each negotiation it cannot start.

Synopsis

std::size_t max_peer_connections_per_minute = 256;

Description

Tip: a room makes at most 64 in any 57 s however its peers behave, so the default lets four rooms do so at once; an application with more rooms raises it.

webcpp::trystero::native::backend_config::relay_limits

What the relays’ WebSockets tolerate of a relay that stops reading or answering.

Synopsis

websocket_limits relay_limits = {};

webcpp::trystero::native::data_channel

A data channel of libdatachannel, the native RTCDataChannel.

Synopsis

class data_channel
    : public backend::data_channel
    , public std::enable_shared_from_this<data_channel>

Description

Its events arrive on libdatachannel’s threads; each is posted to the executor and handled there, through a weak reference, so a late event for a destroyed channel does nothing. It exists only with exceptions: libdatachannel reports errors by throwing.

A text message, as a peer can send, or a binary one larger than any room frame (protocol::max_room_frame_bytes), is dropped on libdatachannel’s thread: neither can be a frame. Binary frames received before the message handler is installed wait in the app’s resource, in order, up to 64 frames for 10 seconds. Exceeding either bound closes the channel. Installing a receiver posts their delivery, so handler registration never calls it inline.

Tip: a received message is copied once, out of libdatachannel’s std::vector<std::byte> into the backend’s bytes, in the room’s resource, on the executor: what runs on libdatachannel’s threads touches no resource of the room, and its posts carry Asio’s default allocator.

Base Classes

Name Description

backend::data_channel

A WebRTC data channel, as the RTCDataChannel a Trystero peer exchanges its frames over.

std::enable_shared_from_this<data_channel>

Member Functions

Name Description

data_channel [constructor]

Constructs the wrapper; only create can, which then takes over the channel’s events.

~data_channel [destructor] [virtual]

Destroys the wrapper, which closes the channel without reporting the close.

buffered_amount [virtual]

The bytes queued to send.

close [virtual]

Closes the channel, which the handler close reports; a refusal is ignored.

is_open [virtual]

Whether the channel is open.

send [virtual]

Sends a binary message; one the channel refuses, closed in between, is dropped, and its close event reports the close.

set_handlers [virtual]

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

Static Member Functions

Name Description

create

Wraps a channel of libdatachannel, and takes over its events.

webcpp::trystero::native::data_channel::data_channel

Constructs the wrapper; only create can, which then takes over the channel’s events.

Synopsis

data_channel(
    private_tag,
    boost::asio::any_io_executor ex,
    std::shared_ptr<rtc::DataChannel> channel,
    std::pmr::memory_resource* memory);

Parameters

Name Description

ex

The executor every handler of the channel runs on.

channel

libdatachannel’s channel.

memory

The resource its handlers and its frames are allocated in.

webcpp::trystero::native::data_channel::~data_channel

Destroys the wrapper, which closes the channel without reporting the close.

Synopsis

virtual
~data_channel() override;

webcpp::trystero::native::data_channel::buffered_amount

The bytes queued to send.

Synopsis

virtual
std::size_t
buffered_amount() const override;

Return Value

libdatachannel’s bufferedAmount().

webcpp::trystero::native::data_channel::close

Closes the channel, which the handler close reports; a refusal is ignored.

Synopsis

virtual
void
close() override;

webcpp::trystero::native::data_channel::is_open

Whether the channel is open.

Synopsis

virtual
bool
is_open() const override;

Return Value

Whether this wrapper has not closed and libdatachannel’s channel is open.

webcpp::trystero::native::data_channel::send

Sends a binary message; one the channel refuses, closed in between, is dropped, and its close event reports the close.

Synopsis

virtual
void
send(std::span<uint8_t const> frame) override;

Parameters

Name Description

frame

The message, read during the call.

webcpp::trystero::native::data_channel::set_handlers

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

Synopsis

virtual
void
set_handlers(handlers h) override;

Parameters

Name Description

h

The new handlers, kept in the channel’s resource.

webcpp::trystero::native::data_channel::create

Wraps a channel of libdatachannel, and takes over its events.

Synopsis

static
std::shared_ptr<data_channel>
create(
    boost::asio::any_io_executor ex,
    std::shared_ptr<rtc::DataChannel> channel,
    std::pmr::memory_resource* memory);

Return Value

The channel.

Parameters

Name Description

ex

The executor every handler of the channel runs on.

channel

libdatachannel’s channel.

memory

The resource the wrapper, its handlers and its frames are allocated in.

webcpp::trystero::native::peer_connection

A peer connection of libdatachannel, the native RTCPeerConnection, with its negotiation driven by the signaling.

Synopsis

class peer_connection
    : public backend::peer_connection
    , public std::enable_shared_from_this<peer_connection>

Description

Automatic negotiation is off, so an offer or an answer exists only when set_local_description asks for one, as in the browser. It exists only with exceptions: libdatachannel reports errors by throwing.

| libdatachannel | Here | | events, on its own threads | posted to the executor, through a weak reference | | exceptions on refused input | error::backend_failure | | Candidate | the JSON of an RTCIceCandidateInit, as the browser writes it |

Tip: the connection, its handlers and the texts it hands to the app are in the app’s resource, and so are the rtc::PeerConnection object and its control block, which create makes with std::allocate_shared; what that object allocates inside libdatachannel, the data channels it creates, and the std::string and rtc::Description its API takes and returns, are libdatachannel’s.

Base Classes

Name Description

backend::peer_connection

A WebRTC peer connection, as the RTCPeerConnection of a Trystero peer, whose negotiation the signaling drives: it makes an offer or an answer only when asked to.

std::enable_shared_from_this<peer_connection>

Type Aliases

Name Description

description_handler

What set_local_description completes with: an error, or the type and the SDP.

Member Functions

Name Description

peer_connection [constructor]

Constructs the wrapper; only create can, which then takes over the connection’s events.

~peer_connection [destructor] [virtual]

Destroys the wrapper, which closes the connection without reporting the close.

add_ice_candidate [virtual]

Adds a remote ICE candidate; the empty candidate that ends the remote gathering adds none.

close [virtual]

Closes the connection and its channels.

create_data_channel [virtual]

Opens a data channel.

has_remote_description [virtual]

Whether a remote description was applied.

native_handle

libdatachannel’s own rtc::PeerConnection, what media will be added to.

set_handlers [virtual]

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

set_local_description [virtual]

Creates the offer or the answer the signaling state calls for and applies it.

set_remote_description [virtual]

Applies the remote peer’s offer or answer.

Static Member Functions

Name Description

create

Makes a peer connection that gathers through servers, bound to bind_address when it is not empty.

See Also

"The native backend", in the guide.

webcpp::trystero::native::peer_connection::description_handler

What set_local_description completes with: an error, or the type and the SDP.

webcpp::trystero::native::peer_connection::peer_connection

Constructs the wrapper; only create can, which then takes over the connection’s events.

Synopsis

peer_connection(
    private_tag,
    boost::asio::any_io_executor ex,
    std::shared_ptr<rtc::PeerConnection> pc,
    std::pmr::memory_resource* memory);

Parameters

Name Description

ex

The executor every handler of the connection runs on.

pc

libdatachannel’s connection.

memory

The resource its handlers and its texts are allocated in.

webcpp::trystero::native::peer_connection::~peer_connection

Destroys the wrapper, which closes the connection without reporting the close.

Synopsis

virtual
~peer_connection() override;

webcpp::trystero::native::peer_connection::add_ice_candidate

Adds a remote ICE candidate; the empty candidate that ends the remote gathering adds none.

Synopsis

virtual
void
add_ice_candidate(
    std::string_view candidate_json,
    handler<void(boost::system::error_code)> done) override;

Parameters

Name Description

candidate_json

The JSON of an RTCIceCandidateInit, read during the call.

done

Called on the executor once it is added, or with error::backend_failure when the JSON has no candidate or libdatachannel refuses it.

webcpp::trystero::native::peer_connection::close

Closes the connection and its channels.

Synopsis

virtual
void
close() override;

webcpp::trystero::native::peer_connection::create_data_channel

Opens a data channel.

Synopsis

virtual
std::shared_ptr<backend::data_channel>
create_data_channel(std::string_view label) override;

Return Value

The channel, made in the connection’s resource, or null when libdatachannel refuses to open one, as on a closed connection.

Parameters

Name Description

label

The channel’s label, read during the call.

webcpp::trystero::native::peer_connection::has_remote_description

Whether a remote description was applied.

Synopsis

virtual
bool
has_remote_description() const override;

Return Value

Whether libdatachannel’s connection has a remote description.

webcpp::trystero::native::peer_connection::native_handle

libdatachannel’s own rtc::PeerConnection, what media will be added to.

Synopsis

std::shared_ptr<rtc::PeerConnection> const&
native_handle() const;

Description

The object and its control block are in the app’s resource: no copy of the pointer may outlive app_config::memory.

Return Value

The connection.

See Also

"Roadmap", in the guide.

webcpp::trystero::native::peer_connection::set_handlers

Replaces the handlers, kept in a shared object so that one replacing them during its own call outlives the call.

Synopsis

virtual
void
set_handlers(handlers h) override;

Parameters

Name Description

h

The new handlers, kept in the connection’s resource.

webcpp::trystero::native::peer_connection::set_local_description

Creates the offer or the answer the signaling state calls for and applies it.

Synopsis

virtual
void
set_local_description(description_handler done) override;

Parameters

Name Description

done

Called on the executor with the description’s type and SDP, or with error::backend_failure when libdatachannel refuses.

webcpp::trystero::native::peer_connection::set_remote_description

Applies the remote peer’s offer or answer.

Synopsis

virtual
void
set_remote_description(
    std::string_view type,
    std::string_view sdp,
    handler<void(boost::system::error_code)> done) override;

Parameters

Name Description

type

"offer" or "answer", read during the call.

sdp

The description’s SDP, read during the call.

done

Called on the executor once it is applied, or with error::backend_failure when libdatachannel refuses it.

webcpp::trystero::native::peer_connection::create

Makes a peer connection that gathers through servers, bound to bind_address when it is not empty.

Synopsis

static
std::shared_ptr<peer_connection>
create(
    boost::asio::any_io_executor ex,
    std::span<backend::ice_server const> servers,
    std::string_view bind_address,
    std::pmr::memory_resource* memory);

Description

A connection that is not bound gathers a host candidate on each interface but the loopback, which ICE never offers (RFC 8445, 5.1.1.1); one bound to an address, as "127.0.0.1", gathers on that address alone, which lets two peers of one machine connect with no other network.

Exceptions

Name Thrown on

std::exception

What libdatachannel throws when it cannot make the connection.

Return Value

The new connection.

Parameters

Name Description

ex

The executor every handler of the connection runs on.

servers

The STUN and TURN servers it gathers through.

bind_address

The local address it binds to, as "127.0.0.1"; empty means every interface but the loopback.

memory

The resource the connection, its handlers and its texts are allocated in.

webcpp::trystero::native::sealer

AES-GCM under one room key, through OpenSSL, in Trystero’s sealed text form, what Trystero’s encrypt and decrypt do with crypto.subtle.

Synopsis

class sealer
    : public backend::sealer

Description

It works synchronously and posts each completion to the executor, as the backend interface asks: a handler never runs inside the call that started it. Its texts and completions are in the room’s resource; OpenSSL’s cipher context is OpenSSL’s. It needs no exceptions.

Base Classes

Name Description

backend::sealer

AES-GCM under one room key, in Trystero’s sealed text form (protocol/signal.hpp), what Trystero’s encrypt and decrypt do with crypto.subtle.

Type Aliases

Name Description

done_handler

What a seal or an open completes with: an error, or the text, a view valid for the call.

Member Functions

Name Description

sealer [constructor]

Constructs a sealer under a room’s key.

open [virtual]

Opens a sealed text.

seal [virtual]

Seals a text under a fresh IV from OpenSSL’s random generator.

See Also

"The native backend", in the guide.

webcpp::trystero::native::sealer::done_handler

What a seal or an open completes with: an error, or the text, a view valid for the call.

webcpp::trystero::native::sealer::sealer

Constructs a sealer under a room’s key.

Synopsis

sealer(
    boost::asio::any_io_executor ex,
    std::array<uint8_t, 32> key,
    std::pmr::memory_resource* memory);

Parameters

Name Description

ex

The executor its completions are posted to.

key

The raw AES-256 key, protocol::seal_key’s.

memory

The room’s resource, which its texts and its completions are allocated in; it must outlive the completions it posts.

webcpp::trystero::native::sealer::open

Opens a sealed text.

Synopsis

virtual
void
open(
    std::string_view sealed,
    done_handler done) override;

Parameters

Name Description

sealed

The sealed text, read during the call.

done

Called on the executor with the plaintext, or with error::wrong_password when the text was sealed under another key or is not a sealed text.

webcpp::trystero::native::sealer::seal

Seals a text under a fresh IV from OpenSSL’s random generator.

Synopsis

virtual
void
seal(
    std::string_view plaintext,
    done_handler done) override;

Parameters

Name Description

plaintext

The text, read during the call.

done

Called on the executor with the sealed text, or with error::backend_failure when OpenSSL fails.

webcpp::trystero::native::timer

An Asio timer whose waits are allocated in the room’s resource.

Synopsis

class timer
    : public trystero::backend::timer

Description

Tip: Asio cannot take back an expiry it has already queued, which it then completes as a success; so each wait carries its number, and cancel, a later start and the destructor end it, by changing the number or freeing it, before its handler runs.

Base Classes

Name Description

trystero::backend::timer

A one-shot timer, what the setTimeout calls of Trystero become.

Member Functions

Name Description

timer [constructor]

Constructs a timer, not started.

cancel [virtual]

Cancels the pending expiry, if any, whose handler is then never called.

start [virtual]

Calls fire on the executor once delay has passed, in place of a pending expiry.

See Also

"The native backend", in the guide.

webcpp::trystero::native::timer::timer

Constructs a timer, not started.

Synopsis

timer(
    boost::asio::any_io_executor const& ex,
    std::pmr::memory_resource* memory);

Parameters

Name Description

ex

The executor it fires on.

memory

The room’s resource, which each wait is allocated in; it must outlive the timer’s pending waits.

webcpp::trystero::native::timer::cancel

Cancels the pending expiry, if any, whose handler is then never called.

Synopsis

virtual
void
cancel() override;

webcpp::trystero::native::timer::start

Calls fire on the executor once delay has passed, in place of a pending expiry.

Synopsis

virtual
void
start(
    std::chrono::milliseconds delay,
    handler<void()> fire) override;

Parameters

Name Description

delay

How long to wait.

fire

What to call when the timer expires; never called once the timer is cancelled, started again or destroyed, even when its expiry was already queued.

webcpp::trystero::native::websocket_limits

What a native WebSocket tolerates of a relay that stops reading or answering.

Synopsis

struct websocket_limits;

Data Members

Name Description

idle_timeout

How long the connection may stay silent: half of it, a keep-alive ping is sent, and a relay that answers nothing within the whole of it is closed, which reports a close.

max_queued_bytes

The most bytes of text queued to write: a relay that stops reading lets them pile up, and a text beyond this drops the connection, which reports a close, so that the room reconnects with its backoff.

See Also

"The native backend", in the guide.

webcpp::trystero::native::websocket_limits::idle_timeout

How long the connection may stay silent: half of it, a keep-alive ping is sent, and a relay that answers nothing within the whole of it is closed, which reports a close.

Synopsis

std::chrono::seconds idle_timeout = std::chrono::seconds(30);

Description

It counts the bytes received, and a ping waits for the write in flight. Native only: a page’s WebSocket cannot ping, so the browser backend has no idle timeout.

webcpp::trystero::native::websocket_limits::max_queued_bytes

The most bytes of text queued to write: a relay that stops reading lets them pile up, and a text beyond this drops the connection, which reports a close, so that the room reconnects with its backoff.

Synopsis

std::size_t max_queued_bytes = protocol::max_queued_relay_bytes;

webcpp::trystero::native::connect_websocket

Connects to a Nostr relay over Boost.Beast: ws:// in the clear, wss:// over TLS with tls.

Synopsis

std::shared_ptr<backend::websocket>
connect_websocket(
    boost::asio::any_io_executor const& ex,
    boost::asio::ssl::context& tls,
    std::string_view url,
    backend::websocket::handlers h,
    std::pmr::memory_resource* memory,
    websocket_limits limits = {});

Description

Writes are queued, one in flight at a time; a received message is handed over as a view of the read buffer, and one longer than protocol::max_relay_message_bytes closes the connection. A relay that stops reading is dropped once the queue would pass limits.max_queued_bytes, and one that stops answering once limits.idle_timeout passes with its keep-alive ping unanswered; both report a close. A URL it cannot parse reports a close, as an unreachable relay does. It needs no exceptions.

Return Value

The WebSocket; destroying it drops the connection without reporting it.

Parameters

Name Description

ex

The executor every handler runs on.

tls

The TLS context of wss://, which must outlive the connection and should verify peers.

url

The relay’s URL, read during the call.

h

What the WebSocket reports.

memory

The room’s resource, which the connection, its handlers and its queued texts are allocated in; Beast’s stream state and the coroutines’ frames are Asio’s.

limits

What it tolerates of a relay that stops reading or answering.

See Also

"The native backend", in the guide.

webcpp::trystero::protocol

Namespaces

Name Description

json

Reading and writing the protocol’s JSON texts, with every allocation in the resource the caller gives. A text the protocol writes is a fixed shape of a few strings and numbers, so it is appended field by field rather than built as a boost::json::value, which would copy every string into a node of its own before serializing it again.

nostr

Types

Name Description

chunk

A decoded chunk, as a view into the frame.

control_message

A decoded accepted, rejected or cancelled frame, as a view into the frame.

decode_result

What decode_action makes of a frame.

envelope

A decoded room envelope, as views into the frame: every data channel message names the room it belongs to, so one connection can serve several rooms of the same app.

proposal

A decoded inline frame or offer, as views into the frame.

sealed_parts

A sealed signal’s parts, as parse_sealed reads them.

signal_message

A message on a signaling topic, the JSON object Trystero’s signal handler reads, as views.

Type Aliases

Name Description

bytes

Bytes, in the memory resource a room or the caller gives.

decoded_action

A decoded action frame: a proposal, a chunk or a transfer’s answer.

Enums

Name Description

frame_kind

The kind of an action frame, the low four bits of its second byte, in Trystero’s action wire, version 2.

payload_format

The format of an action’s payload, the high four bits of a frame’s second byte: how Trystero’s decodePayload reads it.

wire_error

Why a frame cannot be decoded; Trystero drops the peer for any of these.

Functions

Name Description

append_accepted

Appends the frame accepting transfer id.

append_base64

Appends standard base64 with padding, what btoa writes over binary text.

append_cancelled

Appends the frame cancelling transfer id.

append_chunk

Appends a chunk of transfer id.

append_hex

Appends the lowercase hexadecimal of a byte sequence, Trystero’s toHex.

append_inline

Appends an inline frame to out, which may already hold a room envelope’s header.

append_offer

Appends an offer frame announcing size bytes under transfer id.

append_rejected

Appends the frame rejecting transfer id.

base64_decode

The bytes of a base64 text, padded or not, as atob reads it.

base64_encode

Standard base64 with padding, what btoa writes over binary text.

begin_room_frame

Starts a room frame in an empty buffer: the header for token, with room reserved for payload_size more bytes.

decode_action

Decodes one action frame with exactly the checks Trystero’s handleData makes.

decode_envelope

Decodes the envelope of a frame, with the checks of Trystero’s unwrapFrame.

derive_relays

The relays an app uses when it names none, Trystero’s getRelays: the defaults shuffled by the app id, the first redundancy kept.

encode_accepted

The frame accepting a transfer, in a buffer of its own.

encode_cancelled

The frame cancelling a transfer, in a buffer of its own.

encode_chunk

A chunk frame, in a buffer of its own.

encode_inline

An inline frame, in a buffer of its own.

encode_offer

An offer frame, in a buffer of its own.

encode_rejected

The frame rejecting a transfer, in a buffer of its own.

fits_inline

Whether a payload with this metadata travels inline rather than as an offer and chunks.

format_sealed

The text form of a sealed signal, what Trystero’s encrypt returns: the IV’s bytes in decimal joined by commas, "$", the ciphertext in base64.

from_hex

The bytes of a hexadecimal text, in either case.

handshake_proof

The password handshake’s proof, Trystero’s hashChallenge: the hexadecimal SHA-256 of "<challenge>:<password>:<app>:<room>".

make_id

A random id of n characters of id_alphabet, Trystero’s genId.

parse_sealed

The parts of a sealed signal’s text, as Trystero’s decrypt splits it.

peer_topic

The topic a peer receives its offers, answers and candidates on, Trystero’s sha1 of the root topic’s text, "@" and the peer’s id.

presence_frame

A presence frame, Trystero’s wrapRoomPresenceFrame: whether this peer is in a room.

read_signal

The message a parsed signaling payload holds, as views into document.

room_frame

A room frame, Trystero’s wrapRoomFrame: an action frame in the envelope of its room.

room_token

The token that names a room inside a data channel’s envelope: the hexadecimal SHA-256 of "Trystero:<app>:<room>".

root_topic

The topic every member of a room announces on, Trystero’s sha1 of "Trystero@<app>@<room>".

seal_key

The raw AES-GCM key of a room, what Trystero’s genKey imports: the SHA-256 of "<password>:<app>:<room>".

sealed_size

The longest text a plaintext seals to, as format_sealed writes it.

sha1_topic

Trystero’s sha1 of a text, the name of a topic.

sha1_topic_of

Trystero’s sha1 of the concatenation of parts, the name of a topic.

sha256

The SHA-256 of a text.

sha256_of

The SHA-256 of the concatenation of parts, fed to the hash one part at a time.

shuffle

Trystero’s shuffle: Fisher-Yates driven by sin(seed++) * 10000, so every peer of an app picks the same order.

str_to_num

Trystero’s strToNum: the sum of the text’s UTF-16 code units, modulo limit.

to_hex

The lowercase hexadecimal of a byte sequence, Trystero’s toHex.

utf16_code_unit_sum

The sum of the UTF-16 code units of a UTF-8 text, what JavaScript’s charCodeAt sees.

valid_action_name

Whether a name can name an action, as Trystero’s makeAction checks it: 1 to 32 UTF-8 bytes and no NUL.

write_signal

The JSON text of a message, with its members in Trystero’s order and the empty ones left out.

Variables

Name Description

action_wire_version

The version of the action wire, every frame’s first byte, Trystero’s version.

chunk_header_size

The size of a chunk’s header, Trystero’s chunkHeaderSize.

chunk_size

The most payload bytes one chunk carries, Trystero’s chunkSize.

control_header_size

The size of an accepted, rejected or cancelled frame’s header, controlHeaderSize.

default_nostr_relays

Trystero 0.26’s default Nostr relays, its defaultRelayUrls, in its order: the order is part of the relay derivation.

id_alphabet

The 62 characters Trystero draws peer ids, offer ids and nonces from, its charSet.

inline_header_size

The size of an inline frame’s header, Trystero’s inlineHeaderSize.

max_action_frame_bytes

The largest action frame, Trystero’s maxActionFrameBytes: 16 KiB.

max_action_name_bytes

The longest action name, in UTF-8 bytes.

max_offer_id_bytes

The longest offer id a signal may carry, in bytes; Trystero’s are offer_id_size.

max_peer_id_bytes

The longest peer id a room accepts in a signal, in bytes; a signal with a longer one is ignored.

max_queued_relay_bytes

The most text a room holds unsent for one relay, in UTF-8 bytes: 4 MiB, a thousand signals and more.

max_reject_reason_bytes

The longest reason a rejected frame carries, in UTF-8 bytes.

max_relay_message_bytes

The longest text a room reads from a relay, in UTF-8 bytes: 1 MiB, far beyond any message of the protocol (an event’s content is a signal, whose SDP is a few KiB) and of what public relays accept (their events are capped at 64 to 512 KiB).

max_room_frame_bytes

The largest room frame, Trystero’s maxRoomFrameBytes: an action frame in its envelope.

max_room_token_bytes

The longest room token a room frame may carry, in bytes.

max_sealed_candidate_bytes

The longest sealed candidate a signal may carry, in bytes.

max_sealed_description_bytes

The longest sealed offer or answer a signal may carry, in bytes.

nonce_size

The length of the nonce that makes repeated announcements distinct Nostr events.

offer_header_size

The size of an offer’s header, Trystero’s offerHeaderSize.

offer_id_size

The length of the id of a Trystero offer, which its answer and candidates name.

peer_id_size

The length of a Trystero peer id, its selfId.

seal_iv_size

The size of the IV Trystero’s encrypt draws for AES-GCM: 16 bytes, not the usual 12.

webcpp::trystero::protocol::json

Reading and writing the protocol’s JSON texts, with every allocation in the resource the caller gives. A text the protocol writes is a fixed shape of a few strings and numbers, so it is appended field by field rather than built as a boost::json::value, which would copy every string into a node of its own before serializing it again.

Description

Tip: strings are escaped by Boost.JSON’s serializer, so the bytes match what boost::json::serialize and JavaScript’s JSON.stringify write; a Nostr event id is a hash over exactly these bytes.

Functions

Name Description

append_integer

Appends an integer in decimal, as JavaScript writes a safe integer.

append_key

Appends "key": with a leading comma unless it opens the object.

append_string

Appends text as a JSON string literal, quotes included, as JSON.stringify writes it.

append_value

Appends value serialized, as boost::json::serialize writes it.

parse

Parses text into a value of storage, or sets ec.

webcpp::trystero::protocol::json::append_integer

Appends an integer in decimal, as JavaScript writes a safe integer.

Synopsis

void
append_integer(
    std::pmr::string& out,
    int64_t value);

Parameters

Name Description

out

The text to append to.

value

The integer.

webcpp::trystero::protocol::json::append_key

Appends "key": with a leading comma unless it opens the object.

Synopsis

void
append_key(
    std::pmr::string& out,
    std::string_view key);

Parameters

Name Description

out

The text of an object being written, which ends with its { or a member’s value.

key

The member’s name.

webcpp::trystero::protocol::json::append_string

Appends text as a JSON string literal, quotes included, as JSON.stringify writes it.

Synopsis

void
append_string(
    std::pmr::string& out,
    std::string_view text);

Description

Tip: the serializer keeps where it stopped on a stack of its own when the text outgrows its buffer; that stack is in out’s resource.

Parameters

Name Description

out

The text to append to.

text

The string, in UTF-8.

webcpp::trystero::protocol::json::append_value

Appends value serialized, as boost::json::serialize writes it.

Synopsis

void
append_value(
    std::pmr::string& out,
    boost::json::value const& value);

Parameters

Name Description

out

The text to append to; the serializer’s state is in its resource.

value

The value.

webcpp::trystero::protocol::json::parse

Parses text into a value of storage, or sets ec.

Synopsis

boost::json::value
parse(
    std::string_view text,
    boost::json::storage_ptr const& storage,
    boost::system::error_code& ec);

Description

Tip: boost::json::parse keeps its temporaries in the default resource once they outgrow a buffer on the stack; this parser keeps them in storage too.

Return Value

The value, or a null value of storage when ec is set.

Parameters

Name Description

text

The JSON text.

storage

The storage the value and the parser’s temporaries are allocated in.

ec

Set when the text is not one JSON value.

webcpp::trystero::protocol::nostr

Types

Name Description

closed

A relay’s ["CLOSED", <subscription>, <reason>]: the relay ended a subscription.

eose

A relay’s ["EOSE", <subscription>]: the stored events of a subscription were all sent.

event

A relay’s ["EVENT", <subscription>, <event>]: an event of a subscription, as views.

notice

A relay’s ["NOTICE", <text>]: a message for humans.

ok

A relay’s ["OK", <id>, <accepted>, <reason>]: whether it took a published event, as views.

signer

A BIP-340 key pair over libsecp256k1, the one key a peer signs every event with, as Trystero’s Nostr strategy does with its pubkey.

Type Aliases

Name Description

relay_message

What a relay sends, as views into the parsed message; anything else is ignored.

Functions

Name Description

close_message

The client message ending a subscription: ["CLOSE", id].

event_id

The id of a Trystero event, as NIP-01 defines it: the SHA-256 of the JSON array [0, pubkey, created_at, kind, [["x", topic]], content].

event_message

The client message publishing a signed event on a topic, ["EVENT", {...}], what Trystero’s createEvent makes.

read_relay_message

The message a parsed relay text holds, as views into document.

subscribe_message

The client message subscribing to topics, Trystero’s subscribe: `["REQ", id, {"kinds": [...], "since": t, "#x": [...]}]`.

topic_kind

The event kind a topic maps to, Trystero’s topicToKind: 20000 to 29999, ephemeral, so relays forward the events and keep none.

verify

Whether a BIP-340 signature over an event id verifies under an x-only public key.

Variables

Name Description

topic_tag

The tag a Trystero event carries its topic in, its tag: "x".

webcpp::trystero::protocol::nostr::relay_message

What a relay sends, as views into the parsed message; anything else is ignored.

Synopsis

using relay_message = std::variant<event, ok, eose, closed, notice>;

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::nostr::closed

A relay’s ["CLOSED", <subscription>, <reason>]: the relay ended a subscription.

Synopsis

struct closed;

Data Members

Name Description

reason

Why.

subscription

The subscription’s id.

webcpp::trystero::protocol::nostr::closed::reason

Why.

Synopsis

std::string_view reason;

webcpp::trystero::protocol::nostr::closed::subscription

The subscription’s id.

Synopsis

std::string_view subscription;

webcpp::trystero::protocol::nostr::eose

A relay’s ["EOSE", <subscription>]: the stored events of a subscription were all sent.

Synopsis

struct eose;

Data Members

Name Description

subscription

The subscription’s id.

webcpp::trystero::protocol::nostr::eose::subscription

The subscription’s id.

Synopsis

std::string_view subscription;

webcpp::trystero::protocol::nostr::event

A relay’s ["EVENT", <subscription>, <event>]: an event of a subscription, as views.

Synopsis

struct event;

Data Members

Name Description

content

The event’s content.

id

The event’s id.

subscription

The subscription’s id.

topic

The value of the event’s first "x" tag; empty when there is none.

webcpp::trystero::protocol::nostr::event::content

The event’s content.

Synopsis

std::string_view content;

webcpp::trystero::protocol::nostr::event::id

The event’s id.

Synopsis

std::string_view id;

webcpp::trystero::protocol::nostr::event::subscription

The subscription’s id.

Synopsis

std::string_view subscription;

webcpp::trystero::protocol::nostr::event::topic

The value of the event’s first "x" tag; empty when there is none.

Synopsis

std::string_view topic;

webcpp::trystero::protocol::nostr::notice

A relay’s ["NOTICE", <text>]: a message for humans.

Synopsis

struct notice;

Data Members

Name Description

text

The message.

webcpp::trystero::protocol::nostr::notice::text

The message.

Synopsis

std::string_view text;

webcpp::trystero::protocol::nostr::ok

A relay’s ["OK", <id>, <accepted>, <reason>]: whether it took a published event, as views.

Synopsis

struct ok;

Data Members

Name Description

accepted

Whether the relay accepted the event.

id

The event’s id.

reason

Why, as "rate-limited: ..." or "blocked: ..."; may be empty.

webcpp::trystero::protocol::nostr::ok::accepted

Whether the relay accepted the event.

Synopsis

bool accepted = false;

webcpp::trystero::protocol::nostr::ok::id

The event’s id.

Synopsis

std::string_view id;

webcpp::trystero::protocol::nostr::ok::reason

Why, as "rate-limited: ..." or "blocked: ..."; may be empty.

Synopsis

std::string_view reason;

webcpp::trystero::protocol::nostr::signer

A BIP-340 key pair over libsecp256k1, the one key a peer signs every event with, as Trystero’s Nostr strategy does with its pubkey.

Synopsis

class signer;

Description

Tip: its signing context is made in the resource it is given (secp256k1_context_preallocated_create), where secp256k1_context_create would take it from malloc.

Member Functions

Name Description

pubkey

The x-only public key, an event’s pubkey.

sign

Signs a message with BIP-340 Schnorr.

Static Member Functions

Name Description

from_secret

Makes a key from 32 random bytes.

webcpp::trystero::protocol::nostr::signer::pubkey

The x-only public key, an event’s pubkey.

Synopsis

[[nodiscard]]
std::string_view
pubkey() const;

Return Value

64 lowercase hexadecimal digits, valid as long as the signer.

Note

The return value should not be discarded.

webcpp::trystero::protocol::nostr::signer::sign

Signs a message with BIP-340 Schnorr.

Synopsis

[[nodiscard]]
std::array<uint8_t, 64>
sign(
    std::span<uint8_t const, 32> message,
    std::span<uint8_t const, 32> aux) const;

Return Value

The 64-byte signature, an event’s sig.

Note

The return value should not be discarded.

Parameters

Name Description

message

The 32 bytes to sign, an event id.

aux

32 random bytes of auxiliary randomness.

webcpp::trystero::protocol::nostr::signer::from_secret

Makes a key from 32 random bytes.

Synopsis

static
std::optional<signer>
from_secret(
    std::span<uint8_t const, 32> secret,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The signer, or nothing when the bytes are not a valid secret key, which is negligibly rare.

Parameters

Name Description

secret

The secret key.

memory

The resource the signing context and the public key’s text are allocated in; it must outlive the signer.

webcpp::trystero::protocol::nostr::close_message

The client message ending a subscription: ["CLOSE", id].

Synopsis

std::pmr::string
close_message(
    std::string_view subscription,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The message’s JSON text.

Parameters

Name Description

subscription

The subscription’s id.

memory

The resource the message is allocated in.

webcpp::trystero::protocol::nostr::event_id

The id of a Trystero event, as NIP-01 defines it: the SHA-256 of the JSON array [0, pubkey, created_at, kind, [["x", topic]], content].

Synopsis

std::array<uint8_t, 32>
event_id(
    std::string_view pubkey,
    int64_t created_at,
    int kind,
    std::string_view topic,
    std::string_view content,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The id.

Parameters

Name Description

pubkey

The signer’s x-only public key, in hexadecimal.

created_at

The event’s time, in seconds since the Unix epoch.

kind

The event’s kind, topic_kind of its topic.

topic

The topic, the value of its tag "x".

content

The content.

memory

The resource the escaped topic and content are allocated in, during the call.

webcpp::trystero::protocol::nostr::event_message

The client message publishing a signed event on a topic, ["EVENT", {...}], what Trystero’s createEvent makes.

Synopsis

std::pmr::string
event_message(
    signer const& key,
    int64_t created_at,
    std::string_view topic,
    std::string_view content,
    std::span<uint8_t const, 32> aux,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Description

It is written once into one buffer. The topic and the content are escaped once; the id is hashed over those same bytes.

Return Value

The message’s JSON text.

Parameters

Name Description

key

The signer.

created_at

The event’s time, in seconds since the Unix epoch.

topic

The topic, the value of the event’s tag "x".

content

The content, a signal’s JSON text.

aux

32 random bytes for the signature.

memory

The resource the message is allocated in.

webcpp::trystero::protocol::nostr::read_relay_message

The message a parsed relay text holds, as views into document.

Synopsis

std::optional<relay_message>
read_relay_message(boost::json::value const& document);

Return Value

The message, or nothing when Trystero would ignore it: not an array, or not one of the messages of relay_message.

Parameters

Name Description

document

The parsed text, which the caller keeps alive while it reads the views.

webcpp::trystero::protocol::nostr::subscribe_message

The client message subscribing to topics, Trystero’s subscribe: `["REQ", id, {"kinds": [...], "since": t, "#x": [...]}]`.

Synopsis

std::pmr::string
subscribe_message(
    std::string_view subscription,
    std::span<std::pmr::string const> topics,
    int64_t since,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The message’s JSON text.

Parameters

Name Description

subscription

The subscription’s id.

topics

The topics, whose kinds the filter lists once each, in increasing order.

since

The earliest created_at of the events wanted.

memory

The resource the message is allocated in.

webcpp::trystero::protocol::nostr::topic_kind

The event kind a topic maps to, Trystero’s topicToKind: 20000 to 29999, ephemeral, so relays forward the events and keep none.

Synopsis

int
topic_kind(std::string_view topic);

Return Value

20000 plus str_to_num(topic, 10000).

Parameters

Name Description

topic

The topic’s name.

webcpp::trystero::protocol::nostr::verify

Whether a BIP-340 signature over an event id verifies under an x-only public key.

Synopsis

bool
verify(
    std::span<uint8_t const, 64> sig,
    std::span<uint8_t const, 32> id,
    std::span<uint8_t const, 32> pubkey);

Description

It uses libsecp256k1’s static context, which allocates nothing.

Return Value

Whether the signature is valid; false too when the key is not a valid point.

Parameters

Name Description

sig

The signature.

id

The event id.

pubkey

The x-only public key.

webcpp::trystero::protocol::nostr::topic_tag

The tag a Trystero event carries its topic in, its tag: "x".

Synopsis

inline constexpr std::string_view topic_tag = "x";

webcpp::trystero::protocol::bytes

Bytes, in the memory resource a room or the caller gives.

Synopsis

using bytes = std::pmr::vector<uint8_t>;

webcpp::trystero::protocol::decoded_action

A decoded action frame: a proposal, a chunk or a transfer’s answer.

Synopsis

using decoded_action = std::variant<proposal, chunk, control_message>;

webcpp::trystero::protocol::chunk

A decoded chunk, as a view into the frame.

Synopsis

struct chunk;

Data Members

Name Description

data

The data.

id

The transfer’s id.

offset

Where the data starts in the payload, as the float64 the frame carries; NaN when absent.

webcpp::trystero::protocol::chunk::data

The data.

Synopsis

std::span<uint8_t const> data;

webcpp::trystero::protocol::chunk::id

The transfer’s id.

Synopsis

uint32_t id = 0;

webcpp::trystero::protocol::chunk::offset

Where the data starts in the payload, as the float64 the frame carries; NaN when absent.

Synopsis

double offset = 0;

webcpp::trystero::protocol::control_message

A decoded accepted, rejected or cancelled frame, as a view into the frame.

Synopsis

struct control_message;

Data Members

Name Description

id

The transfer’s id.

kind

frame_kind::accepted, frame_kind::rejected or frame_kind::cancelled.

reason

Why the transfer was rejected; rejected frames only.

webcpp::trystero::protocol::control_message::id

The transfer’s id.

Synopsis

uint32_t id = 0;

webcpp::trystero::protocol::control_message::kind

frame_kind::accepted, frame_kind::rejected or frame_kind::cancelled.

Synopsis

frame_kind kind = frame_kind::accepted;

webcpp::trystero::protocol::control_message::reason

Why the transfer was rejected; rejected frames only.

Synopsis

std::string_view reason;

webcpp::trystero::protocol::decode_result

What decode_action makes of a frame.

Synopsis

struct decode_result;

Data Members

Name Description

error

Why the frame cannot be decoded; meaningful only without a frame.

frame

The decoded frame, or nothing when it cannot be decoded.

webcpp::trystero::protocol::decode_result::error

Why the frame cannot be decoded; meaningful only without a frame.

Synopsis

wire_error error = wire_error::invalid_frame;

webcpp::trystero::protocol::decode_result::frame

The decoded frame, or nothing when it cannot be decoded.

Synopsis

std::optional<decoded_action> frame;

webcpp::trystero::protocol::envelope

A decoded room envelope, as views into the frame: every data channel message names the room it belongs to, so one connection can serve several rooms of the same app.

Synopsis

struct envelope;

Description

| Frame | Bytes | | room | 1, token length (uint16 BE), token, action frame | | presence | 2, present (0 or 1), token length (uint16 BE), token |

Enums

Name Description

kind

The kind of a frame, its first byte.

Data Members

Name Description

payload

The action frame, for kind::room.

present

Whether the peer is in the room, for kind::presence.

token

The token of the room the frame is for.

type

The frame’s kind.

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::envelope::kind

The kind of a frame, its first byte.

Synopsis

enum class kind : int;

Members

Name Description

room

A room frame, 1: an action frame for the room.

presence

A presence frame, 2: the peer joins or leaves the room.

webcpp::trystero::protocol::envelope::kind::room

A room frame, 1: an action frame for the room.

Synopsis

room

webcpp::trystero::protocol::envelope::kind::presence

A presence frame, 2: the peer joins or leaves the room.

Synopsis

presence

webcpp::trystero::protocol::envelope::payload

The action frame, for kind::room.

Synopsis

std::span<uint8_t const> payload;

webcpp::trystero::protocol::envelope::present

Whether the peer is in the room, for kind::presence.

Synopsis

bool present = false;

webcpp::trystero::protocol::envelope::token

The token of the room the frame is for.

Synopsis

std::string_view token;

webcpp::trystero::protocol::envelope::type

The frame’s kind.

Synopsis

kind type = kind::room;

webcpp::trystero::protocol::proposal

A decoded inline frame or offer, as views into the frame.

Synopsis

struct proposal;

Data Members

Name Description

format

The payload’s format.

id

The transfer’s id; offers only.

kind

frame_kind::inline_payload or frame_kind::offer.

metadata

The metadata, JSON text, or empty.

name

The action’s name, up to the first NUL of the 32-byte field.

payload

The payload; inline frames only.

size

The payload’s size; for an inline frame, the bytes that follow the metadata.

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::proposal::format

The payload’s format.

Synopsis

payload_format format = payload_format::text;

webcpp::trystero::protocol::proposal::id

The transfer’s id; offers only.

Synopsis

uint32_t id = 0;

webcpp::trystero::protocol::proposal::kind

frame_kind::inline_payload or frame_kind::offer.

Synopsis

frame_kind kind = frame_kind::inline_payload;

webcpp::trystero::protocol::proposal::metadata

The metadata, JSON text, or empty.

Synopsis

std::string_view metadata;

webcpp::trystero::protocol::proposal::name

The action’s name, up to the first NUL of the 32-byte field.

Synopsis

std::string_view name;

webcpp::trystero::protocol::proposal::payload

The payload; inline frames only.

Synopsis

std::span<uint8_t const> payload;

webcpp::trystero::protocol::proposal::size

The payload’s size; for an inline frame, the bytes that follow the metadata.

Synopsis

std::size_t size = 0;

webcpp::trystero::protocol::sealed_parts

A sealed signal’s parts, as parse_sealed reads them.

Synopsis

struct sealed_parts;

Data Members

Name Description

ciphertext

The ciphertext, followed by its 16-byte tag.

iv

The IV, 16 bytes in what Trystero writes.

webcpp::trystero::protocol::sealed_parts::ciphertext

The ciphertext, followed by its 16-byte tag.

Synopsis

bytes ciphertext;

webcpp::trystero::protocol::sealed_parts::iv

The IV, 16 bytes in what Trystero writes.

Synopsis

bytes iv;

webcpp::trystero::protocol::signal_message

A message on a signaling topic, the JSON object Trystero’s signal handler reads, as views.

Synopsis

struct signal_message;

Description

With none of offer, answer and candidate it is an announcement: the sender is in the room. Offer, answer and candidate hold sealed text; a candidate’s plaintext is the JSON of an RTCIceCandidateInit.

Member Functions

Name Description

is_announcement

Whether the message is an announcement.

Data Members

Name Description

answer

The sealed SDP of an answer, answer.

candidate

A sealed ICE candidate, candidate.

offer

The sealed SDP of an offer, offer.

offer_id

The offer the message belongs to, offerId; empty in an announcement.

passive

Whether the sender’s room is passive, passive: a Trystero room joined with passive announces only once it has seen a peer that is not passive.

peer_id

The sender’s id, peerId: 1 to max_peer_id_bytes bytes.

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::signal_message::is_announcement

Whether the message is an announcement.

Synopsis

[[nodiscard]]
bool
is_announcement() const;

Return Value

Whether it holds none of offer, answer and candidate.

Note

The return value should not be discarded.

webcpp::trystero::protocol::signal_message::answer

The sealed SDP of an answer, answer.

Synopsis

std::string_view answer = = {};

webcpp::trystero::protocol::signal_message::candidate

A sealed ICE candidate, candidate.

Synopsis

std::string_view candidate = = {};

webcpp::trystero::protocol::signal_message::offer

The sealed SDP of an offer, offer.

Synopsis

std::string_view offer = = {};

webcpp::trystero::protocol::signal_message::offer_id

The offer the message belongs to, offerId; empty in an announcement.

Synopsis

std::string_view offer_id = = {};

webcpp::trystero::protocol::signal_message::passive

Whether the sender’s room is passive, passive: a Trystero room joined with passive announces only once it has seen a peer that is not passive.

Synopsis

bool passive = false;

webcpp::trystero::protocol::signal_message::peer_id

The sender’s id, peerId: 1 to max_peer_id_bytes bytes.

Synopsis

std::string_view peer_id = = {};

webcpp::trystero::protocol::frame_kind

The kind of an action frame, the low four bits of its second byte, in Trystero’s action wire, version 2.

Synopsis

enum class frame_kind : uint8_t;

Description

A payload that fits one frame travels inline; a larger one is offered, accepted by the receiver, then sent in chunks. Integers and float64 values are big-endian, as DataView writes them.

| Frame | Bytes | | inline (kind 0) | 2, kind or format << 4, name (32 bytes, NUL padded), metadata length | | | (uint16), metadata, payload | | offer (kind 1) | the inline header, transfer id (uint32), payload size (float64), | | | metadata | | chunk (kind 2) | 2, 2, transfer id (uint32), offset (float64), bytes | | accepted (kind 3) | 2, 3, transfer id (uint32) | | rejected (kind 4) | 2, 4, transfer id (uint32), reason (UTF-8, at most 200 bytes) | | cancelled (kind 5) | 2, 5, transfer id (uint32) |

Members

Name Description

inline_payload

A whole payload, Trystero’s inline.

offer

The offer of a transfer, Trystero’s offer.

chunk

A piece of a transfer’s payload, Trystero’s chunk.

accepted

The receiver accepts an offer, Trystero’s accepted.

rejected

The receiver refuses an offer, Trystero’s rejected.

cancelled

The sender gives a transfer up, Trystero’s cancelled.

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::frame_kind::inline_payload

A whole payload, Trystero’s inline.

Synopsis

inline_payload = 0

webcpp::trystero::protocol::frame_kind::offer

The offer of a transfer, Trystero’s offer.

Synopsis

offer = 1

webcpp::trystero::protocol::frame_kind::chunk

A piece of a transfer’s payload, Trystero’s chunk.

Synopsis

chunk = 2

webcpp::trystero::protocol::frame_kind::accepted

The receiver accepts an offer, Trystero’s accepted.

Synopsis

accepted = 3

webcpp::trystero::protocol::frame_kind::rejected

The receiver refuses an offer, Trystero’s rejected.

Synopsis

rejected = 4

webcpp::trystero::protocol::frame_kind::cancelled

The sender gives a transfer up, Trystero’s cancelled.

Synopsis

cancelled = 5

webcpp::trystero::protocol::payload_format

The format of an action’s payload, the high four bits of a frame’s second byte: how Trystero’s decodePayload reads it.

Synopsis

enum class payload_format : uint8_t;

Members

Name Description

text

UTF-8 text, a JavaScript string.

json

JSON text, any other JavaScript value.

binary

Raw bytes, an ArrayBuffer or a typed array.

See Also

"The protocol", in the guide.

webcpp::trystero::protocol::payload_format::text

UTF-8 text, a JavaScript string.

Synopsis

text = 0

webcpp::trystero::protocol::payload_format::json

JSON text, any other JavaScript value.

Synopsis

json = 1

webcpp::trystero::protocol::payload_format::binary

Raw bytes, an ArrayBuffer or a typed array.

Synopsis

binary = 2

webcpp::trystero::protocol::wire_error

Why a frame cannot be decoded; Trystero drops the peer for any of these.

Synopsis

enum class wire_error : int;

Members

Name Description

invalid_frame

Too short, too long, or a kind or a format that does not exist.

incompatible_version

A version other than action_wire_version.

invalid_control

A chunk or a transfer’s answer of the wrong size.

invalid_header

An inline frame or an offer shorter than its header.

invalid_proposal

An empty name, metadata beyond the frame, or an offer of an impossible size or length.

webcpp::trystero::protocol::wire_error::invalid_frame

Too short, too long, or a kind or a format that does not exist.

Synopsis

invalid_frame

webcpp::trystero::protocol::wire_error::incompatible_version

A version other than action_wire_version.

Synopsis

incompatible_version

webcpp::trystero::protocol::wire_error::invalid_control

A chunk or a transfer’s answer of the wrong size.

Synopsis

invalid_control

webcpp::trystero::protocol::wire_error::invalid_header

An inline frame or an offer shorter than its header.

Synopsis

invalid_header

webcpp::trystero::protocol::wire_error::invalid_proposal

An empty name, metadata beyond the frame, or an offer of an impossible size or length.

Synopsis

invalid_proposal

webcpp::trystero::protocol::append_accepted

Appends the frame accepting transfer id.

Synopsis

void
append_accepted(
    bytes& out,
    uint32_t id);

Parameters

Name Description

out

The buffer to append to.

id

The transfer’s id.

webcpp::trystero::protocol::append_base64

Appends standard base64 with padding, what btoa writes over binary text.

Synopsis

void
append_base64(
    std::pmr::string& out,
    std::span<uint8_t const> in);

Parameters

Name Description

out

The text to append to.

in

The bytes.

webcpp::trystero::protocol::append_cancelled

Appends the frame cancelling transfer id.

Synopsis

void
append_cancelled(
    bytes& out,
    uint32_t id);

Parameters

Name Description

out

The buffer to append to.

id

The transfer’s id.

webcpp::trystero::protocol::append_chunk

Appends a chunk of transfer id.

Synopsis

void
append_chunk(
    bytes& out,
    uint32_t id,
    std::size_t offset,
    std::span<uint8_t const> data);

Parameters

Name Description

out

The buffer to append to.

id

The transfer’s id.

offset

Where data starts in the payload.

data

At most chunk_size bytes of the payload.

webcpp::trystero::protocol::append_hex

Appends the lowercase hexadecimal of a byte sequence, Trystero’s toHex.

Synopsis

void
append_hex(
    std::pmr::string& out,
    std::span<uint8_t const> data);

Parameters

Name Description

out

The text to append to.

data

The bytes.

webcpp::trystero::protocol::append_inline

Appends an inline frame to out, which may already hold a room envelope’s header.

Synopsis

void
append_inline(
    bytes& out,
    std::string_view name,
    payload_format format,
    std::string_view metadata,
    std::span<uint8_t const> payload);

Parameters

Name Description

out

The buffer to append to.

name

The action’s name.

format

The payload’s format.

metadata

JSON text, or empty.

payload

The payload.

Preconditions

  • valid_action_name(name) and fits_inline(metadata.size(), payload.size()).

webcpp::trystero::protocol::append_offer

Appends an offer frame announcing size bytes under transfer id.

Synopsis

void
append_offer(
    bytes& out,
    std::string_view name,
    payload_format format,
    std::string_view metadata,
    uint32_t id,
    std::size_t size);

Parameters

Name Description

out

The buffer to append to.

name

The action’s name.

format

The payload’s format.

metadata

JSON text, or empty.

id

The transfer’s id.

size

The payload’s size in bytes.

Preconditions

  • valid_action_name(name), and the metadata fits one frame after the offer’s header.

webcpp::trystero::protocol::append_rejected

Appends the frame rejecting transfer id.

Synopsis

void
append_rejected(
    bytes& out,
    uint32_t id,
    std::string_view reason);

Parameters

Name Description

out

The buffer to append to.

id

The transfer’s id.

reason

Why, cut to max_reject_reason_bytes.

webcpp::trystero::protocol::base64_decode

The bytes of a base64 text, padded or not, as atob reads it.

Synopsis

std::optional<bytes>
base64_decode(
    std::string_view in,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The bytes, or nothing when the text is not base64.

Parameters

Name Description

in

The text.

memory

The resource the bytes are allocated in.

webcpp::trystero::protocol::base64_encode

Standard base64 with padding, what btoa writes over binary text.

Synopsis

std::pmr::string
base64_encode(
    std::span<uint8_t const> in,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The text.

Parameters

Name Description

in

The bytes.

memory

The resource the text is allocated in.

webcpp::trystero::protocol::begin_room_frame

Starts a room frame in an empty buffer: the header for token, with room reserved for payload_size more bytes.

Synopsis

bytes
begin_room_frame(
    std::string_view token,
    std::size_t payload_size,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Description

The action frame is then appended in place, so the frame is allocated and written once.

Return Value

The frame’s header.

Parameters

Name Description

token

The room’s token, room_token’s.

payload_size

The size of the action frame that will follow.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::decode_action

Decodes one action frame with exactly the checks Trystero’s handleData makes.

Synopsis

decode_result
decode_action(std::span<uint8_t const> bytes_in);

Return Value

The frame, as views into bytes_in, or why it cannot be decoded.

Parameters

Name Description

bytes_in

The action frame, out of its room envelope.

webcpp::trystero::protocol::decode_envelope

Decodes the envelope of a frame, with the checks of Trystero’s unwrapFrame.

Synopsis

std::optional<envelope>
decode_envelope(std::span<uint8_t const> frame);

Return Value

The envelope, or nothing when the frame is malformed; Trystero drops the peer then.

Parameters

Name Description

frame

A data channel message.

webcpp::trystero::protocol::derive_relays

The relays an app uses when it names none, Trystero’s getRelays: the defaults shuffled by the app id, the first redundancy kept.

Synopsis

std::pmr::vector<std::string_view>
derive_relays(
    std::string_view app_id,
    std::span<std::string_view const> defaults,
    std::size_t redundancy,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The relays, as views into defaults.

Parameters

Name Description

app_id

The application’s id, whose str_to_num seeds the shuffle.

defaults

The relays to choose from, default_nostr_relays for a room.

redundancy

How many to keep.

memory

The resource the list is allocated in.

webcpp::trystero::protocol::encode_accepted

The frame accepting a transfer, in a buffer of its own.

Synopsis

bytes
encode_accepted(
    uint32_t id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame.

Parameters

Name Description

id

The transfer’s id.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::encode_cancelled

The frame cancelling a transfer, in a buffer of its own.

Synopsis

bytes
encode_cancelled(
    uint32_t id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame.

Parameters

Name Description

id

The transfer’s id.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::encode_chunk

A chunk frame, in a buffer of its own.

Synopsis

bytes
encode_chunk(
    uint32_t id,
    std::size_t offset,
    std::span<uint8_t const> data,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame, as append_chunk writes it.

Parameters

Name Description

id

The transfer’s id.

offset

Where data starts in the payload.

data

At most chunk_size bytes of the payload.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::encode_inline

An inline frame, in a buffer of its own.

Synopsis

bytes
encode_inline(
    std::string_view name,
    payload_format format,
    std::string_view metadata,
    std::span<uint8_t const> payload,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame, as append_inline writes it.

Parameters

Name Description

name

The action’s name.

format

The payload’s format.

metadata

JSON text, or empty.

payload

The payload.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::encode_offer

An offer frame, in a buffer of its own.

Synopsis

bytes
encode_offer(
    std::string_view name,
    payload_format format,
    std::string_view metadata,
    uint32_t id,
    std::size_t size,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame, as append_offer writes it.

Parameters

Name Description

name

The action’s name.

format

The payload’s format.

metadata

JSON text, or empty.

id

The transfer’s id.

size

The payload’s size in bytes.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::encode_rejected

The frame rejecting a transfer, in a buffer of its own.

Synopsis

bytes
encode_rejected(
    uint32_t id,
    std::string_view reason,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame.

Parameters

Name Description

id

The transfer’s id.

reason

Why, cut to max_reject_reason_bytes.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::fits_inline

Whether a payload with this metadata travels inline rather than as an offer and chunks.

Synopsis

bool
fits_inline(
    std::size_t metadata_size,
    std::size_t payload_size);

Return Value

Whether the inline frame fits max_action_frame_bytes.

Parameters

Name Description

metadata_size

The metadata’s size in bytes.

payload_size

The payload’s size in bytes.

webcpp::trystero::protocol::format_sealed

The text form of a sealed signal, what Trystero’s encrypt returns: the IV’s bytes in decimal joined by commas, "$", the ciphertext in base64.

Synopsis

std::pmr::string
format_sealed(
    std::span<uint8_t const> iv,
    std::span<uint8_t const> ciphertext,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The text.

Parameters

Name Description

iv

The IV.

ciphertext

The ciphertext with its tag, as crypto.subtle.encrypt returns it.

memory

The resource the text is allocated in.

webcpp::trystero::protocol::from_hex

The bytes of a hexadecimal text, in either case.

Synopsis

std::optional<bytes>
from_hex(
    std::string_view text,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The bytes, or nothing when the text is not hexadecimal of even length.

Parameters

Name Description

text

The text.

memory

The resource the bytes are allocated in.

webcpp::trystero::protocol::handshake_proof

The password handshake’s proof, Trystero’s hashChallenge: the hexadecimal SHA-256 of "<challenge>:<password>:<app>:<room>".

Synopsis

std::pmr::string
handshake_proof(
    std::string_view challenge,
    std::string_view password,
    std::string_view app_id,
    std::string_view room_id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The proof, 64 hexadecimal digits.

Parameters

Name Description

challenge

The challenge the other peer sent.

password

The password used for this room, shared by its app.

app_id

The application’s id.

room_id

The room’s id.

memory

The resource the proof is allocated in.

webcpp::trystero::protocol::make_id

A random id of n characters of id_alphabet, Trystero’s genId.

Synopsis

template<class Generator>
std::pmr::string
make_id(
    std::size_t n,
    Generator& generator,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The id.

Template Parameters

Name Description

Generator

A uniform random bit generator.

Parameters

Name Description

n

The id’s length.

generator

The source of randomness.

memory

The resource the id is allocated in.

webcpp::trystero::protocol::parse_sealed

The parts of a sealed signal’s text, as Trystero’s decrypt splits it.

Synopsis

std::optional<sealed_parts>
parse_sealed(
    std::string_view text,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The parts, or nothing when the text has no "$", an IV byte that is not a decimal number up to 255, or a ciphertext that is not base64.

Parameters

Name Description

text

The text.

memory

The resource the parts are allocated in.

webcpp::trystero::protocol::peer_topic

The topic a peer receives its offers, answers and candidates on, Trystero’s sha1 of the root topic’s text, "@" and the peer’s id.

Synopsis

std::pmr::string
peer_topic(
    std::string_view app_id,
    std::string_view room_id,
    std::string_view peer_id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The topic’s name.

Parameters

Name Description

app_id

The application’s id.

room_id

The room’s id.

peer_id

The peer’s id.

memory

The resource the topic is allocated in.

webcpp::trystero::protocol::presence_frame

A presence frame, Trystero’s wrapRoomPresenceFrame: whether this peer is in a room.

Synopsis

bytes
presence_frame(
    std::string_view token,
    bool present,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame.

Parameters

Name Description

token

The room’s token, room_token’s.

present

Whether the peer joins (true) or leaves (false) the room.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::read_signal

The message a parsed signaling payload holds, as views into document.

Synopsis

std::optional<signal_message>
read_signal(boost::json::value const& document);

Return Value

The message, or nothing when Trystero would ignore it (not an object, no peer id, or an offer, answer or candidate that is not a non-empty string), or when a field is longer than its bound: the peer id than max_peer_id_bytes, the offer id than max_offer_id_bytes, an offer or answer than max_sealed_description_bytes, a candidate than max_sealed_candidate_bytes.

Parameters

Name Description

document

The parsed payload, which the caller keeps alive while it reads the views.

webcpp::trystero::protocol::room_frame

A room frame, Trystero’s wrapRoomFrame: an action frame in the envelope of its room.

Synopsis

bytes
room_frame(
    std::string_view token,
    std::span<uint8_t const> payload,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The frame.

Parameters

Name Description

token

The room’s token, room_token’s.

payload

The action frame.

memory

The resource the frame is allocated in.

webcpp::trystero::protocol::room_token

The token that names a room inside a data channel’s envelope: the hexadecimal SHA-256 of "Trystero:<app>:<room>".

Synopsis

std::pmr::string
room_token(
    std::string_view app_id,
    std::string_view room_id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The token, 64 hexadecimal digits.

Parameters

Name Description

app_id

The application’s id.

room_id

The room’s id.

memory

The resource the token is allocated in.

webcpp::trystero::protocol::root_topic

The topic every member of a room announces on, Trystero’s sha1 of "Trystero@<app>@<room>".

Synopsis

std::pmr::string
root_topic(
    std::string_view app_id,
    std::string_view room_id,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The topic’s name.

Parameters

Name Description

app_id

The application’s id.

room_id

The room’s id.

memory

The resource the topic is allocated in.

webcpp::trystero::protocol::seal_key

The raw AES-GCM key of a room, what Trystero’s genKey imports: the SHA-256 of "<password>:<app>:<room>".

Synopsis

std::array<uint8_t, 32>
seal_key(
    std::string_view password,
    std::string_view app_id,
    std::string_view room_id);

Return Value

The AES-256 key.

Parameters

Name Description

password

The password used for this room, shared by its app; empty means none.

app_id

The application’s id.

room_id

The room’s id.

webcpp::trystero::protocol::sealed_size

The longest text a plaintext seals to, as format_sealed writes it.

Synopsis

constexpr
std::size_t
sealed_size(std::size_t plaintext);

Description

The IV’s bytes in decimal and commas, "$", and the ciphertext with its 16-byte tag in base64.

Return Value

The size of the longest sealed text, in bytes, whatever the IV.

Parameters

Name Description

plaintext

The plaintext’s size, in bytes.

webcpp::trystero::protocol::sha1_topic

Trystero’s sha1 of a text, the name of a topic.

Synopsis

std::pmr::string
sha1_topic(
    std::string_view text,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The name, as sha1_topic_of writes it.

Parameters

Name Description

text

The text.

memory

The resource the name is allocated in.

webcpp::trystero::protocol::sha1_topic_of

Trystero’s sha1 of the concatenation of parts, the name of a topic.

Synopsis

std::pmr::string
sha1_topic_of(
    std::initializer_list<std::string_view> parts,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Description

It is the SHA-1 digest with every byte written in base 36 without padding, concatenated.

Tip: the text is not fixed width; it is a topic name and is only ever compared, never decoded.

Return Value

The name.

Parameters

Name Description

parts

The texts, in order.

memory

The resource the name is allocated in.

webcpp::trystero::protocol::sha256

The SHA-256 of a text.

Synopsis

std::array<uint8_t, 32>
sha256(std::string_view text);

Return Value

The digest.

Parameters

Name Description

text

The text.

webcpp::trystero::protocol::sha256_of

The SHA-256 of the concatenation of parts, fed to the hash one part at a time.

Synopsis

std::array<uint8_t, 32>
sha256_of(std::initializer_list<std::string_view> parts);

Description

Tip: every digest the protocol takes is of a few ids joined by separators; hashing the parts in place builds none.

Return Value

The digest.

Parameters

Name Description

parts

The texts, in order.

webcpp::trystero::protocol::shuffle

Trystero’s shuffle: Fisher-Yates driven by sin(seed++) * 10000, so every peer of an app picks the same order.

Synopsis

template<class Sequence>
Sequence
shuffle(
    Sequence items,
    double seed);

Description

Tip: std::sin and V8’s Math.sin agree on these inputs; the oracle’s fixtures check it.

Return Value

The items, shuffled.

Template Parameters

Name Description

Sequence

A sequence with size() and random access, as std::pmr::vector.

Parameters

Name Description

items

The items, shuffled in the copy returned.

seed

The seed, str_to_num of the app id for the relays.

webcpp::trystero::protocol::str_to_num

Trystero’s strToNum: the sum of the text’s UTF-16 code units, modulo limit.

Synopsis

double
str_to_num(
    std::string_view text,
    double limit = 9007199254740991.0);

Return Value

The number, in [0, limit).]

Parameters

Name Description

text

The text, in UTF-8.

limit

The modulus; the default is JavaScript’s Number.MAX_SAFE_INTEGER.

webcpp::trystero::protocol::to_hex

The lowercase hexadecimal of a byte sequence, Trystero’s toHex.

Synopsis

std::pmr::string
to_hex(
    std::span<uint8_t const> data,
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

Two digits per byte.

Parameters

Name Description

data

The bytes.

memory

The resource the text is allocated in.

webcpp::trystero::protocol::utf16_code_unit_sum

The sum of the UTF-16 code units of a UTF-8 text, what JavaScript’s charCodeAt sees.

Synopsis

double
utf16_code_unit_sum(std::string_view utf8);

Description

Tip: Trystero derives Nostr kinds and the relay shuffle seed from it, so non-ASCII ids must count as in JS.

Return Value

The sum of the code units of its UTF-16 form, a character above U+FFFF counting as its two surrogates.

Parameters

Name Description

utf8

The text, in UTF-8.

webcpp::trystero::protocol::valid_action_name

Whether a name can name an action, as Trystero’s makeAction checks it: 1 to 32 UTF-8 bytes and no NUL.

Synopsis

bool
valid_action_name(std::string_view name);

Return Value

Whether it is valid.

Parameters

Name Description

name

The name.

webcpp::trystero::protocol::write_signal

The JSON text of a message, with its members in Trystero’s order and the empty ones left out.

Synopsis

std::pmr::string
write_signal(
    signal_message const& m,
    std::string_view nonce = {},
    std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The text.

Parameters

Name Description

m

The message.

nonce

A random text, which makes repeated announcements distinct events; empty for none.

memory

The resource the text is allocated in.

webcpp::trystero::protocol::action_wire_version

The version of the action wire, every frame’s first byte, Trystero’s version.

Synopsis

inline constexpr uint8_t action_wire_version = 2;

webcpp::trystero::protocol::chunk_header_size

The size of a chunk’s header, Trystero’s chunkHeaderSize.

Synopsis

inline constexpr std::size_t chunk_header_size = 14;

webcpp::trystero::protocol::chunk_size

The most payload bytes one chunk carries, Trystero’s chunkSize.

Synopsis

inline constexpr std::size_t chunk_size = max_action_frame_bytes - chunk_header_size;

webcpp::trystero::protocol::control_header_size

The size of an accepted, rejected or cancelled frame’s header, controlHeaderSize.

Synopsis

inline constexpr std::size_t control_header_size = 6;

webcpp::trystero::protocol::default_nostr_relays

Trystero 0.26’s default Nostr relays, its defaultRelayUrls, in its order: the order is part of the relay derivation.

Synopsis

inline constexpr std::array<std::string_view, 29> default_nostr_relays = {
    "wss://0x-nostr-relay.fly.dev",
    "wss://bendernostur.duckdns.org:8443",
    "wss://cdn.satellite.earth",
    "wss://nos.lol",
    "wss://nostr.chaima.info",
    "wss://nostr.christiansass.de",
    "wss://nostr.rblb.it",
    "wss://nostr.red5d.dev",
    "wss://nostr.robosats.org",
    "wss://nostr.stakey.net",
    "wss://nostrcity-club.fly.dev",
    "wss://relay-dev.gulugulu.moe",
    "wss://relay.aarpia.com",
    "wss://relay.agentry.com",
    "wss://relay.bitmacro.cloud",
    "wss://relay.bullishbounty.com",
    "wss://relay.degmods.com",
    "wss://relay.flashapp.me",
    "wss://relay.grigic.org",
    "wss://relay.hackshed.dev",
    "wss://relay.kaleidoswap.com",
    "wss://relay.layer.systems",
    "wss://relay.nostr.blockhenge.com",
    "wss://relay.nostr.dev.br",
    "wss://relay.nostrmap.net",
    "wss://relay.novospes.com",
    "wss://relay.piazza.today",
    "wss://relay.routstr.com",
    "wss://testr.nymble.world",
};

webcpp::trystero::protocol::id_alphabet

The 62 characters Trystero draws peer ids, offer ids and nonces from, its charSet.

Synopsis

inline constexpr std::string_view id_alphabet = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";

webcpp::trystero::protocol::inline_header_size

The size of an inline frame’s header, Trystero’s inlineHeaderSize.

Synopsis

inline constexpr std::size_t inline_header_size = 36;

webcpp::trystero::protocol::max_action_frame_bytes

The largest action frame, Trystero’s maxActionFrameBytes: 16 KiB.

Synopsis

inline constexpr std::size_t max_action_frame_bytes = std::size_t{16} * 1024;

webcpp::trystero::protocol::max_action_name_bytes

The longest action name, in UTF-8 bytes.

Synopsis

inline constexpr std::size_t max_action_name_bytes = 32;

webcpp::trystero::protocol::max_offer_id_bytes

The longest offer id a signal may carry, in bytes; Trystero’s are offer_id_size.

Synopsis

inline constexpr std::size_t max_offer_id_bytes = 64;

webcpp::trystero::protocol::max_peer_id_bytes

The longest peer id a room accepts in a signal, in bytes; a signal with a longer one is ignored.

Synopsis

inline constexpr std::size_t max_peer_id_bytes = 64;

Description

Every id Trystero makes is 20 characters (selfId, peer_id_size); the bound leaves room for an application’s own ids and keeps a flood of long ids from costing more than a short one.

webcpp::trystero::protocol::max_queued_relay_bytes

The most text a room holds unsent for one relay, in UTF-8 bytes: 4 MiB, a thousand signals and more.

Synopsis

inline constexpr std::size_t max_queued_relay_bytes = std::size_t{4} << 20U;

Description

A relay that stops reading lets the texts pile up; beyond this, both backends drop its connection, which reports a close, and the room reconnects with its backoff.

webcpp::trystero::protocol::max_reject_reason_bytes

The longest reason a rejected frame carries, in UTF-8 bytes.

Synopsis

inline constexpr std::size_t max_reject_reason_bytes = 200;

webcpp::trystero::protocol::max_relay_message_bytes

The longest text a room reads from a relay, in UTF-8 bytes: 1 MiB, far beyond any message of the protocol (an event’s content is a signal, whose SDP is a few KiB) and of what public relays accept (their events are capped at 64 to 512 KiB).

Synopsis

inline constexpr std::size_t max_relay_message_bytes = std::size_t{1} << 20U;

Description

Both backends close a relay’s connection that sends a longer one, before copying it, and the room reconnects as it does to a relay that went away.

webcpp::trystero::protocol::max_room_frame_bytes

The largest room frame, Trystero’s maxRoomFrameBytes: an action frame in its envelope.

Synopsis

inline constexpr std::size_t max_room_frame_bytes = max_action_frame_bytes + 3 + max_room_token_bytes;

webcpp::trystero::protocol::max_room_token_bytes

The longest room token a room frame may carry, in bytes.

Synopsis

inline constexpr std::size_t max_room_token_bytes = 128;

webcpp::trystero::protocol::max_sealed_candidate_bytes

The longest sealed candidate a signal may carry, in bytes.

Synopsis

inline constexpr std::size_t max_sealed_candidate_bytes = std::size_t{16} * 1024;

Description

Trystero drops a candidate whose plaintext is longer than 8192 bytes (maxRemoteCandidateSdpLength), and every candidate up to that size, sealed, fits.

webcpp::trystero::protocol::max_sealed_description_bytes

The longest sealed offer or answer a signal may carry, in bytes.

Synopsis

inline constexpr std::size_t max_sealed_description_bytes = std::size_t{64} * 1024;

Description

A data channel’s SDP is a few KiB, with every candidate gathered in it a few more. Trystero bounds none; this one bounds what a negotiation copies and keeps, and refuses no SDP a browser writes for a data channel.

webcpp::trystero::protocol::nonce_size

The length of the nonce that makes repeated announcements distinct Nostr events.

Synopsis

inline constexpr std::size_t nonce_size = 8;

webcpp::trystero::protocol::offer_header_size

The size of an offer’s header, Trystero’s offerHeaderSize.

Synopsis

inline constexpr std::size_t offer_header_size = 48;

webcpp::trystero::protocol::offer_id_size

The length of the id of a Trystero offer, which its answer and candidates name.

Synopsis

inline constexpr std::size_t offer_id_size = 12;

webcpp::trystero::protocol::peer_id_size

The length of a Trystero peer id, its selfId.

Synopsis

inline constexpr std::size_t peer_id_size = 20;

webcpp::trystero::protocol::seal_iv_size

The size of the IV Trystero’s encrypt draws for AES-GCM: 16 bytes, not the usual 12.

Synopsis

inline constexpr std::size_t seal_iv_size = 16;

webcpp::trystero::bytes

Binary data allocated with std::allocator, an action’s binary payload when the user chose standard containers.

Synopsis

Declared in <webcpp/trystero/room.hpp>

using bytes = std::vector<uint8_t>;

Description

A message received as bytes is a copy of the payload, allocated with std::allocator.

webcpp::trystero::payload

What an action carries when its type is not fixed: Trystero’s text, JSON or binary payload.

Synopsis

Declared in <webcpp/trystero/room.hpp>

using payload = std::variant<std::string, boost::json::value, bytes>;

Description

A message received as payload holds the alternative of its format: a std::string for text, a boost::json::value for JSON, in the room’s JSON storage, and bytes for binary data.

webcpp::trystero::peer_id

A peer’s id in a room, Trystero’s selfId and the ids getPeers lists: 20 characters of protocol::id_alphabet unless the configuration names one.

Synopsis

Declared in <webcpp/trystero/room.hpp>

using peer_id = std::pmr::string;

Description

A room hands its ids out in its own resource.

webcpp::trystero::pmr_bytes

Binary data in a memory resource, an action’s binary payload in the room’s resource.

Synopsis

Declared in <webcpp/trystero/room.hpp>

using pmr_bytes = std::pmr::vector<uint8_t>;

Description

A message received as pmr_bytes uses the room’s effective room_options::memory, which must outlive the value. A received buffer in the same resource is moved into the value; a buffer in the app’s distinct resource is copied once into the room’s. An inline payload is taken out of its frame before being moved or copied.

See Also

"Allocation", in the guide.

webcpp::trystero::action

A named channel of messages of type T within a room, wire compatible with what Trystero’s makeAction returns.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class T>
class action;

Description

Messages arrive at the handler on_message set, or, without one, queue for async_receive, up to 1024 per action, the oldest dropped first. A message of another format than T’s is not delivered to the handler. A room makes it with room::make_action, and it keeps the room’s state alive.

Member Functions

Name Description

async_receive

Receives the next message, when no handler is set: the oldest queued, or the next to arrive.

async_send

async_send overloads

name

The action’s name.

on_message

Sets what is called with every message, Trystero’s onMessage.

Friends

Name Description

webcpp::trystero::room

A room joined over Trystero’s Nostr strategy, what Trystero’s joinRoom returns.

Template Parameters

Name Description

T

The type of its messages, one of those room::make_action lists.

See Also

"Rooms and actions", in the guide.

webcpp::trystero::action::async_receive

Receives the next message, when no handler is set: the oldest queued, or the next to arrive.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class CompletionToken>
auto
async_receive(CompletionToken&& token);

Description

Completes with (error_code, T, message_info); a message of another payload format than T’s completes with error::transfer_rejected and an empty value, and leaving the room with error::room_left. T must own its storage: a view would outlive the message.

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code, T, message_info).

Parameters

Name Description

token

The completion token.

webcpp::trystero::action::async_send

async_send overloads

Synopses

Declared in <webcpp/trystero/room.hpp>

Sends a message to every active peer, without metadata.

template<class CompletionToken>
auto
async_send(
    T value,
    CompletionToken&& token);

Sends a message to options.targets, or every active peer, Trystero’s send.

template<class CompletionToken>
auto
async_send(
    T value,
    send_options options,
    CompletionToken&& token);

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code).

Parameters

Name Description

value

The message, taken at the call as the other overload takes it.

token

The completion token.

options

The targets and the metadata.

webcpp::trystero::action::async_send

Sends a message to every active peer, without metadata.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class CompletionToken>
auto
async_send(
    T value,
    CompletionToken&& token);

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code).

Parameters

Name Description

value

The message, taken at the call as the other overload takes it.

token

The completion token.

webcpp::trystero::action::async_send

Sends a message to options.targets, or every active peer, Trystero’s send.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class CompletionToken>
auto
async_send(
    T value,
    send_options options,
    CompletionToken&& token);

Description

A payload that fits one frame is sent inline; a larger one is offered, accepted by each receiver, and sent in chunks. Completes with (error_code) once each target’s local data channel has taken the inline frame, or the last chunk of a transfer the receiver accepted, as Trystero’s send resolves: nothing confirms that the receiver has the message. It completes with the first failure: error::invalid_action_name, error::metadata_too_large, error::no_such_peer, error::transfer_rejected, error::transfer_timed_out, error::channel_stalled, error::peer_disconnected or error::room_left.

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code).

Parameters

Name Description

value

The message, taken at the call: moved when T owns its storage, copied once into the room’s resource when it is a view, and serialized at once into the room’s resource when it is a boost::json::value, or a payload that holds one.

options

The targets and the metadata.

token

The completion token.

webcpp::trystero::action::name

The action’s name.

Synopsis

Declared in <webcpp/trystero/room.hpp>

[[nodiscard]]
std::string_view
name() const;

Return Value

The name, valid as long as the action.

Note

The return value should not be discarded.

webcpp::trystero::action::on_message

Sets what is called with every message, Trystero’s onMessage.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class F>
requires std::is_invocable_v<F&, T, const message_info&>
void
on_message(F&& f);

Description

Messages that queued while the action had no handler are delivered to it first.

Template Parameters

Name Description

F

A callable with a T and a const message_info&; with a view type, the view is valid for the duration of the call.

Parameters

Name Description

f

The handler, kept in the room’s resource; it replaces the one set before.

webcpp::trystero::app

This program in one app: one peer id, and one connection per remote peer, shared by the rooms it joins, as a Trystero JS page shares them.

Synopsis

Declared in <webcpp/trystero/app.hpp>

class app;

Description

Every room uses the app’s immutable password; different passwords require separate apps with distinct peer ids.

Tip: the app and every room of it share one state; destroying the app while rooms remain changes nothing for them. Every allocation of the app goes through app_config::memory, which must outlive the app, its rooms and every operation they leave pending.

Type Aliases

Name Description

executor_type

The executor every handler and completion of the app and its rooms runs on.

Member Functions

Name Description

app [constructor]

Constructors

~app [destructor]

Releases the state; its rooms go on, and their connections with them.

operator=

Assignment operators

app_id

The application’s id, Trystero’s appId.

get_executor

The executor of the app and of its rooms.

join

Joins a room of the app, Trystero’s joinRoom for an app already configured: connects to the relays and announces this peer on the room’s topic.

on_error

Sets what is called with each part of the configuration the app set aside, with an empty peer id and error::invalid_configuration.

relay_urls

The Nostr relays every room of the app uses, derived from the app id unless the configuration named them.

self_id

This peer’s id in every room of the app, Trystero’s selfId.

Static Data Members

Name Description

max_rooms

The most rooms an app holds at once: a Trystero JS peer drops a connection that advertises more, its limit of 64 room tokens per connection.

See Also

"Rooms and actions", in the guide.

webcpp::trystero::app::executor_type

The executor every handler and completion of the app and its rooms runs on.

Synopsis

Declared in <webcpp/trystero/app.hpp>

using executor_type = boost::asio::any_io_executor;

webcpp::trystero::app::app

Constructors

Synopses

Declared in <webcpp/trystero/app.hpp>

Not copyable: an app is one peer id and its connections.

app(app const&) = delete;

Takes the state of the app moved from, which may then only be destroyed or assigned.

app(app&& other) noexcept = default;

Makes an app: draws its id unless the configuration names one, and keeps the configuration.

app(
    executor_type ex,
    backend::factory& factory,
    app_config config);

Parameters

Name Description

other

The app moved from.

ex

The executor of the app and of its rooms.

factory

The backend, which must outlive the app and its rooms.

config

The configuration, copied into config.memory.

Preconditions

  • config.memory is not null.

webcpp::trystero::app::app

Not copyable: an app is one peer id and its connections.

Synopsis

Declared in <webcpp/trystero/app.hpp>

app(app const&) = delete;

webcpp::trystero::app::app

Takes the state of the app moved from, which may then only be destroyed or assigned.

Synopsis

Declared in <webcpp/trystero/app.hpp>

app(app&& other) noexcept = default;

Parameters

Name Description

other

The app moved from.

webcpp::trystero::app::app

Makes an app: draws its id unless the configuration names one, and keeps the configuration.

Synopsis

Declared in <webcpp/trystero/app.hpp>

app(
    executor_type ex,
    backend::factory& factory,
    app_config config);

Description

What no peer could use (an id longer than any peer accepts, a negative idle time, an ICE server WebRTC refuses) is set aside, and reported to on_error.

Parameters

Name Description

ex

The executor of the app and of its rooms.

factory

The backend, which must outlive the app and its rooms.

config

The configuration, copied into config.memory.

Preconditions

  • config.memory is not null.

webcpp::trystero::app::~app

Releases the state; its rooms go on, and their connections with them.

Synopsis

Declared in <webcpp/trystero/app.hpp>

~app();

webcpp::trystero::app::operator=

Assignment operators

Synopses

Declared in <webcpp/trystero/app.hpp>

Not copy-assignable: an app is one peer id and its connections.

app&
operator=(app const&) = delete;

Releases the state it held, then takes the state of the app moved from, which may then only be destroyed or assigned; the rooms of the state released go on.

app&
operator=(app&& other) noexcept;

Return Value

This app.

Parameters

Name Description

other

The app moved from.

webcpp::trystero::app::operator=

Not copy-assignable: an app is one peer id and its connections.

Synopsis

Declared in <webcpp/trystero/app.hpp>

app&
operator=(app const&) = delete;

webcpp::trystero::app::operator=

Releases the state it held, then takes the state of the app moved from, which may then only be destroyed or assigned; the rooms of the state released go on.

Synopsis

Declared in <webcpp/trystero/app.hpp>

app&
operator=(app&& other) noexcept;

Return Value

This app.

Parameters

Name Description

other

The app moved from.

webcpp::trystero::app::app_id

The application’s id, Trystero’s appId.

Synopsis

Declared in <webcpp/trystero/app.hpp>

[[nodiscard]]
std::string_view
app_id() const;

Return Value

The id, valid as long as the app.

Note

The return value should not be discarded.

webcpp::trystero::app::get_executor

The executor of the app and of its rooms.

Synopsis

Declared in <webcpp/trystero/app.hpp>

[[nodiscard]]
executor_type const&
get_executor() const;

Return Value

The executor every handler and completion of the app and its rooms runs on.

Note

The return value should not be discarded.

webcpp::trystero::app::join

Joins a room of the app, Trystero’s joinRoom for an app already configured: connects to the relays and announces this peer on the room’s topic.

Synopsis

Declared in <webcpp/trystero/app.hpp>

boost::system::result<room>
join(
    std::string_view room_id,
    room_options options = {});

Return Value

The room, or error::already_joined while a room of that id is joined in the app, or error::too_many_rooms when max_rooms rooms count.

Parameters

Name Description

room_id

The room’s id, Trystero’s roomId, read during the call.

options

The handshake timeout, receive bound and resource of the room.

webcpp::trystero::app::on_error

Sets what is called with each part of the configuration the app set aside, with an empty peer id and error::invalid_configuration.

Synopsis

Declared in <webcpp/trystero/app.hpp>

template<class F>
requires std::is_invocable_v<F&, std::string_view, boost::system::error_code,
                                     std::string_view>
void
on_error(F&& f);

Description

The reports are delivered once the caller has returned, each once, to the handler set when they are delivered: one replaced before then gets none.

Template Parameters

Name Description

F

A callable with the peer’s id, the error and a description, the views valid for the call.

Parameters

Name Description

f

The handler, kept in the app’s resource; it replaces the one set before.

webcpp::trystero::app::relay_urls

The Nostr relays every room of the app uses, derived from the app id unless the configuration named them.

Synopsis

Declared in <webcpp/trystero/app.hpp>

[[nodiscard]]
std::span<std::pmr::string const>
relay_urls() const;

Return Value

The relays’ URLs, valid as long as the app.

Note

The return value should not be discarded.

webcpp::trystero::app::self_id

This peer’s id in every room of the app, Trystero’s selfId.

Synopsis

Declared in <webcpp/trystero/app.hpp>

[[nodiscard]]
std::string_view
self_id() const;

Return Value

The id, valid as long as the app.

Note

The return value should not be discarded.

webcpp::trystero::app::max_rooms

The most rooms an app holds at once: a Trystero JS peer drops a connection that advertises more, its limit of 64 room tokens per connection.

Synopsis

Declared in <webcpp/trystero/app.hpp>

inline constexpr static
std::size_t max_rooms = 64;

webcpp::trystero::app_config

What an app is made with: the part of Trystero’s joinRoom configuration that every room of the app shares, its peer id among it, and the one connection per remote peer its rooms share.

Synopsis

struct app_config;

Description

The app copies every part of it into memory. It is move-only, as its log is: pass it moved, or build it in place with designated initializers.

Data Members

Name Description

app_id

The application’s id, Trystero’s appId: rooms of different apps never meet.

ice_servers

The STUN and TURN servers; empty means Trystero’s defaults, default_ice_servers.

idle_timeout

How long a connection with no room bound to it after a departure stays open, Trystero’s sharedPeerIdleMs; 0 closes it at once.

log

Where the app’s and its rooms’ diagnostics go: called with a category ("app", "nostr", "signal", "webrtc", "room" or "action") and a sentence; empty means none, and then no text is composed.

memory

The resource every allocation of the app goes through: its state, its connections, the peer connections and data channels its rooms’ negotiations make, the frames received on them, the sentences of its log; not null.

password

The password shared by every room of this app, Trystero’s password.

relay_redundancy

How many of the default relays the derivation keeps, Trystero’s relayConfig.redundancy; ignored when relay_urls names the relays.

relay_urls

The Nostr relays to use, Trystero’s relayConfig.urls; empty means Trystero’s defaults, derived from the app id.

self_id

This program’s id in every room of the app, Trystero’s selfId; empty means 20 random characters, drawn once for the app.

webcpp::trystero::app_config::app_id

The application’s id, Trystero’s appId: rooms of different apps never meet.

Synopsis

std::pmr::string app_id = = {};

webcpp::trystero::app_config::ice_servers

The STUN and TURN servers; empty means Trystero’s defaults, default_ice_servers.

Synopsis

std::pmr::vector<backend::ice_server> ice_servers = = {};

Description

A server WebRTC would refuse (a URL that is not stun:, stuns:, turn: or turns: host[:port], or a TURN server without a user name and a credential) is dropped when the app is made, and reported with error::invalid_configuration; the app goes on with the others, on every backend.

webcpp::trystero::app_config::idle_timeout

How long a connection with no room bound to it after a departure stays open, Trystero’s sharedPeerIdleMs; 0 closes it at once.

Synopsis

std::chrono::milliseconds idle_timeout = {123'333};

Description

A negative value is replaced with 0, and reported with error::invalid_configuration.

webcpp::trystero::app_config::log

Where the app’s and its rooms’ diagnostics go: called with a category ("app", "nostr", "signal", "webrtc", "room" or "action") and a sentence; empty means none, and then no text is composed.

Synopsis

/* implementation-defined */ log = = {};

Description

The app moves the sink into memory, and composes each sentence there.

webcpp::trystero::app_config::memory

The resource every allocation of the app goes through: its state, its connections, the peer connections and data channels its rooms’ negotiations make, the frames received on them, the sentences of its log; not null.

Synopsis

std::pmr::memory_resource* memory = std::pmr::get_default_resource();

Description

What libdatachannel, OpenSSL and the browser’s JavaScript allocate stays theirs. The app uses the resource from its executor only, so it need not be thread-safe when that executor runs on one thread. It must outlive the app, its rooms, retained values allocated in it and every operation they leave pending: on Asio, until the io_context their executor belongs to has run out of their work or has been destroyed, so a resource is declared before its io_context; in the browser, until the app’s and its rooms’ pending promises have settled. The app and its rooms allocate and free per event, so a resource that reuses what is freed (a pool) suits a long-lived app, and a monotonic one only a short-lived one.

See Also

"Allocation", in the guide.

webcpp::trystero::app_config::password

The password shared by every room of this app, Trystero’s password.

Synopsis

std::pmr::string password = = {};

Description

The app copies it into its resource and keeps it unchanged. Different passwords require separate apps with distinct peer ids, so a connection authenticated in one domain cannot be reused in another. Empty means a passwordless domain. Signals are still sealed with the app and room ids, and the room handshake then has no password challenge.

webcpp::trystero::app_config::relay_redundancy

How many of the default relays the derivation keeps, Trystero’s relayConfig.redundancy; ignored when relay_urls names the relays.

Synopsis

std::size_t relay_redundancy = 5;

webcpp::trystero::app_config::relay_urls

The Nostr relays to use, Trystero’s relayConfig.urls; empty means Trystero’s defaults, derived from the app id.

Synopsis

std::pmr::vector<std::pmr::string> relay_urls = = {};

webcpp::trystero::app_config::self_id

This program’s id in every room of the app, Trystero’s selfId; empty means 20 random characters, drawn once for the app.

Synopsis

std::pmr::string self_id = = {};

Description

Other peers ignore an id longer than protocol::max_peer_id_bytes (64): the app replaces one with 20 random characters, and reports it with error::invalid_configuration.

webcpp::trystero::error_category_impl

The category of error, named "webcpp.trystero", whose messages are fixed texts.

Synopsis

class error_category_impl final
    : public boost::system::error_category

Description

A program reaches its one instance through error_category.

Base Classes

Name Description

boost::system::error_category

Member Functions

Name Description

message

message overloads

name [virtual]

The category’s name.

Non-Member Functions

Name Description

operator!=

operator<

operator==

webcpp::trystero::error_category_impl::message

message overloads

Synopses

The message of a value of error, as a std::string.

virtual
std::string
message(int ev) const override;

The message of a value of error without a std::string.

virtual
char const*
message(
    int ev,
    char*,
    std::size_t) const noexcept override;

Return Value

  • The message, or "unknown webcpp.trystero error" for a value error does not name.

  • The message, a static text.

Parameters

Name Description

ev

The value, an error as an int.

webcpp::trystero::error_category_impl::message

The message of a value of error, as a std::string.

Synopsis

virtual
std::string
message(int ev) const override;

Return Value

The message, or "unknown webcpp.trystero error" for a value error does not name.

Parameters

Name Description

ev

The value, an error as an int.

webcpp::trystero::error_category_impl::message

The message of a value of error without a std::string.

Synopsis

virtual
char const*
message(
    int ev,
    char*,
    std::size_t) const noexcept override;

Description

A room logs through this form, which allocates nothing: the messages are static texts, and the buffer is not used.

Return Value

The message, a static text.

Parameters

Name Description

ev

The value, an error as an int.

webcpp::trystero::error_category_impl::name

The category’s name.

Synopsis

virtual
char const*
name() const noexcept override;

Return Value

"webcpp.trystero".

webcpp::trystero::handler

A move-only callable of a signature, whose target is allocated through a memory resource; see the partial specialization handler<R(Args...)>.

Synopsis

template<class Signature>
class handler;

Specializations

Name Description

handler<R(Args...)>

A move-only callable of the signature R(Args...), which holds its target inline when the target fits in three pointers and is nothrow-movable, and otherwise in storage of its allocator.

Template Parameters

Name Description

Signature

A function type, R(Args...).

See Also

"Allocation", in the guide.

webcpp::trystero::handler<R(Args...)>

A move-only callable of the signature R(Args...), which holds its target inline when the target fits in three pointers and is nothrow-movable, and otherwise in storage of its allocator.

Synopsis

template<
    class R,
    class... Args>
class handler<R(Args...)>;

Description

It replaces std::function wherever trystero keeps a handler, including the backend, app, rooms and log sink, since std::function takes no allocator since C++17. Each handler uses its owner’s std::pmr::polymorphic_allocator<>, over app_config::memory or the effective room_options::memory; one constructed from a callable alone uses the default resource. The resource must outlive the handler.

Tip: a move assignment carries the source’s allocator with its target, as moving the target there does; the allocator-extended move, which a std::pmr container uses, moves the target into another resource when the two differ.

Type Aliases

Name Description

allocator_type

What the handler allocates its target with when the target does not fit inline.

Member Functions

Name Description

handler [constructor]

Constructors

~handler [destructor]

Destroys the target, and gives its storage back to the allocator.

operator=

Assignment operators

get_allocator

The allocator the target is allocated with when it does not fit inline.

operator()

Calls the target.

operator bool

Whether the handler has a target.

Template Parameters

Name Description

R

The result type.

Args

The parameter types.

See Also

"Allocation", in the guide.

webcpp::trystero::handler<R(Args...)>::allocator_type

What the handler allocates its target with when the target does not fit inline.

Synopsis

using allocator_type = std::pmr::polymorphic_allocator<>;

webcpp::trystero::handler<R(Args...)>::handler

Constructors

Synopses

Constructs an empty handler, which must not be called.

handler() noexcept = default;

Not copyable: a handler owns its target, which may not be copyable.

handler(handler const&) = delete;

Constructs a handler of other’s target and allocator.

handler(handler&& other) noexcept;

Constructs an empty handler, which must not be called, from nullptr.

handler(std::nullptr_t) noexcept;

Constructs a handler of f, allocated with the default resource when it does not fit inline.

template<class F>
requires (!std::is_same_v<std::remove_cvref_t<F>, handler> &&
                 std::is_invocable_r_v<R, std::decay_t<F>&, Args...> &&
                 std::is_move_constructible_v<std::decay_t<F>>)
handler(F&& f);

Constructs a handler of other’s target, moved into allocator’s resource when it differs from other’s: the allocator-extended move a std::pmr container uses.

handler(
    std::allocator_arg_t,
    allocator_type allocator,
    handler&& other);

Constructs a handler of f, allocated with allocator when it does not fit inline.

template<class F>
requires (!std::is_same_v<std::remove_cvref_t<F>, handler> &&
                 std::is_invocable_r_v<R, std::decay_t<F>&, Args...> &&
                 std::is_move_constructible_v<std::decay_t<F>>)
handler(
    std::allocator_arg_t,
    allocator_type allocator,
    F&& f);

Template Parameters

Name Description

F

The callable’s type, invocable with Args... and returning something convertible to R.

Parameters

Name Description

other

The handler whose target is taken; it is left empty.

f

The callable, moved or copied into the handler.

allocator

What the handler keeps, and allocates the target with when it moves it.

webcpp::trystero::handler<R(Args...)>::handler

Constructs an empty handler, which must not be called.

Synopsis

handler() noexcept = default;

webcpp::trystero::handler<R(Args...)>::handler

Not copyable: a handler owns its target, which may not be copyable.

Synopsis

handler(handler const&) = delete;

webcpp::trystero::handler<R(Args...)>::handler

Constructs a handler of other’s target and allocator.

Synopsis

handler(handler&& other) noexcept;

Parameters

Name Description

other

The handler whose target is taken; it is left empty.

webcpp::trystero::handler<R(Args...)>::handler

Constructs an empty handler, which must not be called, from nullptr.

Synopsis

handler(std::nullptr_t) noexcept;

webcpp::trystero::handler<R(Args...)>::handler

Constructs a handler of f, allocated with the default resource when it does not fit inline.

Synopsis

template<class F>
requires (!std::is_same_v<std::remove_cvref_t<F>, handler> &&
                 std::is_invocable_r_v<R, std::decay_t<F>&, Args...> &&
                 std::is_move_constructible_v<std::decay_t<F>>)
handler(F&& f);

Template Parameters

Name Description

F

The callable’s type, invocable with Args... and returning something convertible to R.

Parameters

Name Description

f

The callable, moved or copied into the handler.

webcpp::trystero::handler<R(Args...)>::handler

Constructs a handler of other’s target, moved into allocator’s resource when it differs from other’s: the allocator-extended move a std::pmr container uses.

Synopsis

handler(
    std::allocator_arg_t,
    allocator_type allocator,
    handler&& other);

Parameters

Name Description

allocator

What the handler keeps, and allocates the target with when it moves it.

other

The handler whose target is taken; it is left empty.

webcpp::trystero::handler<R(Args...)>::handler

Constructs a handler of f, allocated with allocator when it does not fit inline.

Synopsis

template<class F>
requires (!std::is_same_v<std::remove_cvref_t<F>, handler> &&
                 std::is_invocable_r_v<R, std::decay_t<F>&, Args...> &&
                 std::is_move_constructible_v<std::decay_t<F>>)
handler(
    std::allocator_arg_t,
    allocator_type allocator,
    F&& f);

Template Parameters

Name Description

F

The callable’s type, invocable with Args... and returning something convertible to R.

Parameters

Name Description

allocator

What the target is allocated with, and what the handler keeps; its resource must outlive the handler.

f

The callable, moved or copied into the handler.

webcpp::trystero::handler<R(Args...)>::~handler

Destroys the target, and gives its storage back to the allocator.

Synopsis

~handler();

webcpp::trystero::handler<R(Args...)>::operator=

Assignment operators

Synopses

Not copy-assignable: a handler owns its target, which may not be copyable.

handler&
operator=(handler const&) = delete;

Destroys the target, then takes other’s target and allocator.

handler&
operator=(handler&& other) noexcept;

Destroys the target, which leaves the handler empty.

handler&
operator=(std::nullptr_t) noexcept;

Return Value

This handler.

Parameters

Name Description

other

The handler whose target is taken; it is left empty.

webcpp::trystero::handler<R(Args...)>::operator=

Not copy-assignable: a handler owns its target, which may not be copyable.

Synopsis

handler&
operator=(handler const&) = delete;

webcpp::trystero::handler<R(Args...)>::operator=

Destroys the target, then takes other’s target and allocator.

Synopsis

handler&
operator=(handler&& other) noexcept;

Return Value

This handler.

Parameters

Name Description

other

The handler whose target is taken; it is left empty.

webcpp::trystero::handler<R(Args...)>::operator=

Destroys the target, which leaves the handler empty.

Synopsis

handler&
operator=(std::nullptr_t) noexcept;

Return Value

This handler.

webcpp::trystero::handler<R(Args...)>::get_allocator

The allocator the target is allocated with when it does not fit inline.

Synopsis

[[nodiscard]]
allocator_type
get_allocator() const noexcept;

Return Value

The handler’s allocator.

Note

The return value should not be discarded.

webcpp::trystero::handler<R(Args...)>::operator()

Calls the target.

Synopsis

R
operator()(Args... args);

Return Value

What the target returns, converted to R.

Parameters

Name Description

args

The arguments, forwarded to the target.

Preconditions

  • The handler is not empty.

webcpp::trystero::handler<R(Args...)>::operator bool

Whether the handler has a target.

Synopsis

explicit
operator bool() const noexcept;

Return Value

Whether it may be called.

webcpp::trystero::json_resource

A boost::container::pmr::memory_resource that allocates from a std::pmr::memory_resource.

Synopsis

class json_resource final
    : public boost::container::pmr::memory_resource

Description

A room makes one over room_options::memory, or the app’s resource when null, as the JSON storage every JSON value of the room is allocated in: what it parses from a relay, a signal or the handshake, and the boost::json::value an action’s handler receives. The storage is counted, so a value a handler keeps keeps it alive; Boost.JSON allocates that count with operator new, once per room.

Base Classes

Name Description

boost::container::pmr::memory_resource

Member Functions

Name Description

json_resource [constructor]

Constructs a resource that allocates from upstream.

upstream

The resource this one allocates from.

Protected Member Functions

Name Description

do_allocate [virtual]

Allocates from the upstream resource.

do_deallocate [virtual]

Gives storage back to the upstream resource.

do_is_equal [virtual]

Whether other is this resource: it is equal to itself alone.

Non-Member Functions

Name Description

operator!=

Returns: !(a == b).

operator==

Returns: &a == &b || a.is_equal(b).

See Also

"Allocation", in the guide.

webcpp::trystero::json_resource::json_resource

Constructs a resource that allocates from upstream.

Synopsis

explicit
json_resource(std::pmr::memory_resource* upstream) noexcept;

Parameters

Name Description

upstream

The resource every allocation goes to; it must outlive this resource and every value allocated in it.

webcpp::trystero::json_resource::upstream

The resource this one allocates from.

Synopsis

[[nodiscard]]
std::pmr::memory_resource*
upstream() const noexcept;

Return Value

The upstream it was constructed with.

Note

The return value should not be discarded.

webcpp::trystero::json_resource::do_allocate

Allocates from the upstream resource.

Synopsis

virtual
void*
do_allocate(
    std::size_t bytes,
    std::size_t alignment) override;

Return Value

The storage, from the upstream resource.

Parameters

Name Description

bytes

The size.

alignment

The alignment.

webcpp::trystero::json_resource::do_deallocate

Gives storage back to the upstream resource.

Synopsis

virtual
void
do_deallocate(
    void* pointer,
    std::size_t bytes,
    std::size_t alignment) override;

Parameters

Name Description

pointer

The storage, which do_allocate returned.

bytes

The size it was allocated with.

alignment

The alignment it was allocated with.

webcpp::trystero::json_resource::do_is_equal

Whether other is this resource: it is equal to itself alone.

Synopsis

[[nodiscard]]
virtual
bool
do_is_equal(boost::container::pmr::memory_resource const& other) const noexcept override;

Description

Tip: another adapter over the same upstream would do too, but telling one apart from another resource needs RTTI, which a user may build without; an unequal resource costs Boost.JSON a copy where it could have moved.

Return Value

Whether other is this resource.

Note

The return value should not be discarded.

Parameters

Name Description

other

The resource to compare with.

webcpp::trystero::message_info

Who sent a message, and the metadata it carried, the second and third arguments of Trystero’s action handler, in the receiving room’s resource.

Synopsis

Declared in <webcpp/trystero/room.hpp>

struct message_info;

Data Members

Name Description

from

The sender’s id.

metadata

The metadata the sender passed, as JSON text, or empty when it passed none.

webcpp::trystero::message_info::from

The sender’s id.

Synopsis

Declared in <webcpp/trystero/room.hpp>

peer_id from;

webcpp::trystero::message_info::metadata

The metadata the sender passed, as JSON text, or empty when it passed none.

Synopsis

Declared in <webcpp/trystero/room.hpp>

std::pmr::string metadata;

webcpp::trystero::room

A room joined over Trystero’s Nostr strategy, what Trystero’s joinRoom returns.

Synopsis

Declared in <webcpp/trystero/room.hpp>

class room;

Description

app::join makes it, joined; async_leave leaves with Trystero’s farewell, and destroying it leaves at once. Its peers meet over the relays, connect over WebRTC through the connections its app shares between its rooms, and exchange the messages of the actions it makes, wire compatible with Trystero’s JavaScript.

Every room uses its app’s immutable password. Initial sealed signaling authenticates that domain before a connection is adopted. The unchanged JS room handshake verifies only at the lower id; different passwords require separate apps with distinct self ids.

Tip: every handler the room calls, and every completion, runs on its app’s executor. The views a handler receives are valid for the duration of the call. Every allocation of the room goes through its effective room_options::memory, the app’s resource when null; shared connections and received frames use app_config::memory. A completion handler is allocated as its own associated allocator says. Destroying the room leaves operations pending on its executor, so both resources must outlive those operations and every retained value allocated in them.

Type Aliases

Name Description

executor_type

The executor every handler and completion of the room runs on.

Member Functions

Name Description

room [constructor]

Constructors

~room [destructor]

Leaves at once, without the farewell’s grace period: every peer is told, the room lets go of the app’s connections, which the app’s other rooms may go on using, and every pending operation completes with error::room_left.

operator=

Assignment operators

async_leave

Says goodbye to every peer and closes everything, Trystero’s leave.

async_ping

Measures the round trip to a peer, Trystero’s ping.

get_executor

The executor of the room’s app.

make_action

Makes an action of this room, Trystero’s makeAction: a named channel of messages.

on_error

Sets what is called with each problem with one peer that the room handled itself, as a wrong password or a timed-out handshake, Trystero’s onPeerError.

on_peer_join

Sets what is called with each peer that joins, once its handshake completes, Trystero’s onPeerJoin.

on_peer_leave

Sets what is called with each joined peer that leaves, Trystero’s onPeerLeave.

peers

The peers whose handshake completed, Trystero’s getPeers.

relay_urls

The Nostr relays this room uses, its app’s: derived from the app id unless the app’s configuration named them.

self_id

This peer’s id in the room, Trystero’s selfId: its app’s.

Friends

Name Description

webcpp::trystero::app

This program in one app: one peer id, and one connection per remote peer, shared by the rooms it joins, as a Trystero JS page shares them.

See Also

"Rooms and actions", in the guide.

webcpp::trystero::room::executor_type

The executor every handler and completion of the room runs on.

Synopsis

Declared in <webcpp/trystero/room.hpp>

using executor_type = boost::asio::any_io_executor;

webcpp::trystero::room::room

Constructors

Synopses

Declared in <webcpp/trystero/room.hpp>

Not copyable: a room is one membership.

room(room const&) = delete;

Takes the membership of the room moved from, which may then only be destroyed or assigned.

room(room&&) noexcept = default;

webcpp::trystero::room::room

Not copyable: a room is one membership.

Synopsis

Declared in <webcpp/trystero/room.hpp>

room(room const&) = delete;

webcpp::trystero::room::room

Takes the membership of the room moved from, which may then only be destroyed or assigned.

Synopsis

Declared in <webcpp/trystero/room.hpp>

room(room&&) noexcept = default;

webcpp::trystero::room::~room

Leaves at once, without the farewell’s grace period: every peer is told, the room lets go of the app’s connections, which the app’s other rooms may go on using, and every pending operation completes with error::room_left.

Synopsis

Declared in <webcpp/trystero/room.hpp>

~room();

webcpp::trystero::room::operator=

Assignment operators

Synopses

Declared in <webcpp/trystero/room.hpp>

Not copy-assignable: a room is one membership.

room&
operator=(room const&) = delete;

Leaves the membership this room held at once, as the destructor does, then takes the membership of the room moved from, which may then only be destroyed or assigned.

room&
operator=(room&& other) noexcept;

Return Value

This room.

Parameters

Name Description

other

The room moved from.

webcpp::trystero::room::operator=

Not copy-assignable: a room is one membership.

Synopsis

Declared in <webcpp/trystero/room.hpp>

room&
operator=(room const&) = delete;

webcpp::trystero::room::operator=

Leaves the membership this room held at once, as the destructor does, then takes the membership of the room moved from, which may then only be destroyed or assigned.

Synopsis

Declared in <webcpp/trystero/room.hpp>

room&
operator=(room&& other) noexcept;

Description

Every peer of the membership left is told, the room lets go of the app’s connections, which the app’s other rooms may go on using, and every operation pending on it completes with error::room_left, even while an action of it lives.

Return Value

This room.

Parameters

Name Description

other

The room moved from.

webcpp::trystero::room::async_leave

Says goodbye to every peer and closes everything, Trystero’s leave.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class CompletionToken>
auto
async_leave(CompletionToken&& token);

Description

Each peer is sent @_leave, and 99 ms later, as Trystero waits, the room lets go of the app’s connections, which the app’s other rooms may go on using, and every pending operation completes with error::room_left. Completes with (error_code), empty, also when the room was already left.

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code).

Parameters

Name Description

token

The completion token.

webcpp::trystero::room::async_ping

Measures the round trip to a peer, Trystero’s ping.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class CompletionToken>
auto
async_ping(
    std::string_view peer,
    CompletionToken&& token);

Description

Completes with (error_code, std::chrono::milliseconds): the time from sending @_ping to receiving its @_pong, or error::no_such_peer when the peer is not active, error::peer_disconnected when it leaves first, and error::room_left.

Return Value

What the token’s initiation returns.

Template Parameters

Name Description

CompletionToken

An Asio completion token for void(error_code, milliseconds).

Parameters

Name Description

peer

The peer’s id, copied at the call, so a deferred operation does not depend on the caller’s string.

token

The completion token.

webcpp::trystero::room::get_executor

The executor of the room’s app.

Synopsis

Declared in <webcpp/trystero/room.hpp>

[[nodiscard]]
executor_type const&
get_executor() const;

Return Value

The executor every handler and completion of the room runs on.

Note

The return value should not be discarded.

webcpp::trystero::room::make_action

Makes an action of this room, Trystero’s makeAction: a named channel of messages.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class T>
action<T>
make_action(std::string_view name);

Description

Frames of the action that arrived before it was made, which the room keeps for a while (64 per peer, for 120 s), are delivered to it.

Return Value

The action.

Template Parameters

Name Description

T

The type of its messages: std::string, std::pmr::string, std::string_view, boost::json::value, bytes, pmr_bytes, std::span<const std::uint8_t>, or payload for any of Trystero’s formats.

Parameters

Name Description

name

The action’s name, 1 to 32 UTF-8 bytes without NUL; with another, every send fails with error::invalid_action_name.

webcpp::trystero::room::on_error

Sets what is called with each problem with one peer that the room handled itself, as a wrong password or a timed-out handshake, Trystero’s onPeerError.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class F>
requires std::is_invocable_v<F&, std::string_view, boost::system::error_code,
                                     std::string_view>
void
on_error(F&& f);

Description

Signals that do not open with the room key (error::wrong_password) are reported once per negotiation, and paced: the first of a quiet 10 s at once, with its peer, and the others of those 10 s in one report, with an empty peer id and a description that starts with their number, since anyone who reads the relays can send them from any number of fresh ids. A part of the configuration the app set aside is reported to app::on_error, not here.

Template Parameters

Name Description

F

A callable with the peer’s id, the error and a description, the views valid for the call.

Parameters

Name Description

f

The handler, kept in the room’s resource; it replaces the one set before.

webcpp::trystero::room::on_peer_join

Sets what is called with each peer that joins, once its handshake completes, Trystero’s onPeerJoin.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class F>
requires std::is_invocable_v<F&, std::string_view>
void
on_peer_join(F&& f);

Template Parameters

Name Description

F

A callable with a std::string_view, the peer’s id, valid for the call.

Parameters

Name Description

f

The handler, kept in the room’s resource; it replaces the one set before.

webcpp::trystero::room::on_peer_leave

Sets what is called with each joined peer that leaves, Trystero’s onPeerLeave.

Synopsis

Declared in <webcpp/trystero/room.hpp>

template<class F>
requires std::is_invocable_v<F&, std::string_view>
void
on_peer_leave(F&& f);

Template Parameters

Name Description

F

A callable with a std::string_view, the peer’s id, valid for the call.

Parameters

Name Description

f

The handler, kept in the room’s resource; it replaces the one set before.

webcpp::trystero::room::peers

The peers whose handshake completed, Trystero’s getPeers.

Synopsis

Declared in <webcpp/trystero/room.hpp>

[[nodiscard]]
std::pmr::vector<peer_id>
peers() const;

Return Value

Their ids, in the room’s resource.

Note

The return value should not be discarded.

webcpp::trystero::room::relay_urls

The Nostr relays this room uses, its app’s: derived from the app id unless the app’s configuration named them.

Synopsis

Declared in <webcpp/trystero/room.hpp>

[[nodiscard]]
std::span<std::pmr::string const>
relay_urls() const;

Return Value

The relays’ URLs, valid as long as the room.

Note

The return value should not be discarded.

webcpp::trystero::room::self_id

This peer’s id in the room, Trystero’s selfId: its app’s.

Synopsis

Declared in <webcpp/trystero/room.hpp>

[[nodiscard]]
std::string_view
self_id() const;

Return Value

The id, valid as long as the room.

Note

The return value should not be discarded.

webcpp::trystero::room_options

What a room of an app is joined with: the part of Trystero’s joinRoom configuration that is the room’s own.

Synopsis

struct room_options;

Data Members

Name Description

handshake_timeout

How long a connected peer has to finish the handshake before it is dropped, Trystero’s handshakeTimeoutMs.

max_receive_bytes

The most bytes the room reserves at once for the transfers it receives, across peers, Trystero’s maxReceiveBytes.

memory

The resource every allocation of the room goes through: its state, bindings, negotiations, relays, actions, sent frames, transfers and JSON.

webcpp::trystero::room_options::handshake_timeout

How long a connected peer has to finish the handshake before it is dropped, Trystero’s handshakeTimeoutMs.

Synopsis

std::chrono::milliseconds handshake_timeout = {10'000};

webcpp::trystero::room_options::max_receive_bytes

The most bytes the room reserves at once for the transfers it receives, across peers, Trystero’s maxReceiveBytes.

Synopsis

std::size_t max_receive_bytes = std::size_t{256} * 1024 * 1024;

webcpp::trystero::room_options::memory

The resource every allocation of the room goes through: its state, bindings, negotiations, relays, actions, sent frames, transfers and JSON.

Synopsis

std::pmr::memory_resource* memory = nullptr;

Description

Null means the app’s resource.

It must outlive the room, every value it hands out and every operation it leaves pending: on Asio, until its io_context has run out of the room’s work or has been destroyed; in the browser, until its pending promises have settled. Received frames and shared connections use the app’s resource.

webcpp::trystero::send_options

Where an action’s message goes and what it carries beside its payload, the target and the metadata of Trystero’s send.

Synopsis

Declared in <webcpp/trystero/room.hpp>

struct send_options;

Description

Tip: it owns its strings because a completion token such as asio::deferred starts the operation later, after the caller’s views may be gone.

Data Members

Name Description

metadata

The metadata, as JSON text, or empty for none.

targets

The peers to send to; empty means every active peer.

webcpp::trystero::send_options::metadata

The metadata, as JSON text, or empty for none.

Synopsis

Declared in <webcpp/trystero/room.hpp>

std::pmr::string metadata = = {};

webcpp::trystero::send_options::targets

The peers to send to; empty means every active peer.

Synopsis

Declared in <webcpp/trystero/room.hpp>

std::pmr::vector<peer_id> targets = = {};

webcpp::trystero::error

What an operation of the library completes with when it does not succeed.

Synopsis

enum class error : int;

Description

Each value is one of the errors Trystero rejects a promise with or reports to onPeerError, as a code of error_category rather than a message: an operation completes with it, and room::on_error reports it with the peer it concerns.

Members

Name Description

room_left

The room was left while the operation was pending, Trystero’s "room left".

no_such_peer

The target is not an active peer of the room, Trystero’s "no active peer with id".

peer_disconnected

The peer went away before the operation finished, Trystero’s "peer disconnected".

invalid_action_name

The action’s name is not 1 to 32 UTF-8 bytes without NUL, as Trystero requires.

metadata_too_large

The action’s metadata does not fit one frame, Trystero’s "action metadata is too large".

transfer_rejected

The receiver refused a transfer, or a received payload is not of the awaited type.

transfer_timed_out

The receiver neither accepted nor refused a transfer in time.

channel_stalled

The data channel stopped draining while a transfer waited for it.

wrong_password

A peer’s signal or handshake used another password, or the peer sent a handshake message the protocol does not expect, Trystero’s "incorrect password for overlapping room".

handshake_timed_out

A connected peer did not finish the handshake in time, Trystero’s "handshake timed out".

handshake_failed

Reserved: nothing reports it today.

backend_failure

The browser or the native stack refused an operation.

invalid_configuration

The room’s configuration names something no backend can use, as an ICE server whose URL WebRTC refuses; the room goes on without it, and room::on_error reports it with no peer.

already_joined

The app already holds a room of that id; a room is joined once per app.

too_many_rooms

The app already holds as many rooms as a peer accepts, 64 of them.

See Also

"Rooms and actions", in the guide.

webcpp::trystero::error::room_left

The room was left while the operation was pending, Trystero’s "room left".

Synopsis

room_left = 1

webcpp::trystero::error::no_such_peer

The target is not an active peer of the room, Trystero’s "no active peer with id".

Synopsis

no_such_peer

webcpp::trystero::error::peer_disconnected

The peer went away before the operation finished, Trystero’s "peer disconnected".

Synopsis

peer_disconnected

webcpp::trystero::error::invalid_action_name

The action’s name is not 1 to 32 UTF-8 bytes without NUL, as Trystero requires.

Synopsis

invalid_action_name

webcpp::trystero::error::metadata_too_large

The action’s metadata does not fit one frame, Trystero’s "action metadata is too large".

Synopsis

metadata_too_large

webcpp::trystero::error::transfer_rejected

The receiver refused a transfer, or a received payload is not of the awaited type.

Synopsis

transfer_rejected

webcpp::trystero::error::transfer_timed_out

The receiver neither accepted nor refused a transfer in time.

Synopsis

transfer_timed_out

webcpp::trystero::error::channel_stalled

The data channel stopped draining while a transfer waited for it.

Synopsis

channel_stalled

webcpp::trystero::error::wrong_password

A peer’s signal or handshake used another password, or the peer sent a handshake message the protocol does not expect, Trystero’s "incorrect password for overlapping room".

Synopsis

wrong_password

webcpp::trystero::error::handshake_timed_out

A connected peer did not finish the handshake in time, Trystero’s "handshake timed out".

Synopsis

handshake_timed_out

webcpp::trystero::error::handshake_failed

Reserved: nothing reports it today.

Synopsis

handshake_failed

Description

A handshake message of the wrong shape reports wrong_password, as Trystero’s handshake does.

webcpp::trystero::error::backend_failure

The browser or the native stack refused an operation.

Synopsis

backend_failure

webcpp::trystero::error::invalid_configuration

The room’s configuration names something no backend can use, as an ICE server whose URL WebRTC refuses; the room goes on without it, and room::on_error reports it with no peer.

Synopsis

invalid_configuration

webcpp::trystero::error::already_joined

The app already holds a room of that id; a room is joined once per app.

Synopsis

already_joined

webcpp::trystero::error::too_many_rooms

The app already holds as many rooms as a peer accepts, 64 of them.

Synopsis

too_many_rooms

webcpp::trystero::default_ice_servers

Trystero’s default STUN servers, its defaultIceServers: Google’s and Cloudflare’s.

Synopsis

std::pmr::vector<backend::ice_server>
default_ice_servers(std::pmr::memory_resource* memory = std::pmr::get_default_resource());

Return Value

The servers, an app’s when its configuration names none.

Parameters

Name Description

memory

The resource the list and its strings are allocated in.

webcpp::trystero::error_category

The category of every error code.

Synopsis

boost::system::error_category const&
error_category() noexcept;

Return Value

The one instance of error_category_impl.

webcpp::trystero::make_error_code

An error code of error_category, which boost::system::error_code finds by argument-dependent lookup.

Synopsis

boost::system::error_code
make_error_code(error e) noexcept;

Return Value

The code of e.

Parameters

Name Description

e

The error.

webcpp::trystero::operator==

Returns: &a == &b || a.is_equal(b).

Synopses

constexpr
bool
operator==(
    error_category_impl const& lhs,
    error_category_impl const& rhs) noexcept;

Returns: &a == &b || a.is_equal(b).

[[nodiscard]]
bool
operator==(
    json_resource const& a,
    json_resource const& b) noexcept;

webcpp::trystero::operator==

Synopsis

constexpr
bool
operator==(
    error_category_impl const& lhs,
    error_category_impl const& rhs) noexcept;
Note

This function is a hidden friend of error_category_impl, declared inside its base class error_category: it can be found only through argument-dependent lookup.

webcpp::trystero::operator==

Returns: &a == &b || a.is_equal(b).

Synopsis

[[nodiscard]]
bool
operator==(
    json_resource const& a,
    json_resource const& b) noexcept;
Note

This function is a hidden friend of json_resource, declared inside its base class memory_resource: it can be found only through argument-dependent lookup.

Return Value

Note

The return value should not be discarded.

webcpp::trystero::operator!=

Returns: !(a == b).

Synopses

constexpr
bool
operator!=(
    error_category_impl const& lhs,
    error_category_impl const& rhs) noexcept;

Returns: !(a == b).

[[nodiscard]]
bool
operator!=(
    json_resource const& a,
    json_resource const& b) noexcept;

webcpp::trystero::operator!=

Synopsis

constexpr
bool
operator!=(
    error_category_impl const& lhs,
    error_category_impl const& rhs) noexcept;
Note

This function is a hidden friend of error_category_impl, declared inside its base class error_category: it can be found only through argument-dependent lookup.

webcpp::trystero::operator!=

Returns: !(a == b).

Synopsis

[[nodiscard]]
bool
operator!=(
    json_resource const& a,
    json_resource const& b) noexcept;
Note

This function is a hidden friend of json_resource, declared inside its base class memory_resource: it can be found only through argument-dependent lookup.

Return Value

Note

The return value should not be discarded.

webcpp::trystero::operator<

Synopsis

constexpr
bool
operator<(
    error_category_impl const& lhs,
    error_category_impl const& rhs) noexcept;
Note

This function is a hidden friend of error_category_impl, declared inside its base class error_category: it can be found only through argument-dependent lookup.

WEBCPP_TRYSTERO_NO_EXCEPTIONS

Defined when trystero is built without exceptions: when webcpp has none (the compiler has none, or the developer defined WEBCPP_NO_EXCEPTIONS for every webcpp library), or when the developer defines it alone, to disable trystero’s in a build that has them.

Synopsis

#define WEBCPP_TRYSTERO_NO_EXCEPTIONS

Description

trystero raises no exception of its own: every operation that can fail completes with an error code. The native backend’s WebRTC, over libdatachannel, which reports errors by throwing, needs exceptions: while this is defined, native::data_channel, native::peer_connection and native::backend do not exist, and the rest of trystero is unchanged.

See Also

"The native backend", in the guide.

Appendix A: Differences from Trystero

trystero ports the part of Trystero 0.26.0 that rooms of messages need: the Nostr strategy, the signaling, the password handshake, the action wire and the room’s API. Where it keeps a feature, it keeps Trystero’s wire and its defaults; where it bounds what a peer or a relay can make a room hold, it says so in Bounded signaling, since Trystero JS bounds none of it. These features of Trystero are not ported, each for its reason:

Not ported Why

Audio and video: addStream, removeStream, addTrack, removeTrack, replaceTrack, onPeerStream and onPeerTrack, with the actions @_stream, @_track and @_signal and their renegotiation

The largest part left, on the roadmap (Roadmap): in the browser, the page’s MediaStream objects; natively, libdatachannel’s RTP tracks, whose capture, encoding and decoding would be the application’s. A room ignores a frame of one of these actions.

Request actions, kind: 'request': request, requestMany and onRequest

Not ported yet: an application answers a message with another, to its sender’s id.

onReceive, which accepts or declines an incoming transfer; the progress callbacks onProgress and onReceiveProgress; and cancelling a send with an AbortSignal

Not ported yet: a room accepts every transfer for an action with a receiver within max_receive_bytes, as Trystero does when no onReceive is set, and reports a transfer only when it completes.

onPeerHandshake, a handshake of the application’s own

Not ported yet: the password handshake, which runs on the same internal action, @_hsdata, is.

Passive rooms, passive: true

Not ported yet: a room always announces itself.

The BitTorrent, MQTT, Firebase, Supabase, IPFS and WebSocket relay strategies

Each is a strategy of its own to port; Nostr’s public relays need no account and no server of the application’s own.

getRelaySockets, pauseRelayReconnection, resumeRelayReconnection, manualReconnection and warnOnRelayFailure

Not ported yet: a room reconnects with its backoff, and reports what its relays do through its log.

trickleIce: false, turnConfig and rtcPolyfill

Candidates are always trickled, as Trystero’s Nostr strategy does by default; ice_servers names every STUN and TURN server, in place of Trystero’s defaults; and the backend is what a polyfill would be.

Where a ported feature behaves otherwise

Behaviour Trystero JS Here Why

Scope of sharing and of the self id

the page: one selfId for every app and room, one manager per strategy

an app object: its self id and its connections

no static state; several peers in one process; an id across apps is a program’s choice (app_config::self_id) and never reaches the wire of another app

Passwords across rooms

independently configured per room, including mixed passwords on one shared connection

one immutable app_config::password for every room, separate apps and self ids for different passwords

the initial sealed negotiation authenticates one password domain before any of its rooms reuse the connection; the JS wire remains unchanged

A peer kicked from a room, or that kicked us, over a live connection

bound again by its next signal or presence

bound again at a rebinding time, 11 s doubling to 5 min; the lower id refuses it meanwhile and keeps its frames

a peer sending invalid room proofs would otherwise loop reports and announcements without end; one refusing side cannot alternate with another

Reuse over a live connection

binds on any signal of that peer id

binds only a peer that advertised the room’s token itself, and by reuse only on their first meeting over that connection

a forged announcement must not bind an innocent peer

Announcements after a peer leaves

restarted after every leave

restarted only when no live connection to that peer remains

over a live connection, rebinding goes through the connection

Presence after binding

sent when a room registers

also sent again when a room binds by reuse or at its rebinding time

it makes the other side, Trystero JS included, bind at once

An answer to an earlier challenge

checked against the current challenge only

accepted when it is correct for any of the last 8 challenges sent since the last completed handshake

a late answer of an ended binding is honest and proves the password; failing it reports a false error

Frames after the higher id’s own kick

kept for the next binding

dropped until the higher id sends presence

they belong to the ended binding; flushed into the next one, they would answer or be answered in its place

Joining a room already joined

the same room

error::already_joined

a room is a handle whose destruction leaves

Rooms per app

unbounded; a peer advertising a 65th is dropped by the other side

join refuses the 65th, counting rooms in their farewell

the 65th would cost every connection of the app

A connection whose last room was kicked

idle 123,333 ms

closed at once

a bound: a failed peer holds no connection, as today

Relay sockets and the Nostr key

shared by the rooms of the page, topics batched per socket

per room, as today

not part of this decision; invisible to peers; shared through the app in a later change

Relay and ICE configuration

the first room’s relays; each negotiating room’s RTC configuration

the app’s, for every room

one configuration for what the rooms share

idle_timeout

test-only (_test_only_sharedPeerIdleMs)

a configuration member, Trystero’s default

tests and programs that close sooner

Frames waiting for a bound room’s handler

64, 10 s, then the connection drops

no such state

a binding’s delivery is set when it is made

Renegotiation and media over a shared connection

routed through a control room, streams owned per room; a connection with any media is destroyed when its last room goes

not ported

media is on the roadmap (Roadmap)

Passive rooms

presence only for active rooms

not ported

as today

Crossed duplicate connections

first registered wins at each side; crossed, both lose and meet again

the same

matching Trystero’s rule is what keeps a C++ and a Trystero JS side agreeing

on_peer_join does not replay the joined peers. Trystero calls a newly assigned onPeerJoin with each peer already joined. A room reports a join from its executor, after the call to join has returned, so a handler set right after join sees every join; peers() lists the joined peers for a handler set later.

peers() lists ids. Trystero’s getPeers maps each id to its RTCPeerConnection. A connection is the backend’s, a page’s object in the browser and libdatachannel’s natively, and the room does not hand it out.

A send to a peer that has not joined fails. Trystero warns on the console and resolves; async_send completes with error::no_such_peer, since an operation that fails says so with its error code.

No example of trystero runs under the build, since each needs the public relays or a browser, so none has a twin in Trystero’s language, and the interop lane, not a twin, compares trystero with Trystero (The oracle and the interop lanes).

Appendix B: Roadmap

What trystero does not port yet, and what it does not build itself yet (Differences from Trystero gives each feature left out with its reason):

  1. Media. Audio and video tracks and streams, and Trystero’s @_stream, @_track and @_signal actions with renegotiation. In the browser they are the page’s MediaStreamTrack objects. Natively they would be libdatachannel’s RTP tracks, whose encoding, decoding and capture are the application’s. What that needs was measured: libdatachannel keeps only weak references to tracks, answers with every codec offered unless the answer is pruned, and echoes the offerer’s msid into the answer.

  2. Trystero’s other features. Request actions (kind: 'request'), decisions on incoming transfers and their progress, the application’s own handshake, passive rooms, and the other strategies, among them MQTT and BitTorrent trackers.

  3. libsecp256k1 from webcpp’s own build. libsecp256k1 is still found where b2’s command line or the superproject’s .local/ says. Native OpenSSL and libdatachannel already build from pinned third_party/ sources (Building).

Appendix C: Acknowledgements

trystero ports the wire protocol of Trystero 0.26.0, Copyright (c) 2021 Dan Motzenbecker, so that its rooms are Trystero’s rooms. It was written with Trystero’s source open: its protocol constants, its message layouts, its default relays in their order, with the shuffle that derives an app’s relays from its id, and its default STUN servers come from it. Trystero is distributed under the MIT License, whose text and copyright notice are in LICENSE-TRYSTERO.txt, at the root of the library, exactly as Trystero 0.26.0 ships them. That adds no licence to trystero’s own code, which is under the Boost Software License alone.

The BIP-340 signatures of the Nostr events are libsecp256k1's, by the Bitcoin Core contributors, under the MIT License. The native backend’s WebRTC is libdatachannel's, by Paul-Louis Ageneau, under the Mozilla Public License 2.0.

Appendix D: Changelog

What changed in trystero in each version of webcpp, newest first, one line per change. "Unreleased" is what the next version brings. The release notes of every version are on webcpp’s index page.

Unreleased

webcpp 0.1.0

  • Fixed native multiroom handoffs losing early presence frames, and negotiations stalled by a channel closing before room binding.

  • Changed the native backend to build pinned libdatachannel sources and their dependencies through B2, sharing source-built OpenSSL with Asio and Beast.

  • Changed native builds to require threading=multi; the backend uses workers.

  • Fixed GCC compilation of native WebSocket coroutine callbacks.

  • Changed room construction to app::join, sharing connections between rooms.

  • Breaking change: Removed room_config; construct app and use join with app_config and room_options.

  • Breaking change: Changed passwords to app_config; different passwords require separate apps and self ids.

  • Added a changelog, the page’s last appendix, with each version’s changes.

  • Changed config.hpp to include <webcpp/config.hpp>, webcpp’s configuration, which the build adds.

  • Added WEBCPP_NO_EXCEPTIONS, which disables trystero’s exceptions with every webcpp library’s.