Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cfc72bd
fix(webtty): discover transports and harden asynchronous ownership
uartnet Sep 6, 2026
18faccd
fix(stun): qualify attribute type lookup for MSVC
uartnet Sep 7, 2026
15b7b1e
fix(stun): reject lengths that exceed wire fields
uartnet Sep 7, 2026
355ea8e
fix(build): make examples and fixtures portable to Windows
uartnet Sep 7, 2026
a408f80
test(io): retain asynchronous stream payloads through completion
uartnet Sep 7, 2026
92910ed
fix(examples): retain HTTP coroutine address arguments
uartnet Sep 7, 2026
a79fadd
Use Boost.Regex for Windows CLI parsing and test executable startup
uartnet Sep 8, 2026
13aefae
Bind Windows ConPTY children to console standard handles
uartnet Sep 8, 2026
f1034c4
Pin strict Windows package validation to the VS 2022 baseline
uartnet Sep 8, 2026
c7f9bbd
test(webtty): drain stdin after the early-exit close frame
uartnet Sep 8, 2026
8fa5303
build(msvc): scope the Asio inline buffer warning to its external header
uartnet Sep 8, 2026
bf72b3e
fix(cpp): stage HTTP example plugins and clarify FIFO completion owne…
uartnet Sep 8, 2026
7033388
build(cpp): qualify consistent native sanitizer graphs and MSVC buffers
uartnet Sep 8, 2026
909e5a8
fix(cpp): qualify optimized Boost 1.83 consumers on Windows
uartnet Sep 8, 2026
09c0d38
fix(webtty): recover missed Darwin FIFO EOF notifications
uartnet Sep 9, 2026
fa29e14
fix(windows): complete synchronous I/O cancellation before closing ha…
uartnet Sep 9, 2026
0ec2141
test: require actual Windows pipe ASan execution in package CI
uartnet Sep 9, 2026
fda51ae
test: execute native SDK qualification even with populated Conan cache
uartnet Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .github/scripts/test-memory-sanitizers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Run native memory checks with a consistently instrumented dependency graph."""

import argparse
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess


def run(command, environment, timeout, stdout=None):
timeout_tool = shutil.which(
"gtimeout" if platform.system() == "Darwin" else "timeout"
)
if timeout_tool is None:
raise RuntimeError("Memory checks require GNU coreutils timeout")
subprocess.run(
[timeout_tool, "--signal=TERM", "--kill-after=10s", str(timeout), *command],
env=environment,
stdout=stdout,
check=True,
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--jobs", type=int, choices=range(1, 17), default=2)
parser.add_argument("--shared", action="store_true")
parser.add_argument("--dynamic-plugins", action="store_true")
args = parser.parse_args()
if platform.system() not in ("Linux", "Darwin"):
parser.error("Memory checks require Linux or macOS")
source = Path(__file__).resolve().parents[2]
output = args.output.resolve()
output.mkdir(parents=True, exist_ok=True)
environment = os.environ.copy()
command = [
"conan",
"install",
str(source),
"--build=missing",
"--output-folder=" + str(output / "dependencies"),
"-s",
"build_type=Debug",
"-s",
"compiler.cppstd=20",
"-o",
"enable_testing=True",
"-o",
"enable_strict_warnings=True",
"-o",
"warnings_as_errors=True",
"-o",
"shared=" + str(args.shared),
"-o",
"static_plugins=" + str(not args.dynamic_plugins),
"-c:h",
"tools.build:jobs=" + str(args.jobs),
"--format=json",
]
configurations = {
"tools.build:cflags": ["-fsanitize=address", "-fno-omit-frame-pointer"],
"tools.build:cxxflags": ["-fsanitize=address", "-fno-omit-frame-pointer"],
"tools.build:sharedlinkflags": ["-fsanitize=address"],
"tools.build:exelinkflags": ["-fsanitize=address"],
}
configurations["tools.info.package_id:confs"] = list(configurations)
for name, value in configurations.items():
command.extend(["-c:h", name + "=" + json.dumps(value)])
with (output / "dependencies.json").open("w") as graph:
# Third-party build tools keep process caches; LSan is mandatory at runtime.
run(
command,
environment | {"ASAN_OPTIONS": "detect_leaks=0:halt_on_error=1"},
5400,
graph,
)
toolchains = list((output / "dependencies").rglob("conan_toolchain.cmake"))
if len(toolchains) != 1:
raise RuntimeError("Expected exactly one instrumented Conan toolchain")
build = output / "build"
flags = "-fsanitize=address,undefined"
run(
[
"cmake",
"-S",
str(source),
"-B",
str(build),
"-G",
"Ninja",
"-DCMAKE_TOOLCHAIN_FILE=" + str(toolchains[0]),
"-DCMAKE_BUILD_TYPE=Debug",
"-DCMAKE_C_FLAGS=" + flags + " -fno-omit-frame-pointer",
"-DCMAKE_CXX_FLAGS=" + flags + " -fno-omit-frame-pointer",
"-DCMAKE_EXE_LINKER_FLAGS=" + flags,
"-DCMAKE_SHARED_LINKER_FLAGS=" + flags,
"-DCMAKE_MODULE_LINKER_FLAGS=" + flags,
"-DRSTREAM_TEST_TIMEOUT_SCALE=2",
"-DRSTREAM_TEST_TIMEOUT_SECONDS=300",
],
environment,
180,
)
run(
["cmake", "--build", str(build), "--parallel", str(args.jobs)],
environment,
1800,
)
detect_leaks = "1" if platform.system() == "Linux" else "0"
run(
[
"ctest",
"--test-dir",
str(build),
"--output-on-failure",
"--output-junit",
str(output / "results.junit.xml"),
],
environment
| {
"ASAN_OPTIONS": "detect_leaks=" + detect_leaks + ":halt_on_error=1",
"UBSAN_OPTIONS": "halt_on_error=1:print_stacktrace=1",
},
1800,
)


if __name__ == "__main__":
main()
12 changes: 8 additions & 4 deletions .github/workflows/conan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
conan create --build=missing --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default \
conan create --build=missing --build="rstream/*" --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default \
-s:b compiler.cppstd=20 \
-s:h compiler.cppstd=20 \
-o "rstream/*:shared=${{ matrix.shared }}" \
Expand Down Expand Up @@ -226,7 +226,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
conan create --build=missing --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default \
conan create --build=missing --build="rstream/*" --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default \
-s:b compiler.cppstd=20 \
-s:h compiler.cppstd=20 \
-o "rstream/*:shared=${{ matrix.shared }}" \
Expand Down Expand Up @@ -258,7 +258,7 @@ jobs:
windows:
name: Windows ${{ matrix.linkage }} libraries
if: ${{ github.actor == vars.CI_ALLOWED_ACTOR }}
runs-on: windows-latest
runs-on: windows-2022
env:
CCACHE_BASEDIR: ${{ github.workspace }}
CCACHE_COMPILERCHECK: content
Expand Down Expand Up @@ -317,18 +317,22 @@ jobs:
ccache --set-config=max_size=1G
ccache --set-config=compression=true
ccache --zero-stats
- name: Check required Windows test configuration
shell: pwsh
run: python test/test_conan_windows_asan.py
- name: Build and test Conan package
shell: pwsh
run: |
foreach ($staticPlugins in @("True", "False")) {
conan create --build=missing --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default `
conan create --build=missing --build="rstream/*" --user conan --channel "${{ inputs.channel }}" -pr:b default -pr:h default `
-s:b compiler.cppstd=20 `
-s:h compiler.cppstd=20 `
-o "rstream/*:shared=${{ matrix.shared }}" `
-o "rstream/*:static_plugins=$staticPlugins" `
-o "rstream/*:enable_testing=True" `
-o "rstream/*:enable_strict_warnings=True" `
-o "rstream/*:warnings_as_errors=True" `
-c "rstream/*:tools.cmake.cmaketoolchain:extra_variables={'RSTREAM_TEST_WINDOWS_PIPE_ASAN': {'value': True, 'cache': True, 'type': 'BOOL'}}" `
.
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,22 @@ jobs:
runs-on: ubuntu-latest
environment: stable-release
steps:
- name: Checkout trusted WebTTY certification policy
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: rstreamlabs/rstream-engine
ref: main
token: ${{ secrets.WEBTTY_REPOSITORIES_TOKEN }}
path: .webtty-policy
persist-credentials: false
- uses: ./.webtty-policy/.github/actions/verify-webtty-release
with:
component: cpp
candidate-sha: ${{ github.sha }}
assembly: ${{ vars.WEBTTY_RELEASE_ASSEMBLY }}
run-id: ${{ vars.WEBTTY_CERTIFICATION_RUN_ID }}
allowed-actor: ${{ vars.CI_ALLOWED_ACTOR }}
token: ${{ secrets.WEBTTY_REPOSITORIES_TOKEN }}
- run: printf 'Approved C++ package candidates for %s\n' "${GITHUB_SHA}"

publish-linux-windows:
Expand Down
67 changes: 65 additions & 2 deletions .github/workflows/reliability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jobs:
fail-fast: false
matrix:
include:
- name: Address and undefined behavior sanitizers
preset: asan
- name: Thread sanitizer
preset: tsan
steps:
Expand All @@ -37,6 +35,71 @@ jobs:
run: cmake --build --preset "${{ matrix.preset }}"
- name: Test
run: ctest --preset "${{ matrix.preset }}"
memory-checks:
name: ${{ matrix.name }}
if: ${{ github.actor == vars.CI_ALLOWED_ACTOR }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- name: Linux address, undefined behavior and leak checks
runner: ubuntu-24.04
cache: conan-linux-asan-v1
test_step: Check runtime with instrumented dependencies
options: ""
- name: Address and undefined behavior sanitizers
runner: macos-latest
cache: conan-macos-asan-v1
test_step: Test
options: --shared --dynamic-plugins
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install Linux build prerequisites
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build pkg-config
- name: Install macOS build prerequisites
if: runner.os == 'macOS'
run: brew install cmake coreutils ninja pkg-config
- name: Configure Conan
run: |
python3 -m pip install conan==2.31.2
conan profile detect --force
conan config install conan/config
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: memory-cache
with:
path: ~/.conan2/p
key: ${{ matrix.cache }}-${{ runner.arch }}-${{ hashFiles('conanfile.py', 'conan/config/**', '.github/scripts/test-memory-sanitizers.py') }}
restore-keys: ${{ matrix.cache }}-${{ runner.arch }}-
- name: ${{ matrix.test_step }}
run: python3 .github/scripts/test-memory-sanitizers.py --output out/memory-sanitizers --jobs 3 ${{ matrix.options }}
- name: Preserve memory check evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: memory-sanitizers-${{ runner.os }}-${{ github.sha }}
path: |
out/memory-sanitizers/dependencies.json
out/memory-sanitizers/results.junit.xml
if-no-files-found: error
retention-days: 90
- name: Trim dependency build caches
if: ${{ always() && !cancelled() }}
run: conan cache clean "*" --source --build --download --temp
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: ${{ always() && !cancelled() && steps.memory-cache.outputs.cache-hit != 'true' }}
with:
path: ~/.conan2/p
key: ${{ steps.memory-cache.outputs.cache-primary-key }}
static-analysis:
name: Static analysis
if: ${{ github.actor == vars.CI_ALLOWED_ACTOR }}
Expand Down
19 changes: 19 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,22 @@ commits = [
paths = ['''.*demo/stun/test/test_stun_parsing\.cpp''']
regexTarget = "match"
regexes = ['''(?:password|integrity_key)\s*=\s*"[^"]+"''']

[[rules]]
id = "generic-api-key"

[[rules.allowlists]]
description = "Exact public keys, signatures and identifiers from the isolated WebTTY approval fixture; no private keys or authentication tokens."
condition = "AND"
paths = ['''(^|/)test/webtty/fixtures/workspace-approved-client\.json$''']
regexTarget = "line"
regexes = [
'''^\s*"workspaceTrustKeysetId":\ "cmtorkj0w0001f4s7ibymzn7r",\s*$''',
'''^\s*"workspaceTrustPublicSigningKey":\ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqnyUNNjrBJlT3IeiR5_dFnTqaURvuKr2l4uPxHv2NzV3hwriJEbNFRMPlwfRIvAls3J4hYTEdLGLHldtCwddKg"\s*$''',
'''^\s*"device_public_encryption_key":\ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtLfGX25FV2Rxvc52BlCsIJ_6eDzGmMbjtH6PRw2gFf22lJVTQyresVtx6SPBOa8IrUPrwKfNUSEIuEznez2H9g",\s*$''',
'''^\s*"device_public_signing_key":\ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfyoJ8mkp7UIh88xxmuULgqDD11diVcieAgk0kBUoVHq6NR1p0eq5YUuWg7G4A_7OpEDa1qA6_sj9uNBeN43CnQ",\s*$''',
'''^\s*"trust_keyset_id":\ "cmtorkj0w0001f4s7ibymzn7r",\s*$''',
'''^\s*"trust_keyset_signature":\ "XdspZHqxzF0E7cVIHvrS7XFBr3GGpsQ95AGrRYSyulF\-LfKnqy5cWy\-qo1I09vqvXqdmzf7nReqlyciMdq\-2OQ",\s*$''',
'''^\s*"keyset_id":\ "cmtorkj0w0001f4s7ibymzn7r",\s*$''',
'''^\s*"webtty_key_algorithm":\ "webtty\-x25519\-hpke\-v1",\s*$''',
]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Conan Center dependency policy, constrained-system requirements, and complete
validation procedure are defined in
[docs/001-sdk-engineering.md](docs/001-sdk-engineering.md).

Windows CLI builds require docopt.cpp compiled with Boost.Regex. The Conan recipe selects and validates this variant and shares the SDK’s supported Boost version with docopt. MSVC’s `std::regex` can overflow the stack while parsing the WebTTY help text. If supplying dependencies directly to CMake, build docopt with `USE_BOOST_REGEX=ON`; the CLI startup tests verify both `--help` and `--version` for the client and server. No process-wide stack-size increase is required.

## Build from source

The recommended source build uses Conan to provision third-party dependencies and then builds the package:
Expand Down
6 changes: 3 additions & 3 deletions bin/inspect/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ int main(int argc, char** argv)
auto args = docopt::docopt(USAGE, {argv + 1, argv + argc}, true, version);
if (args["version"].asBool()) {
if (args["--verbose"].asBool()) {
auto version = rstream::core::get_project_info();
auto project_info = rstream::core::get_project_info();
if (args["--json"].asBool()) {
nlohmann::json json;
json << version;
json << project_info;
std::cout << json.dump(2) << std::endl;
}
else {
std::cout << version << std::endl;
std::cout << project_info << std::endl;
}
}
else {
Expand Down
14 changes: 13 additions & 1 deletion bin/nperf/lib/cpp/rstream/nperf/client.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// See LICENSE file in the project root for license information.

#ifdef _MSC_VER
// MSVC can flag Asio's buffer conversion as unreachable after inlining.
#pragma warning(push)
#pragma warning(disable : 4702)
#include <boost/asio/buffer.hpp>
#pragma warning(pop)
#endif

#include "client.hpp"

#include <algorithm>
Expand Down Expand Up @@ -1194,7 +1202,11 @@ void client::impl::base::session::do_handshake_websocket(const io::address& addr
// set the control callback. This will be called
// on every incoming ping, pong, and close frame
{
auto completion_handler = std::bind(&session::on_control_callback, shared_from_this(), std::placeholders::_1, std::placeholders::_2);
auto completion_handler = [weak = weak_from_this()](boost::beast::websocket::frame_type kind, const boost::beast::string_view& payload) {
if (auto ptr = weak.lock()) {
ptr->on_control_callback(kind, payload);
}
};
m_websocket->control_callback(rstream::core::wrap_function<void(boost::beast::websocket::frame_type, const boost::beast::string_view&)>(m_strand, completion_handler));
}
// we're sending binary data
Expand Down
14 changes: 13 additions & 1 deletion bin/nperf/lib/cpp/rstream/nperf/server.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// See LICENSE file in the project root for license information.

#ifdef _MSC_VER
// MSVC can flag Asio's buffer conversion as unreachable after inlining.
#pragma warning(push)
#pragma warning(disable : 4702)
#include <boost/asio/buffer.hpp>
#pragma warning(pop)
#endif

#include "server.hpp"

#include <chrono>
Expand Down Expand Up @@ -875,7 +883,11 @@ void server::impl::session::do_accept_websocket()
// set the control callback. This will be called
// on every incoming ping, pong, and close frame
{
auto completion_handler = std::bind(&session::on_control_callback, shared_from_this(), std::placeholders::_1, std::placeholders::_2);
auto completion_handler = [weak = weak_from_this()](boost::beast::websocket::frame_type kind, const boost::beast::string_view& payload) {
if (auto ptr = weak.lock()) {
ptr->on_control_callback(kind, payload);
}
};
m_websocket->control_callback(boost::asio::bind_executor(m_strand, completion_handler));
}
// we're sending binary data
Expand Down
Loading