C++ API
Header-only C++ wrapper for tacet, providing RAII semantics, modern C++ idioms, and a fluent builder API.
Quick Reference
Section titled “Quick Reference”#include <tacet.hpp>#include <iostream>
using namespace tacet;using namespace std::chrono_literals;
int main() { auto result = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(30s) .maxSamples(100000) .fromEnv() // Allow CI to override via TO_* env vars .test([](std::span<uint64_t> baseline, std::span<uint64_t> sample) { for (size_t i = 0; i < baseline.size(); i++) { baseline[i] = measure(generate_baseline()); sample[i] = measure(generate_sample()); } });
std::cout << result << std::endl; // Pretty-printed output return result.outcome == ToOutcome::Fail ? 1 : 0;}Installation
Section titled “Installation”The C++ wrapper is a header-only library that wraps the C bindings.
git clone https://github.com/agucova/tacetcd tacetcargo build --release -p tacet-c
# Headers at:# crates/tacet-c/include/tacet.h# bindings/cpp/tacet.hpp# Library at: target/release/libtacet_c.{a,so,dylib}Requires C++20 for std::span and std::chrono_literals.
Oracle Builder Class
Section titled “Oracle Builder Class”The Oracle class provides a fluent builder interface for configuring and running tests.
Factory Method
Section titled “Factory Method”// Create Oracle for your attacker modelOracle oracle = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork); // LAN, HTTP/2Oracle oracle = Oracle::forAttacker(ToAttackerModel::SharedHardware); // SGX, containersOracle oracle = Oracle::forAttacker(ToAttackerModel::RemoteNetwork); // Public internetOracle oracle = Oracle::forAttacker(ToAttackerModel::Research); // Detect any differenceBuilder Methods
Section titled “Builder Methods”All builder methods return a new Oracle instance (immutable pattern):
using namespace std::chrono_literals;
auto oracle = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(60s) // std::chrono duration .maxSamples(50000) // Max samples per class .passThreshold(0.01) // P(leak) below this -> Pass .failThreshold(0.99) // P(leak) above this -> Fail .seed(12345) // Reproducibility .thresholdNs(500.0) // Custom threshold (overrides attacker model) .timerFrequencyHz(24000000) // Timer frequency for tick conversion .fromEnv(); // Merge TO_* environment variablesTerminal Methods
Section titled “Terminal Methods”// Run test with callbackToResult result = oracle.test([](std::span<uint64_t> baseline, std::span<uint64_t> sample) { for (size_t i = 0; i < baseline.size(); i++) { baseline[i] = measure_baseline(); sample[i] = measure_sample(); }});
// Analyze pre-collected datastd::vector<uint64_t> baseline_data, sample_data;ToResult result = oracle.analyze(baseline_data, sample_data);Accessors
Section titled “Accessors”const ToConfig& config = oracle.config(); // Underlying C configdouble threshold = oracle.thresholdNs(); // Effective thresholdConfiguration Functions
Section titled “Configuration Functions”For the low-level API, create configurations using these functions:
// Specific attacker modelsToConfig cfg = config_adjacent_network(); // 100ns thresholdToConfig cfg = config_shared_hardware(); // ~0.6ns thresholdToConfig cfg = config_remote_network(); // 50μs thresholdToConfig cfg = config_default(ToAttackerModel::Research);
// Environment variable overrideToConfig cfg = config_default(ToAttackerModel::AdjacentNetwork);cfg.time_budget_secs = 30.0;cfg.max_samples = 100000;cfg = config_from_env(cfg);Environment Variables
Section titled “Environment Variables”Allow CI systems to override configuration:
auto result = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(30s) .maxSamples(100000) .fromEnv() // Merges TO_* env vars .test(collector);Supported variables:
TO_TIME_BUDGET_SECS- Time budget in secondsTO_MAX_SAMPLES- Maximum samples per classTO_PASS_THRESHOLD- Pass threshold (e.g., 0.05)TO_FAIL_THRESHOLD- Fail threshold (e.g., 0.95)TO_SEED- Random seedTO_THRESHOLD_NS- Custom threshold in nanoseconds
Stream Operators
Section titled “Stream Operators”All enums and result types have operator<< overloads:
#include <iostream>
ToResult result = /* ... */;
// Print individual fieldsstd::cout << result.outcome << std::endl; // "Pass", "Fail", etc.std::cout << result.quality << std::endl; // "Excellent", "Good", etc.std::cout << result.exploitability << std::endl; // "StandardRemote", etc.
// Print entire result (pretty-formatted)std::cout << result << std::endl;// Output:// Outcome: Pass// Leak probability: 3.20%// Effect: 2.50 ns (shift) + 1.30 ns (tail) [1.00, 5.00] ns 95% CI, pattern: Mixed// Quality: Good// Samples: 15000 per class// Elapsed: 12.34 seconds// Thresholds: user=100.00 ns, effective=100.00 ns, floor=5.00 nsLow-Level API
Section titled “Low-Level API”For more control, use the calibrate/step API with RAII wrappers:
RAII Classes
Section titled “RAII Classes”// State manages adaptive sampling stateState state; // Automatically freed on destructionstate.total_samples(); // Samples collectedstate.leak_probability(); // Current estimate (0.5 initially)
// Calibration manages calibration dataCalibration cal = calibrate(baseline, sample, config);Adaptive Loop
Section titled “Adaptive Loop”auto config = config_default(ToAttackerModel::AdjacentNetwork);config.time_budget_secs = 30.0;config.max_samples = 100000;
// Calibration phasestd::vector<uint64_t> cal_baseline(5000), cal_sample(5000);collect_samples(cal_baseline, cal_sample);Calibration calibration = calibrate(cal_baseline, cal_sample, config);
// Create stateState state;
// Adaptive loopauto start = std::chrono::steady_clock::now();while (true) { std::vector<uint64_t> batch_b(1000), batch_s(1000); collect_samples(batch_b, batch_s);
auto elapsed = std::chrono::steady_clock::now() - start; double elapsed_secs = std::chrono::duration<double>(elapsed).count();
ToStepResult step_result = step(calibration, state, batch_b, batch_s, config, elapsed_secs);
if (step_result.has_decision) { // step_result.result contains the final ToResult std::cout << step_result.result << std::endl; break; }}// State and Calibration automatically freedOne-Shot Analysis
Section titled “One-Shot Analysis”std::vector<uint64_t> baseline(10000), sample(10000);// ... collect samples ...
ToResult result = analyze(baseline, sample, config);Result Types
Section titled “Result Types”ToOutcome
Section titled “ToOutcome”enum class ToOutcome { Pass, // No leak detected Fail, // Leak confirmed Inconclusive, // Cannot decide Unmeasurable // Operation too fast};ToResult
Section titled “ToResult”The result struct contains all analysis information:
struct ToResult { ToOutcome outcome; double leak_probability; // P(effect > threshold | data) ToEffect effect; // Effect size estimate ToMeasurementQuality quality; // Measurement precision uint64_t samples_used; // Samples per class double elapsed_secs; // Test duration ToExploitability exploitability; ToInconclusiveReason inconclusive_reason; double mde_shift_ns; // Minimum detectable shift double mde_tail_ns; // Minimum detectable tail double theta_user_ns; // Requested threshold double theta_eff_ns; // Effective threshold double theta_floor_ns; // Measurement floor ToDiagnostics diagnostics; // Detailed diagnostics};Other Enums
Section titled “Other Enums”enum class ToMeasurementQuality { Excellent, // MDE < 5ns Good, // MDE 5-20ns Poor, // MDE 20-100ns TooNoisy // MDE > 100ns};
enum class ToExploitability { SharedHardwareOnly, // < 10ns Http2Multiplexing, // 10-100ns StandardRemote, // 100ns - 10μs ObviousLeak // > 10μs};
enum class ToEffectPattern { UniformShift, // Constant timing difference TailEffect, // Difference in upper quantiles Mixed, // Both components Indeterminate // Cannot determine};Exception Safety
Section titled “Exception Safety”The C++ wrapper throws exceptions for errors:
try { auto result = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(30s) .test(collector);} catch (const tacet::CalibrationError& e) { std::cerr << "Calibration failed: " << e.what() << std::endl;} catch (const tacet::Error& e) { std::cerr << "Error: " << e.what() << std::endl;}Exception types:
Error- Base classNullPointerErrorInvalidConfigErrorCalibrationErrorAnalysisErrorNotEnoughSamplesError
Complete Example
Section titled “Complete Example”#include <tacet.hpp>#include <iostream>#include <random>#include <cstring>
using namespace tacet;using namespace std::chrono_literals;
// Platform-specific timer#if defined(__x86_64__)inline uint64_t read_timer() { uint32_t lo, hi; __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); return (static_cast<uint64_t>(hi) << 32) | lo;}#elif defined(__aarch64__)inline uint64_t read_timer() { uint64_t val; __asm__ volatile("mrs %0, cntvct_el0" : "=r"(val)); return val;}#endif
#define DO_NOT_OPTIMIZE(x) __asm__ volatile("" : : "r,m"(x) : "memory")
// Leaky comparison (for testing)bool leaky_compare(const uint8_t* a, const uint8_t* b, size_t n) { for (size_t i = 0; i < n; i++) { if (a[i] != b[i]) return false; // Early exit! } return true;}
int main() { std::array<uint8_t, 32> secret; std::random_device rd; std::generate(secret.begin(), secret.end(), std::ref(rd));
std::mt19937_64 rng(42); std::uniform_int_distribution<uint8_t> dist(0, 255);
auto result = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(30s) .maxSamples(100000) .fromEnv() .test([&](std::span<uint64_t> baseline, std::span<uint64_t> sample) { std::array<uint8_t, 32> input;
for (size_t i = 0; i < baseline.size(); i++) { // Baseline: all zeros std::fill(input.begin(), input.end(), 0); uint64_t start = read_timer(); bool r = leaky_compare(input.data(), secret.data(), 32); uint64_t end = read_timer(); DO_NOT_OPTIMIZE(r); baseline[i] = end - start;
// Sample: random std::generate(input.begin(), input.end(), [&]() { return dist(rng); }); start = read_timer(); r = leaky_compare(input.data(), secret.data(), 32); end = read_timer(); DO_NOT_OPTIMIZE(r); sample[i] = end - start; } });
std::cout << result << std::endl; return result.outcome == ToOutcome::Fail ? 1 : 0;}Compilation
Section titled “Compilation”clang++ -std=c++20 -stdlib=libc++ -O3 -o timing_test timing_test.cpp \ -I/path/to/tacet/crates/tacet-c/include \ -I/path/to/tacet/bindings/cpp \ -L/path/to/tacet/target/release \ -ltacet_cComparison: Builder API vs Low-Level API
Section titled “Comparison: Builder API vs Low-Level API”Builder API (Recommended)
Section titled “Builder API (Recommended)”auto result = Oracle::forAttacker(ToAttackerModel::AdjacentNetwork) .timeBudget(60s) .maxSamples(100000) .fromEnv() .test([](auto baseline, auto sample) { // collect samples });
std::cout << result << std::endl;Low-Level API
Section titled “Low-Level API”auto config = config_default(ToAttackerModel::AdjacentNetwork);config.time_budget_secs = 60.0;config.max_samples = 100000;config = config_from_env(config);
auto calibration = calibrate(cal_baseline, cal_sample, config);State state;// ... manual loop with step() ...
ToResult result = /* ... */;std::cout << "Outcome: " << outcome_to_string(result.outcome) << std::endl;The builder API is more ergonomic, handles the adaptive loop internally, and provides operator<< for easy output.