From b777220ef67fffd1607eda54a3bfbcf22bae6d4b Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Thu, 22 Oct 2026 23:57:24 +0800 Subject: [PATCH 1/6] feat: complete the Permit installation loop and local trial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every install now holds a stable installation id (ins_ + 32 hex, persisted beside the gate state) that rides along with credential verification, so credentials bound to installation_id finally verify on the matching install and fail closed everywhere else. The id is typed into the GateInfo RPC and shown copyable on the License page. The 14-day trial moves to permit local-trial state (fixed window + last_seen high-water mark, clock-rollback detection); legacy trial-started date state migrates on first read — an active window keeps its remaining time, an expired one stays expired, and Trial cannot restart a consumed window. --- app/backend.rkt | 2 + app/gate.rkt | 167 ++++++++++++++++++++++++++----------- linux/GeneratedBackend.hpp | 48 ++++++++++- linux/src/main.cpp | 42 ++++++---- support/entitlement.rkt | 15 +++- tests/gate-test.rkt | 128 +++++++++++++++++++++++++++- 6 files changed, 331 insertions(+), 71 deletions(-) diff --git a/app/backend.rkt b/app/backend.rkt index e3d40d2..1f221be 100644 --- a/app/backend.rkt +++ b/app/backend.rkt @@ -142,6 +142,7 @@ ([tier : String] [pro : Bool] [trial_days : Int64] + [installation_id : String] [detail : Any])) (define-record PresetEntry @@ -306,6 +307,7 @@ (GateInfo (symbol->string* (hash-ref info 'tier "free")) (and (hash-ref info 'pro #f) #t) (optional-int (hash-ref info 'trial_days 0)) + (symbol->string* (hash-ref info 'installation_id "")) (any->wire (hash-ref info 'detail (void))))) (define (current-engine-status) (sup-status->record (sup-status app-supervisor))) diff --git a/app/gate.rkt b/app/gate.rkt index c43a5b7..d07629d 100644 --- a/app/gate.rkt +++ b/app/gate.rkt @@ -6,17 +6,20 @@ ;; simulator, config editor/preview, presets (watch free) ;; pro — engine control (GM/slave), reference-source management, ;; unlimited packet store, pcap/report exports (drive paid) -;; trial — 14 days of Pro from the moment the user starts it +;; trial — 14 days of Pro from the moment the user starts it, tracked +;; as permit local-trial state (last_seen high-water mark) ;; ;; The tier never blocks the six pages, only the actions listed above. ;; A customer activates an offline Permit v1 entitlement credential ;; (.permit.json), verified against the Ed25519 trust store shipped with -;; the application. +;; the application. Each install also holds a stable installation id +;; (ins_ + 32 hex) so the vendor can issue credentials bound to it. (require json racket/date racket/file racket/string + permit "../support/entitlement.rkt" "../support/updates.rkt" racket/runtime-path) @@ -32,6 +35,7 @@ gate-info gate-check gate-state-path + gate-installation-id credential-path) (define-runtime-path shipped-trust-store "keys/trust-store.json") @@ -69,48 +73,77 @@ (define d (build-path (gate-home-dir) ".gptp-studio")) (unless (directory-exists? d) (make-directory* d))) -;; persisted: trial-started ISO date, so the trial survives restarts -(define (read-gate-state) - (with-handlers ([exn:fail? (lambda (_) (hasheq))]) - (define v (call-with-input-file (gate-state-path) read-json)) - (if (hash? v) v (hasheq)))) +;; Stable per-install identity (ins_ + 32 hex, permit's format): created +;; on first use, persisted beside the gate state, and surfaced on the +;; License page so a customer can hand it to the vendor for an +;; installation-bound credential. +(define (gate-installation-id) + (define path (build-path (gate-home-dir) ".gptp-studio" "installation-id")) + (define existing + (and (file-exists? path) + (with-handlers ([exn:fail? (lambda (_) #f)]) + (string-trim (file->string path))))) + (if (and (string? existing) (regexp-match? #px"^ins_[0-9a-f]{32}$" existing)) + existing + (let ([value (generate-installation-id)]) + (ensure-gate-dir!) + (call-with-output-file path + (lambda (out) (display value out) (newline out)) + #:exists 'truncate/replace) + value))) -(define (write-gate-state! h) - (ensure-gate-dir!) - (call-with-output-file (gate-state-path) - (lambda (out) (write-json h out)) - #:exists 'replace)) +;; The trial is permit local-trial state in the historical gate-state.json +;; path: a fixed started/expires window plus a last_seen high-water mark +;; that survives restarts and rejects clock rollback (nuisance resistance, +;; not an anti-tamper boundary). -(define (iso-today) - (define d (current-date)) - (format "~a-~a-~a" - (date-year d) - (~r2 (date-month d)) - (~r2 (date-day d)))) +(define trial-duration-seconds (* trial-days 24 60 60)) -(define (~r2 n) - (define s (number->string n)) - (if (< (string-length s) 2) (string-append "0" s) s)) +;; local-trial, a legacy {"trial-started": date} hash, or #f for absent, +;; corrupt, or foreign state. jsexpr->local-trial raises on any wrong +;; shape, so every malformed byte reads as "no state", never a crash. +(define (read-trial-state path) + (cond + [(not (file-exists? path)) #f] + [else + (with-handlers ([exn:fail? (lambda (_) #f)]) + (define v (call-with-input-file path read-json)) + (cond + [(not (hash? v)) #f] + [(hash-ref v 'trial-started #f) v] + [else (jsexpr->local-trial v)]))])) + +;; Legacy (pre-0.3.0) state anchored the 14-day window at the recorded +;; date's noon. Migration rewrites the file into local-trial form once: +;; an active legacy window keeps its remaining time, an expired one stays +;; expired so Trial cannot restart, and a malformed date reads as absent. +(define (current-trial!) + (define path (gate-state-path)) + (define state (read-trial-state path)) + (cond + [(local-trial? state) state] + [(hash? state) + (define started (hash-ref state 'trial-started #f)) + (define anchor + (and (string? started) + (regexp-match? #px"^[0-9]{4}-[0-9]{2}-[0-9]{2}$" started) + (string-append started "T12:00:00Z"))) + (and anchor + (let ([trial (make-local-trial #:duration-seconds trial-duration-seconds + #:at anchor)]) + (write-local-trial trial path) + trial))] + [else #f])) -;; days until the ISO yyyy-mm-dd date's end; negative once it has passed -(define (days-until-expiry iso) - (define m (regexp-match #rx"^([0-9]{4})-([0-9]{2})-([0-9]{2})$" iso)) - (unless m - (raise-arguments-error 'days-until-expiry "not an ISO date" "date" iso)) - (define noon - (find-seconds 0 0 12 - (string->number (list-ref m 3)) - (string->number (list-ref m 2)) - (string->number (list-ref m 1)) - #f)) - (inexact->exact (floor (- (/ noon 86400.0) (/ (current-seconds) 86400.0))))) - -(define (days-since iso) - (days-until-expiry iso)) ; negative == already past - -(define (trial-days-left) - (define started (hash-ref (read-gate-state) 'trial-started #f)) - (and started (+ trial-days (days-since started)))) ; days until expiry; negative = over +;; Evaluate the trial and persist the last_seen high-water mark after the +;; observation; #f when no trial state exists. +(define (trial-evaluation) + (define trial (current-trial!)) + (and trial + (let ([evaluation (evaluate-local-trial trial)]) + (write-local-trial (local-trial-evaluation-state evaluation) + (gate-state-path)) + evaluation))) ;; ---- public -------------------------------------------------------------------- @@ -121,10 +154,13 @@ (define-values (tier info) (cond [(file-exists? lic) - (define-values (ok? lic-info) (entitlement-validate lic (trust-store-path))) + (define-values (ok? lic-info) + (entitlement-validate lic (trust-store-path) + #:installation-id (gate-installation-id))) (if ok? (values 'pro lic-info) (values 'free #f))] [else - (values (if (let ([left (trial-days-left)]) (and left (> left 0))) + (define evaluation (trial-evaluation)) + (values (if (and evaluation (local-trial-evaluation-active? evaluation)) 'trial 'free) #f)])) @@ -145,6 +181,17 @@ ;; Whether staging an official update is part of what this install holds. ;; Free and trial always update; a perpetual Pro install updates through ;; its entitlement's updates_through day. +(define (iso-today) + (define d (current-date)) + (format "~a-~a-~a" + (date-year d) + (~r2 (date-month d)) + (~r2 (date-day d)))) + +(define (~r2 n) + (define s (number->string n)) + (if (< (string-length s) 2) (string-append "0" s) s)) + (define (gate-update-window-open? [today (iso-today)]) (updates-window-open? #:pro? (eq? (unbox gate-cache) 'pro) #:updates-through (gate-updates-through) @@ -161,9 +208,14 @@ [else "此功能需要 Pro 许可证。"]))) (define (gate-trial-start!) - (define st (read-gate-state)) - (unless (hash-ref st 'trial-started #f) - (write-gate-state! (hash-set st 'trial-started (iso-today)))) + ;; Only absent, corrupt, or malformed state starts a fresh window: a + ;; valid state — active or already expired — is preserved so Trial + ;; cannot restart itself. + (unless (current-trial!) + (ensure-gate-dir!) + (write-local-trial + (make-local-trial #:duration-seconds trial-duration-seconds) + (gate-state-path))) (gate-init!)) ;; Activate from a credential file path (verified, then copied into place). @@ -171,7 +223,9 @@ (cond [(not (file-exists? path)) (values #f "许可证文件不存在")] [else - (define-values (ok? info) (entitlement-validate path (trust-store-path))) + (define-values (ok? info) + (entitlement-validate path (trust-store-path) + #:installation-id (gate-installation-id))) (if ok? (begin (ensure-gate-dir!) @@ -184,6 +238,7 @@ [(expired) "许可证已过期"] [(not-yet-valid) "许可证尚未生效"] [(edition) "许可证不包含 Pro 版授权"] + [(installation) "许可证绑定其他安装实例"] [(type) "文件不是许可证凭据"] [(key-revoked) "签发密钥已被吊销"] [(key-window) "签发密钥不在有效签发期内"] @@ -196,16 +251,30 @@ (define (gate-info) (define tier (gate-tier)) + (define installation (gate-installation-id)) (define lic (credential-path)) (define detail (cond [(eq? tier 'pro) (define-values (_ok? info) (with-handlers ([exn:fail? (lambda (_) (values #f (hasheq)))]) - (entitlement-validate lic (trust-store-path)))) + (entitlement-validate lic (trust-store-path) + #:installation-id installation))) (hasheq 'subject (hash-ref info 'subject #f) 'expiry (hash-ref info 'expires #f) 'updates_through (hash-ref info 'updates-through #f))] - [(eq? tier 'trial) (hasheq 'days_left (trial-days-left))] + [(eq? tier 'trial) + (define evaluation (trial-evaluation)) + (hasheq 'days_left (and evaluation + (inexact->exact + (ceiling (/ (local-trial-evaluation-remaining-seconds + evaluation) + 86400.0)))) + 'expires_at (and evaluation + (local-trial-evaluation-expires-at evaluation)))] [else (hasheq)])) - (hasheq 'tier (symbol->string tier) 'pro (gate-pro?) 'trial_days trial-days 'detail detail)) + (hasheq 'tier (symbol->string tier) + 'pro (gate-pro?) + 'trial_days trial-days + 'installation_id installation + 'detail detail)) diff --git a/linux/GeneratedBackend.hpp b/linux/GeneratedBackend.hpp index 88abaf2..b55c942 100644 --- a/linux/GeneratedBackend.hpp +++ b/linux/GeneratedBackend.hpp @@ -1,6 +1,7 @@ // Generated by Rivet. Do not edit by hand. #pragma once +#include #include #include #include @@ -47,6 +48,7 @@ struct GateInfo { std::string tier; bool pro; std::int64_t trial_days; + std::string installation_id; rivet::Value detail; }; @@ -212,7 +214,7 @@ inline rivet::Value encode__Optional_String_(std::optional const& v inline rivet::Value encode_UpdateState(UpdateState const& v) { rivet::Value::List r; r.reserve(5); r.push_back(encode_String(v.state)); r.push_back(encode_Int64(v.percent)); r.push_back(encode__Optional_String_(v.error)); r.push_back(encode__Optional_String_(v.staged_path)); r.push_back(encode__Optional_String_(v.install_command)); return rivet::Value(std::move(r)); } inline rivet::Value encode_Bool(bool v) { return rivet::Value(v); } inline rivet::Value encode_Any(rivet::Value v) { return v; } -inline rivet::Value encode_GateInfo(GateInfo const& v) { rivet::Value::List r; r.reserve(4); r.push_back(encode_String(v.tier)); r.push_back(encode_Bool(v.pro)); r.push_back(encode_Int64(v.trial_days)); r.push_back(encode_Any(v.detail)); return rivet::Value(std::move(r)); } +inline rivet::Value encode_GateInfo(GateInfo const& v) { rivet::Value::List r; r.reserve(5); r.push_back(encode_String(v.tier)); r.push_back(encode_Bool(v.pro)); r.push_back(encode_Int64(v.trial_days)); r.push_back(encode_String(v.installation_id)); r.push_back(encode_Any(v.detail)); return rivet::Value(std::move(r)); } inline rivet::Value encode_NicInfo(NicInfo const& v) { rivet::Value::List r; r.reserve(3); r.push_back(encode_String(v.name)); r.push_back(encode_String(v.description)); r.push_back(encode_Any(v.detail)); return rivet::Value(std::move(r)); } inline rivet::Value encode__List_NicInfo_(std::vector const& xs) { rivet::Value::List r; r.reserve(xs.size()); for (auto const& x : xs) r.push_back(encode_NicInfo(x)); return rivet::Value(std::move(r)); } inline rivet::Value encode__List_String_(std::vector const& xs) { rivet::Value::List r; r.reserve(xs.size()); for (auto const& x : xs) r.push_back(encode_String(x)); return rivet::Value(std::move(r)); } @@ -250,7 +252,7 @@ inline std::optional decode__Optional_String_(rivet::Value const& v inline UpdateState decode_UpdateState(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p || p->size() != 5) throw std::runtime_error("Rivet result type mismatch: UpdateState"); return UpdateState{decode_String((*p)[0]), decode_Int64((*p)[1]), decode__Optional_String_((*p)[2]), decode__Optional_String_((*p)[3]), decode__Optional_String_((*p)[4])}; } inline bool decode_Bool(rivet::Value const& v) { if (auto p = std::get_if(&v.data)) return *p; throw std::runtime_error("Rivet result type mismatch: Bool"); } inline rivet::Value decode_Any(rivet::Value const& v) { return v; } -inline GateInfo decode_GateInfo(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p || p->size() != 4) throw std::runtime_error("Rivet result type mismatch: GateInfo"); return GateInfo{decode_String((*p)[0]), decode_Bool((*p)[1]), decode_Int64((*p)[2]), decode_Any((*p)[3])}; } +inline GateInfo decode_GateInfo(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p || p->size() != 5) throw std::runtime_error("Rivet result type mismatch: GateInfo"); return GateInfo{decode_String((*p)[0]), decode_Bool((*p)[1]), decode_Int64((*p)[2]), decode_String((*p)[3]), decode_Any((*p)[4])}; } inline NicInfo decode_NicInfo(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p || p->size() != 3) throw std::runtime_error("Rivet result type mismatch: NicInfo"); return NicInfo{decode_String((*p)[0]), decode_String((*p)[1]), decode_Any((*p)[2])}; } inline std::vector decode__List_NicInfo_(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p) throw std::runtime_error("Rivet result type mismatch: (List NicInfo)"); std::vector r; r.reserve(p->size()); for (auto const& x : *p) r.push_back(decode_NicInfo(x)); return r; } inline std::vector decode__List_String_(rivet::Value const& v) { auto p = std::get_if(&v.data); if (!p) throw std::runtime_error("Rivet result type mismatch: (List String)"); std::vector r; r.reserve(p->size()); for (auto const& x : *p) r.push_back(decode_String(x)); return r; } @@ -308,88 +310,130 @@ class API { std::future apply_update() { auto rivet_call = backend_.call("apply-update", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> UpdateState { return detail::decode_UpdateState(rivet_call.get()); }); } [[nodiscard]] std::uint64_t apply_update_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("apply-update", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_UpdateState(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional apply_update_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("apply-update", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_UpdateState(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future bootstrap() { auto rivet_call = backend_.call("bootstrap", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> Bootstrap { return detail::decode_Bootstrap(rivet_call.get()); }); } [[nodiscard]] std::uint64_t bootstrap_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("bootstrap", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bootstrap(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional bootstrap_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("bootstrap", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bootstrap(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future capture_start(std::string iface) { auto rivet_call = backend_.call("capture-start", rivet::Value::List{detail::encode_String(iface)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t capture_start_async(std::string iface, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("capture-start", rivet::Value::List{detail::encode_String(iface)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional capture_start_within(std::string iface, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("capture-start", rivet::Value::List{detail::encode_String(iface)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future capture_status_now() { auto rivet_call = backend_.call("capture-status-now", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> CaptureStatus { return detail::decode_CaptureStatus(rivet_call.get()); }); } [[nodiscard]] std::uint64_t capture_status_now_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("capture-status-now", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_CaptureStatus(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional capture_status_now_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("capture-status-now", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_CaptureStatus(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future capture_stop() { auto rivet_call = backend_.call("capture-stop", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> CaptureStatus { return detail::decode_CaptureStatus(rivet_call.get()); }); } [[nodiscard]] std::uint64_t capture_stop_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("capture-stop", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_CaptureStatus(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional capture_stop_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("capture-stop", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_CaptureStatus(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future check_updates() { auto rivet_call = backend_.call("check-updates", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> UpdateCheck { return detail::decode_UpdateCheck(rivet_call.get()); }); } [[nodiscard]] std::uint64_t check_updates_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("check-updates", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_UpdateCheck(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional check_updates_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("check-updates", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_UpdateCheck(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future conf_preview() { auto rivet_call = backend_.call("conf-preview", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::string { return detail::decode_String(rivet_call.get()); }); } [[nodiscard]] std::uint64_t conf_preview_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("conf-preview", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_String(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional conf_preview_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("conf-preview", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_String(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future diagnostics_export(std::string path, std::string iface, std::string role, std::string reference) { auto rivet_call = backend_.call("diagnostics-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t diagnostics_export_async(std::string path, std::string iface, std::string role, std::string reference, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("diagnostics-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional diagnostics_export_within(std::string path, std::string iface, std::string role, std::string reference, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("diagnostics-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future diagnostics_text() { auto rivet_call = backend_.call("diagnostics-text", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::string { return detail::decode_String(rivet_call.get()); }); } [[nodiscard]] std::uint64_t diagnostics_text_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("diagnostics-text", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_String(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional diagnostics_text_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("diagnostics-text", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_String(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future doctor_report() { auto rivet_call = backend_.call("doctor-report", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> DoctorReport { return detail::decode_DoctorReport(rivet_call.get()); }); } [[nodiscard]] std::uint64_t doctor_report_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("doctor-report", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_DoctorReport(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional doctor_report_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("doctor-report", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_DoctorReport(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future engine_check_offsets() { auto rivet_call = backend_.call("engine-check-offsets", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t engine_check_offsets_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("engine-check-offsets", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional engine_check_offsets_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("engine-check-offsets", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future engine_set_reference(std::string source) { auto rivet_call = backend_.call("engine-set-reference", rivet::Value::List{detail::encode_String(source)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t engine_set_reference_async(std::string source, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("engine-set-reference", rivet::Value::List{detail::encode_String(source)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional engine_set_reference_within(std::string source, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("engine-set-reference", rivet::Value::List{detail::encode_String(source)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future engine_start(std::string role, std::string mode, std::string iface, std::vector ifaces) { auto rivet_call = backend_.call("engine-start", rivet::Value::List{detail::encode_String(role), detail::encode_String(mode), detail::encode_String(iface), detail::encode__List_String_(ifaces)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> EngineStartResult { return detail::decode_EngineStartResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t engine_start_async(std::string role, std::string mode, std::string iface, std::vector ifaces, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("engine-start", rivet::Value::List{detail::encode_String(role), detail::encode_String(mode), detail::encode_String(iface), detail::encode__List_String_(ifaces)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_EngineStartResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional engine_start_within(std::string role, std::string mode, std::string iface, std::vector ifaces, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("engine-start", rivet::Value::List{detail::encode_String(role), detail::encode_String(mode), detail::encode_String(iface), detail::encode__List_String_(ifaces)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_EngineStartResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future engine_status_now() { auto rivet_call = backend_.call("engine-status-now", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> EngineStatus { return detail::decode_EngineStatus(rivet_call.get()); }); } [[nodiscard]] std::uint64_t engine_status_now_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("engine-status-now", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_EngineStatus(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional engine_status_now_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("engine-status-now", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_EngineStatus(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future engine_stop() { auto rivet_call = backend_.call("engine-stop", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> EngineStatus { return detail::decode_EngineStatus(rivet_call.get()); }); } [[nodiscard]] std::uint64_t engine_stop_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("engine-stop", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_EngineStatus(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional engine_stop_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("engine-stop", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_EngineStatus(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future gm_settings() { auto rivet_call = backend_.call("gm-settings", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> GmSettings { return detail::decode_GmSettings(rivet_call.get()); }); } [[nodiscard]] std::uint64_t gm_settings_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("gm-settings", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_GmSettings(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional gm_settings_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("gm-settings", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_GmSettings(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future gm_settings_apply(std::optional clock_class, std::optional clock_accuracy, std::optional offset_scaled_log_variance, std::optional current_utc_offset, std::optional leap61, std::optional leap59, std::optional current_utc_offset_valid, std::optional ptp_timescale, std::optional time_traceable, std::optional frequency_traceable, std::optional time_source, std::optional priority1, std::optional priority2) { auto rivet_call = backend_.call("gm-settings-apply", rivet::Value::List{detail::encode__Optional_Int64_(clock_class), detail::encode__Optional_Int64_(clock_accuracy), detail::encode__Optional_Int64_(offset_scaled_log_variance), detail::encode__Optional_Int64_(current_utc_offset), detail::encode__Optional_Int64_(leap61), detail::encode__Optional_Int64_(leap59), detail::encode__Optional_Int64_(current_utc_offset_valid), detail::encode__Optional_Int64_(ptp_timescale), detail::encode__Optional_Int64_(time_traceable), detail::encode__Optional_Int64_(frequency_traceable), detail::encode__Optional_Int64_(time_source), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t gm_settings_apply_async(std::optional clock_class, std::optional clock_accuracy, std::optional offset_scaled_log_variance, std::optional current_utc_offset, std::optional leap61, std::optional leap59, std::optional current_utc_offset_valid, std::optional ptp_timescale, std::optional time_traceable, std::optional frequency_traceable, std::optional time_source, std::optional priority1, std::optional priority2, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("gm-settings-apply", rivet::Value::List{detail::encode__Optional_Int64_(clock_class), detail::encode__Optional_Int64_(clock_accuracy), detail::encode__Optional_Int64_(offset_scaled_log_variance), detail::encode__Optional_Int64_(current_utc_offset), detail::encode__Optional_Int64_(leap61), detail::encode__Optional_Int64_(leap59), detail::encode__Optional_Int64_(current_utc_offset_valid), detail::encode__Optional_Int64_(ptp_timescale), detail::encode__Optional_Int64_(time_traceable), detail::encode__Optional_Int64_(frequency_traceable), detail::encode__Optional_Int64_(time_source), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional gm_settings_apply_within(std::optional clock_class, std::optional clock_accuracy, std::optional offset_scaled_log_variance, std::optional current_utc_offset, std::optional leap61, std::optional leap59, std::optional current_utc_offset_valid, std::optional ptp_timescale, std::optional time_traceable, std::optional frequency_traceable, std::optional time_source, std::optional priority1, std::optional priority2, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("gm-settings-apply", rivet::Value::List{detail::encode__Optional_Int64_(clock_class), detail::encode__Optional_Int64_(clock_accuracy), detail::encode__Optional_Int64_(offset_scaled_log_variance), detail::encode__Optional_Int64_(current_utc_offset), detail::encode__Optional_Int64_(leap61), detail::encode__Optional_Int64_(leap59), detail::encode__Optional_Int64_(current_utc_offset_valid), detail::encode__Optional_Int64_(ptp_timescale), detail::encode__Optional_Int64_(time_traceable), detail::encode__Optional_Int64_(frequency_traceable), detail::encode__Optional_Int64_(time_source), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future license_activate(std::string path) { auto rivet_call = backend_.call("license-activate", rivet::Value::List{detail::encode_String(path)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t license_activate_async(std::string path, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("license-activate", rivet::Value::List{detail::encode_String(path)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional license_activate_within(std::string path, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("license-activate", rivet::Value::List{detail::encode_String(path)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future license_deactivate() { auto rivet_call = backend_.call("license-deactivate", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> GateInfo { return detail::decode_GateInfo(rivet_call.get()); }); } [[nodiscard]] std::uint64_t license_deactivate_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("license-deactivate", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_GateInfo(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional license_deactivate_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("license-deactivate", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_GateInfo(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future license_status() { auto rivet_call = backend_.call("license-status", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> GateInfo { return detail::decode_GateInfo(rivet_call.get()); }); } [[nodiscard]] std::uint64_t license_status_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("license-status", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_GateInfo(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional license_status_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("license-status", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_GateInfo(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future license_trial_start() { auto rivet_call = backend_.call("license-trial-start", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> GateInfo { return detail::decode_GateInfo(rivet_call.get()); }); } [[nodiscard]] std::uint64_t license_trial_start_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("license-trial-start", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_GateInfo(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional license_trial_start_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("license-trial-start", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_GateInfo(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> list_nics() { auto rivet_call = backend_.call("list-nics", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_NicInfo_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t list_nics_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("list-nics", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_NicInfo_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> list_nics_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("list-nics", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_NicInfo_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> logs() { auto rivet_call = backend_.call("logs", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_LogRow_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t logs_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("logs", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_LogRow_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> logs_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("logs", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_LogRow_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future logs_export(std::string path) { auto rivet_call = backend_.call("logs-export", rivet::Value::List{detail::encode_String(path)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t logs_export_async(std::string path, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("logs-export", rivet::Value::List{detail::encode_String(path)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional logs_export_within(std::string path, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("logs-export", rivet::Value::List{detail::encode_String(path)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future packets_clear() { auto rivet_call = backend_.call("packets-clear", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t packets_clear_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("packets-clear", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional packets_clear_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("packets-clear", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future packets_page(std::int64_t limit) { auto rivet_call = backend_.call("packets-page", rivet::Value::List{detail::encode_Int64(limit)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> PacketPage { return detail::decode_PacketPage(rivet_call.get()); }); } [[nodiscard]] std::uint64_t packets_page_async(std::int64_t limit, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("packets-page", rivet::Value::List{detail::encode_Int64(limit)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_PacketPage(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional packets_page_within(std::int64_t limit, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("packets-page", rivet::Value::List{detail::encode_Int64(limit)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_PacketPage(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future params_merge(std::optional domain, std::optional priority1, std::optional priority2, std::optional log_announce_interval, std::optional log_sync_interval, std::optional network_transport, std::optional delay_mechanism, std::optional transport_specific, std::optional ptp_dst_mac, std::optional p2p_dst_mac, std::optional gm_capable, std::optional slave_only, std::optional assume_two_step, std::optional path_trace_enabled, std::optional follow_up_info, std::optional clock_class, std::vector ifaces) { auto rivet_call = backend_.call("params-merge", rivet::Value::List{detail::encode__Optional_Int64_(domain), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2), detail::encode__Optional_Int64_(log_announce_interval), detail::encode__Optional_Int64_(log_sync_interval), detail::encode__Optional_String_(network_transport), detail::encode__Optional_String_(delay_mechanism), detail::encode__Optional_Int64_(transport_specific), detail::encode__Optional_String_(ptp_dst_mac), detail::encode__Optional_String_(p2p_dst_mac), detail::encode__Optional_Int64_(gm_capable), detail::encode__Optional_Int64_(slave_only), detail::encode__Optional_Int64_(assume_two_step), detail::encode__Optional_Int64_(path_trace_enabled), detail::encode__Optional_Int64_(follow_up_info), detail::encode__Optional_Int64_(clock_class), detail::encode__List_String_(ifaces)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t params_merge_async(std::optional domain, std::optional priority1, std::optional priority2, std::optional log_announce_interval, std::optional log_sync_interval, std::optional network_transport, std::optional delay_mechanism, std::optional transport_specific, std::optional ptp_dst_mac, std::optional p2p_dst_mac, std::optional gm_capable, std::optional slave_only, std::optional assume_two_step, std::optional path_trace_enabled, std::optional follow_up_info, std::optional clock_class, std::vector ifaces, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("params-merge", rivet::Value::List{detail::encode__Optional_Int64_(domain), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2), detail::encode__Optional_Int64_(log_announce_interval), detail::encode__Optional_Int64_(log_sync_interval), detail::encode__Optional_String_(network_transport), detail::encode__Optional_String_(delay_mechanism), detail::encode__Optional_Int64_(transport_specific), detail::encode__Optional_String_(ptp_dst_mac), detail::encode__Optional_String_(p2p_dst_mac), detail::encode__Optional_Int64_(gm_capable), detail::encode__Optional_Int64_(slave_only), detail::encode__Optional_Int64_(assume_two_step), detail::encode__Optional_Int64_(path_trace_enabled), detail::encode__Optional_Int64_(follow_up_info), detail::encode__Optional_Int64_(clock_class), detail::encode__List_String_(ifaces)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional params_merge_within(std::optional domain, std::optional priority1, std::optional priority2, std::optional log_announce_interval, std::optional log_sync_interval, std::optional network_transport, std::optional delay_mechanism, std::optional transport_specific, std::optional ptp_dst_mac, std::optional p2p_dst_mac, std::optional gm_capable, std::optional slave_only, std::optional assume_two_step, std::optional path_trace_enabled, std::optional follow_up_info, std::optional clock_class, std::vector ifaces, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("params-merge", rivet::Value::List{detail::encode__Optional_Int64_(domain), detail::encode__Optional_Int64_(priority1), detail::encode__Optional_Int64_(priority2), detail::encode__Optional_Int64_(log_announce_interval), detail::encode__Optional_Int64_(log_sync_interval), detail::encode__Optional_String_(network_transport), detail::encode__Optional_String_(delay_mechanism), detail::encode__Optional_Int64_(transport_specific), detail::encode__Optional_String_(ptp_dst_mac), detail::encode__Optional_String_(p2p_dst_mac), detail::encode__Optional_Int64_(gm_capable), detail::encode__Optional_Int64_(slave_only), detail::encode__Optional_Int64_(assume_two_step), detail::encode__Optional_Int64_(path_trace_enabled), detail::encode__Optional_Int64_(follow_up_info), detail::encode__Optional_Int64_(clock_class), detail::encode__List_String_(ifaces)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future pcap_export(std::string path) { auto rivet_call = backend_.call("pcap-export", rivet::Value::List{detail::encode_String(path)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t pcap_export_async(std::string path, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("pcap-export", rivet::Value::List{detail::encode_String(path)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional pcap_export_within(std::string path, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("pcap-export", rivet::Value::List{detail::encode_String(path)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future pcap_import(std::string path) { auto rivet_call = backend_.call("pcap-import", rivet::Value::List{detail::encode_String(path)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t pcap_import_async(std::string path, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("pcap-import", rivet::Value::List{detail::encode_String(path)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional pcap_import_within(std::string path, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("pcap-import", rivet::Value::List{detail::encode_String(path)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future preset_apply_rpc(std::string name) { auto rivet_call = backend_.call("preset-apply-rpc", rivet::Value::List{detail::encode_String(name)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t preset_apply_rpc_async(std::string name, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("preset-apply-rpc", rivet::Value::List{detail::encode_String(name)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional preset_apply_rpc_within(std::string name, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("preset-apply-rpc", rivet::Value::List{detail::encode_String(name)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future preset_delete_rpc(std::string name) { auto rivet_call = backend_.call("preset-delete-rpc", rivet::Value::List{detail::encode_String(name)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t preset_delete_rpc_async(std::string name, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("preset-delete-rpc", rivet::Value::List{detail::encode_String(name)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional preset_delete_rpc_within(std::string name, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("preset-delete-rpc", rivet::Value::List{detail::encode_String(name)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future preset_save_rpc(std::string name, std::string role, std::string iface) { auto rivet_call = backend_.call("preset-save-rpc", rivet::Value::List{detail::encode_String(name), detail::encode_String(role), detail::encode_String(iface)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t preset_save_rpc_async(std::string name, std::string role, std::string iface, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("preset-save-rpc", rivet::Value::List{detail::encode_String(name), detail::encode_String(role), detail::encode_String(iface)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional preset_save_rpc_within(std::string name, std::string role, std::string iface, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("preset-save-rpc", rivet::Value::List{detail::encode_String(name), detail::encode_String(role), detail::encode_String(iface)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> presets() { auto rivet_call = backend_.call("presets", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_PresetEntry_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t presets_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("presets", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_PresetEntry_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> presets_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("presets", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_PresetEntry_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future qualify(std::string iface, std::string role, std::string reference, std::vector ifaces) { auto rivet_call = backend_.call("qualify", rivet::Value::List{detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference), detail::encode__List_String_(ifaces)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t qualify_async(std::string iface, std::string role, std::string reference, std::vector ifaces, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("qualify", rivet::Value::List{detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference), detail::encode__List_String_(ifaces)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional qualify_within(std::string iface, std::string role, std::string reference, std::vector ifaces, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("qualify", rivet::Value::List{detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference), detail::encode__List_String_(ifaces)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future report_export(std::string path, std::string iface, std::string role, std::string reference) { auto rivet_call = backend_.call("report-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t report_export_async(std::string path, std::string iface, std::string role, std::string reference, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("report-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional report_export_within(std::string path, std::string iface, std::string role, std::string reference, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("report-export", rivet::Value::List{detail::encode_String(path), detail::encode_String(iface), detail::encode_String(role), detail::encode_String(reference)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future series_reset() { auto rivet_call = backend_.call("series-reset", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t series_reset_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("series-reset", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional series_reset_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("series-reset", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future series_window(std::string series_id) { auto rivet_call = backend_.call("series-window", rivet::Value::List{detail::encode_String(series_id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> SeriesWindow { return detail::decode_SeriesWindow(rivet_call.get()); }); } [[nodiscard]] std::uint64_t series_window_async(std::string series_id, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("series-window", rivet::Value::List{detail::encode_String(series_id)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_SeriesWindow(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional series_window_within(std::string series_id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("series-window", rivet::Value::List{detail::encode_String(series_id)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_SeriesWindow(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future set_settings(std::string language, std::int64_t offset_warn_us) { auto rivet_call = backend_.call("set-settings", rivet::Value::List{detail::encode_String(language), detail::encode_Int64(offset_warn_us)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t set_settings_async(std::string language, std::int64_t offset_warn_us, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("set-settings", rivet::Value::List{detail::encode_String(language), detail::encode_Int64(offset_warn_us)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional set_settings_within(std::string language, std::int64_t offset_warn_us, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("set-settings", rivet::Value::List{detail::encode_String(language), detail::encode_Int64(offset_warn_us)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future simulator_faults() { auto rivet_call = backend_.call("simulator-faults", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> rivet::Value { return detail::decode_Any(rivet_call.get()); }); } [[nodiscard]] std::uint64_t simulator_faults_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("simulator-faults", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Any(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional simulator_faults_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("simulator-faults", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Any(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future start_download() { auto rivet_call = backend_.call("start-download", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> UpdateState { return detail::decode_UpdateState(rivet_call.get()); }); } [[nodiscard]] std::uint64_t start_download_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("start-download", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_UpdateState(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional start_download_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("start-download", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_UpdateState(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future update_state() { auto rivet_call = backend_.call("update-state", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> UpdateState { return detail::decode_UpdateState(rivet_call.get()); }); } [[nodiscard]] std::uint64_t update_state_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("update-state", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_UpdateState(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional update_state_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("update-state", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_UpdateState(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future wireshark_launch(std::string action, std::string iface) { auto rivet_call = backend_.call("wireshark-launch", rivet::Value::List{detail::encode_String(action), detail::encode_String(iface)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> ActionResult { return detail::decode_ActionResult(rivet_call.get()); }); } [[nodiscard]] std::uint64_t wireshark_launch_async(std::string action, std::string iface, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("wireshark-launch", rivet::Value::List{detail::encode_String(action), detail::encode_String(iface)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_ActionResult(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional wireshark_launch_within(std::string action, std::string iface, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("wireshark-launch", rivet::Value::List{detail::encode_String(action), detail::encode_String(iface)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_ActionResult(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } private: rivet::linux_runtime::Backend& backend_; diff --git a/linux/src/main.cpp b/linux/src/main.cpp index 7dba32e..1c91a82 100644 --- a/linux/src/main.cpp +++ b/linux/src/main.cpp @@ -166,6 +166,7 @@ struct AppState { // License page GtkLabel* gate_value{nullptr}; + GtkLabel* installation_value{nullptr}; GtkButton* trial_start{nullptr}; GtkEntry* license_path{nullptr}; GtkButton* activate{nullptr}; @@ -334,6 +335,7 @@ void refresh_capture_buttons(rivet_app::CaptureStatus const& status); void repull_series(std::string const& series); void repull_packets(); void repull_logs(); +void apply_gate_info(rivet_app::GateInfo const& gate); void on_start_clicked(GtkButton*, gpointer); gboolean on_update_auto_check(gpointer); @@ -378,9 +380,7 @@ int on_backend_finished(gpointer) { thresholds.push_back({-warn_us * 1000, "alarm low", "#f5c211"}); } g_state.chart->set_thresholds(std::move(thresholds)); - std::string gate = value.gate.tier; - gate += value.gate.pro ? " (Pro)" : ""; - gtk_label_set_text(g_state.gate_value, gate.c_str()); + apply_gate_info(value.gate); }); }); repull_series("offset"); @@ -741,6 +741,20 @@ void on_gm_apply_clicked(GtkButton*, gpointer) { }); } +// One renderer for every GateInfo arrival (bootstrap, trial, activate, +// deactivate): the tier line plus the copyable installation id a customer +// hands to the vendor for an installation-bound credential. +void apply_gate_info(rivet_app::GateInfo const& gate) { + std::string suffix; + if (gate.tier == "trial") { + suffix = " (trial)"; + } else if (gate.pro) { + suffix = " (Pro)"; + } + gtk_label_set_text(g_state.gate_value, (gate.tier + suffix).c_str()); + gtk_label_set_text(g_state.installation_value, gate.installation_id.c_str()); +} + void on_trial_clicked(GtkButton*, gpointer) { if (g_state.api == nullptr) { return; @@ -751,9 +765,7 @@ void on_trial_clicked(GtkButton*, gpointer) { return; } auto const gate = result.get(); - idle_deliver([gate] { - gtk_label_set_text(g_state.gate_value, (gate.tier + " (trial)").c_str()); - }); + idle_deliver([gate] { apply_gate_info(gate); }); }); } @@ -775,9 +787,7 @@ void on_activate_clicked(GtkButton*, gpointer) { return; } auto const info = gate.get(); - idle_deliver([info] { - gtk_label_set_text(g_state.gate_value, (info.tier + (info.pro ? " (Pro)" : "")).c_str()); - }); + idle_deliver([info] { apply_gate_info(info); }); }); }); }); @@ -793,9 +803,7 @@ void on_deactivate_clicked(GtkButton*, gpointer) { return; } auto const gate = result.get(); - idle_deliver([gate] { - gtk_label_set_text(g_state.gate_value, gate.tier.c_str()); - }); + idle_deliver([gate] { apply_gate_info(gate); }); }); } @@ -1858,10 +1866,12 @@ GtkWidget* build_license_page() { auto* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); auto* gate_row = info_row("License tier", &g_state.gate_value); + auto* installation_row = info_row("Installation ID", &g_state.installation_value); + gtk_label_set_selectable(g_state.installation_value, TRUE); auto* trial = gtk_button_new_with_label("Start Trial"); auto* path = gtk_entry_new(); - gtk_entry_set_placeholder_text(GTK_ENTRY(path), "/path/to/license.lic"); + gtk_entry_set_placeholder_text(GTK_ENTRY(path), "/path/to/entitlement.permit.json"); auto* activate = gtk_button_new_with_label("Activate License"); auto* deactivate = gtk_button_new_with_label("Deactivate"); auto* path_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); @@ -1881,12 +1891,16 @@ GtkWidget* build_license_page() { g_state.report_export = GTK_BUTTON(report); g_state.diagnostics_export = GTK_BUTTON(diagnostics); - auto* note = gtk_label_new("Offline signed licenses; no cloud dependency. Real-engine roles and exports require Pro."); + auto* note = gtk_label_new( + "Offline Permit credentials (.permit.json); no cloud dependency. " + "Real-engine roles and exports require Pro. Hand the installation " + "ID to the vendor when requesting an installation-bound license."); gtk_label_set_wrap(GTK_LABEL(note), TRUE); gtk_widget_add_css_class(note, "dim-label"); gtk_label_set_xalign(GTK_LABEL(note), 0.0F); gtk_box_append(GTK_BOX(box), gate_row); + gtk_box_append(GTK_BOX(box), installation_row); gtk_box_append(GTK_BOX(box), trial); gtk_box_append(GTK_BOX(box), path_row); gtk_box_append(GTK_BOX(box), exports); diff --git a/support/entitlement.rkt b/support/entitlement.rkt index 4d4c625..3a8c59b 100644 --- a/support/entitlement.rkt +++ b/support/entitlement.rkt @@ -23,7 +23,7 @@ (define entitlement-product "gptp-studio") (define entitlement-pro-edition "pro") -;; path trust-store-path -> (values ok? info) +;; path trust-store-path [#:installation-id] -> (values ok? info) ;; info on success: subject / expires (use_expires_at or #f) / ;; updates-through (updates_through or #f) / issuer. ;; info on failure: reason (a stable symbol for messaging) / detail. @@ -31,7 +31,11 @@ ;; a customer-input condition, so every failure from the permit layer is ;; mapped onto a typed reason. Signature and trust decisions raise inside ;; verify-credential; time and grant decisions come back as status reasons. -(define (entitlement-validate path trust-store-path) +;; Callers that hold an installation identity must pass it: a credential +;; whose constraints bind installation_id only verifies on the matching +;; install, and fail-closed (#f) rejects every bound credential. +(define (entitlement-validate path trust-store-path + #:installation-id [installation-id #f]) ;; A verified credential on success; on any verification failure a ;; three-element list ('verify-failed reason detail). Failures travel as ;; plain values so the single-value body never sees multiple values. @@ -56,7 +60,7 @@ (list 'verify-failed 'type "credential is not an entitlement")))) (cond [(verified-credential? result) - (define status (credential-status result)) + (define status (credential-status result #:installation-id installation-id)) (cond [(not (hash-ref status 'usable?)) (values #f @@ -67,7 +71,10 @@ [(installation-mismatch) 'installation] [else 'unusable]) 'detail (format "~a" (hash-ref status 'reasons))))] - [(not (edition-entitled? result entitlement-product entitlement-pro-edition)) + [(not (edition-entitled? result + entitlement-product + entitlement-pro-edition + #:installation-id installation-id)) (values #f (hasheq 'reason 'edition 'detail "credential does not grant the pro edition"))] [else diff --git a/tests/gate-test.rkt b/tests/gate-test.rkt index 9421e3d..6d1eeec 100644 --- a/tests/gate-test.rkt +++ b/tests/gate-test.rkt @@ -6,6 +6,7 @@ ;; issuer key, never the shipped trust store. (require rackunit + racket/date racket/file racket/runtime-path json @@ -55,7 +56,8 @@ #:credential-id [credential-id "cred_gate_test"] #:subject-id [subject-id "customer_01"] #:subject-display [subject-display "customer@example.com"] - #:issued-at [issued-at "2026-01-01T00:00:00Z"]) + #:issued-at [issued-at "2026-01-01T00:00:00Z"] + #:installation-id [installation-id 'null]) (hasheq 'protocol "permit" 'version 1 'type "entitlement" @@ -85,7 +87,7 @@ 'limits (hasheq 'named_seats 'null 'device_activations 'null 'concurrent_seats 'null) - 'installation_id 'null + 'installation_id installation-id 'activation_required #f) 'extensions (hasheq))) @@ -384,3 +386,125 @@ (issue-credential (string->bytes/utf-8 (jsexpr->string (gate-payload))) private-key "test-1" store-v2)) "safe issuance refuses a retired signing key") + +;; ---- installation identity and installation-bound credentials ---- +;; Every install holds a stable ins_… id (persisted beside the gate +;; state). A credential whose constraints bind that id verifies only on +;; the matching install; presenting a bound credential without an id +;; fails closed; the gate surfaces the id for the customer to hand to +;; the vendor. + +(let ([scratch (make-temporary-file "gate-ins-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (define iid (gate-installation-id)) + (check-true (regexp-match? #px"^ins_[0-9a-f]{32}$" iid) + "the installation id has permit's ins_ + 32 hex shape") + (check-equal? (gate-installation-id) iid "the id is stable across calls") + (check-true (file-exists? (build-path scratch ".gptp-studio" "installation-id")) + "the id persists beside the gate state") + + (define bound-here + (issue-to-file (gate-payload #:credential-id "cred_gate_bound" + #:installation-id iid) + (make-temporary-file "gate-bound-~a.permit.json"))) + (define bound-elsewhere + (issue-to-file (gate-payload #:credential-id "cred_gate_foreign-install" + #:installation-id + "ins_ffffffffffffffffffffffffffffffff") + (make-temporary-file "gate-bound2-~a.permit.json"))) + + (define-values (bh-ok? _bh-info) + (entitlement-validate bound-here trust-store-path #:installation-id iid)) + (check-true bh-ok? "a credential bound to this installation verifies") + + (define-values (be-ok? be-info) + (entitlement-validate bound-elsewhere trust-store-path #:installation-id iid)) + (check-false be-ok? "a credential bound to another installation fails") + (check-equal? (hash-ref be-info 'reason) 'installation + "the mismatch is reported as installation") + + (define-values (nc-ok? nc-info) + (entitlement-validate bound-here trust-store-path)) + (check-false nc-ok? "a bound credential without an id fails closed") + (check-equal? (hash-ref nc-info 'reason) 'installation + "fail-closed is reported as installation") + + (define-values (ib-act-ok? _ib-subject) (gate-activate! bound-here)) + (check-true ib-act-ok? "an installation-bound credential activates") + (check-equal? (gate-init!) 'pro "the bound credential drives the pro tier") + (define ins-info (gate-info)) + (check-equal? (hash-ref ins-info 'installation_id) iid + "gate-info surfaces the installation id") + (gate-deactivate!) + (check-equal? (gate-init!) 'free "deactivation drops the bound credential")) + +;; ---- the local trial ---- +;; Trial state is permit local-trial format in the historical path: an +;; explicit start opens a 14-day pro window that survives restarts; +;; legacy date-anchored state migrates (active stays active, expired +;; stays expired); corrupt state reads as absent and an explicit start +;; replaces it. + +(define (iso-days-ago n) + (define d (seconds->date (- (current-seconds) (* n 86400)))) + (format "~a-~a-~a" + (date-year d) + (let ([s (number->string (date-month d))]) + (if (< (string-length s) 2) (string-append "0" s) s)) + (let ([s (number->string (date-day d))]) + (if (< (string-length s) 2) (string-append "0" s) s)))) + +(define (write-legacy-trial-state! started) + ;; gate-installation-id doubles as the "state dir exists" step + (gate-installation-id) + (call-with-output-file (gate-state-path) + (lambda (out) (write-json (hasheq 'trial-started started) out)) + #:exists 'replace)) + +;; a fresh trial runs the full pro surface and survives a restart +(let ([scratch (make-temporary-file "gate-trial-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (check-equal? (gate-init!) 'free "a fresh home without trial state is free") + (gate-trial-start!) + (check-equal? (gate-init!) 'trial "an explicit start opens the trial tier") + (check-true (gate-pro?) "a trial holds the pro surface") + (define state (call-with-input-file (gate-state-path) read-json)) + (check-equal? (hash-ref state 'protocol) "permit-local-trial" + "trial state is permit local-trial format") + (define detail (hash-ref (gate-info) 'detail)) + (check-true (and (integer? (hash-ref detail 'days_left)) + (<= 1 (hash-ref detail 'days_left) 14)) + "the trial reports days_left within its window") + (check-true (string? (hash-ref detail 'expires_at)) + "the trial reports its expiry instant") + (check-equal? (gate-init!) 'trial "the trial survives a restart")) + +;; an active legacy window migrates and keeps running +(let ([scratch (make-temporary-file "gate-legacy-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (write-legacy-trial-state! (iso-days-ago 1)) + (check-equal? (gate-init!) 'trial + "an active legacy trial migrates to the trial tier") + (define state (call-with-input-file (gate-state-path) read-json)) + (check-equal? (hash-ref state 'protocol) "permit-local-trial" + "migration rewrote the state into local-trial format")) + +;; an expired legacy window stays expired; Trial cannot restart it +(let ([scratch (make-temporary-file "gate-legacy2-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (write-legacy-trial-state! "2026-01-01") + (check-equal? (gate-init!) 'free "an expired legacy trial reads as free") + (gate-trial-start!) + (check-equal? (gate-init!) 'free + "an expired window is preserved; Trial does not restart")) + +;; corrupt state reads as absent; an explicit start replaces it +(let ([scratch (make-temporary-file "gate-trial3-~a" 'directory)]) + (putenv "GPTP_STUDIO_HOME" (path->string scratch)) + (gate-installation-id) ; ensures ~/.gptp-studio exists + (call-with-output-file (gate-state-path) + (lambda (out) (display "{{{not json" out)) + #:exists 'replace) + (check-equal? (gate-init!) 'free "corrupt trial state reads as free, no crash") + (gate-trial-start!) + (check-equal? (gate-init!) 'trial "a start after corrupt state opens fresh")) From 385594d18d72575ad3b2fb260ceb84d3dd60840e Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Thu, 22 Oct 2026 23:57:24 +0800 Subject: [PATCH 2/6] fix: declare the GTK4 runtime dependencies the host actually links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deb still depended on libgtk-3 + libwebkit2gtk-4.1 — the stack the 0.1.0 Rivet migration removed — while the GTK4 host links gtk4, gio, ncurses, zlib and libsecret. Depends is now libgtk-4-1 (>= 4.10) + libsecret-1-0 (plus the engine tools); the rpm spec moves gtk3/ webkit2gtk4.1 to gtk4/libsecret the same way. --- scripts/package-deb.sh | 2 +- scripts/package-extras.sh | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/package-deb.sh b/scripts/package-deb.sh index 276ef09..c1995aa 100644 --- a/scripts/package-deb.sh +++ b/scripts/package-deb.sh @@ -67,7 +67,7 @@ Priority: optional Architecture: $ARCH Maintainer: turinglambdaai Homepage: https://github.com/turinglambdaai/gptp-studio -Depends: libgtk-3-0 | libgtk-3-0t64, libwebkit2gtk-4.1-0, libpcap0.8 | libpcap0.8t64, linuxptp, ethtool, iproute2, libcap2-bin, openssl +Depends: libgtk-4-1 (>= 4.10), libpcap0.8t64 | libpcap0.8, linuxptp, ethtool, iproute2, libcap2-bin, libsecret-1-0 Description: Professional gPTP / IEEE 802.1AS debugging workstation gPTP Studio is a Linux-native engineering workstation for Automotive Ethernet timing work. It combines linuxptp engine control, PHC and hardware diff --git a/scripts/package-extras.sh b/scripts/package-extras.sh index f02846c..9b890de 100755 --- a/scripts/package-extras.sh +++ b/scripts/package-extras.sh @@ -76,14 +76,13 @@ Summary: Professional gPTP / IEEE 802.1AS debugging workstation License: EULA URL: https://github.com/turinglambdaai/gptp-studio BuildArch: __GPTP_RPM_ARCH__ -Requires: gtk3 -Requires: webkit2gtk4.1 +Requires: gtk4 >= 4.10.0 Requires: libpcap Requires: linuxptp Requires: ethtool Requires: iproute Requires: libcap -Requires: openssl +Requires: libsecret %description gPTP Studio is a Linux-native engineering workstation for Automotive From e66e2b14817b023cf2459daea2b45890e454f740 Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Thu, 22 Oct 2026 23:57:30 +0800 Subject: [PATCH 3/6] ci: cancel superseded runs on the same ref --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc9621d..0bae4e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,12 @@ on: pull_request: branches: [main] +# A new push to the same ref supersedes the queued run; in-flight runs on +# main are never cancelled mid-release. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + jobs: test: runs-on: ${{ matrix.os }} From baf7d6717aae03182c6384821c144a4cf786ca9f Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Thu, 22 Oct 2026 23:57:30 +0800 Subject: [PATCH 4/6] docs: align the acceptance guide and licensing docs with the GTK4 product ACCEPTANCE.md still described the WebKitGTK stack (gtk3/webkit deps, the old page names, glaze-era entry commands, a 22.04 gate). It now matches the Rivet/GTK4 product, adds license and update-window acceptance points, and points at rivet-acceptance.rktd as the executable gate. The vendor guide documents installation-bound issuance; the roadmap marks the signed update channel as delivered. --- ACCEPTANCE.md | 69 +++++++++++++++++++----------------- docs/roadmap.md | 2 +- docs/vendor-license-guide.md | 8 +++++ 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/ACCEPTANCE.md b/ACCEPTANCE.md index f74f884..ef690af 100644 --- a/ACCEPTANCE.md +++ b/ACCEPTANCE.md @@ -1,13 +1,15 @@ # 验收指南 / ACCEPTANCE -> gPTP Studio 的产品验收只针对 Linux。推荐使用 Ubuntu 22.04 LTS 或 Ubuntu 24.04 LTS,并优先在带真实 PHC/HW timestamp 的网卡上执行真机项。 +> gPTP Studio 的产品验收只针对 Linux。基线发行版是 Ubuntu 24.04 LTS(GTK4 宿主要求 GTK ≥ 4.10,22.04 的 4.6 低于下限),并优先在带真实 PHC/HW timestamp 的网卡上执行真机项。 +> +> 可执行验收门以 `rivet-acceptance.rktd` 为准(`raco rivet acceptance --json`):合同测试、RVT1 wire、GTK4 宿主构建、打包、AT-SPI 原生 UI 五个套件。本文件是其上的人工验收补充;涉及物理 NIC/PHC/精度的声明另见 §3–§5 与 `docs/hardware-qualification.md`。 ## 0. 环境 ```bash sudo apt update sudo apt install linuxptp libpcap-dev ethtool iproute2 libcap2-bin \ - libgtk-3-0 libwebkit2gtk-4.1-0 + libgtk-4-1 libsecret-1-0 ``` 先执行: @@ -32,27 +34,37 @@ gptp-studio --doctor gptp-studio --simulator ``` -- [ ] WebKitGTK 原生窗口正常打开; -- [ ] 六页导航与顶部状态栏正常; -- [ ] Overview 曲线持续更新; -- [ ] Packets 页面持续出现真实编码的模拟 gPTP 帧; -- [ ] 报文详情、解码树、hex 可用; -- [ ] Role & Config 修改 Domain/Sync 周期后 `ptp4l.conf` 立即变化; +- [ ] GTK4 原生窗口正常打开; +- [ ] 六页导航(Engine / Qualification / Packets / Logs / GM Tuning / License & Reports)与顶部状态栏正常; +- [ ] Engine 页同步曲线持续更新; +- [ ] Packets 页面持续出现真实编码的模拟 gPTP 帧,报文详情、解码树、hex 可用; +- [ ] GM Tuning 修改优先级后 `pmc GRANDMASTER_SETTINGS_NP` 应用到运行中的引擎; - [ ] 角色切换、告警、日志、预设、语言切换正常。 -## 2. 官方包 +## 2. 许可证与更新 + +- [ ] License 页显示本机 Installation ID(`ins_` + 32 位十六进制),可选中复制,重启后不变; +- [ ] Start Trial 后档位变为 trial,剩余天数在 1–14 内;试用期间引擎控制/导出可用; +- [ ] 系统时钟回拨超过 5 分钟后试用判定失效(免费档),不崩溃; +- [ ] 试用到期后回到 free,引擎控制/导出重新被拒并给出 Pro 提示; +- [ ] 激活一份有效 `.permit.json` 后档位变为 Pro,detail 显示 subject 与 updates_through;激活绑定其他 Installation ID 的凭据被拒绝并提示; +- [ ] Deactivate 后回到 free,`~/.gptp-studio/entitlement.permit.json` 被删除; +- [ ] 「检查更新…」在有新版本的 feed 上给出同意对话框,全程错误文案可复制; +- [ ] Pro 永久授权在 `updates_through` 截止日当天仍可下载官方更新,次日起拒绝出网并说明续购;Free/试用不受限。 + +## 3. 官方包 ### Debian 包 ```bash -sudo apt install ./gPTP-Studio-v1.0.0-linux-amd64.deb +sudo apt install ./gPTP-Studio-linux-x64.deb command -v gptp-studio gptp-studio --doctor ``` - [ ] `/usr/bin/gptp-studio` 可用; -- [ ] 桌面菜单存在 gPTP Studio; -- [ ] 图标正常; +- [ ] 依赖声明为 GTK4 运行库(`libgtk-4-1`),不含 WebKitGTK; +- [ ] 桌面菜单存在 gPTP Studio,图标正常; - [ ] `/usr/share/doc/gptp-studio/` 包含 LICENSE / NOTICE / EULA / THIRD_PARTY_NOTICES; - [ ] 安装动作没有自动修改 sudoers 或 file capabilities; - [ ] 卸载软件不会删除 `~/.gptp-studio/` 用户数据。 @@ -60,17 +72,17 @@ gptp-studio --doctor ### Relocatable tarball ```bash -tar -xzf gPTP-Studio-v1.0.0-linux-x64.tar.gz +tar -xzf gPTP-Studio-linux-x64.tar.gz cd gptp-studio-distributed ./bin/gptp-studio --version ./bin/gptp-studio --doctor ``` - [ ] 无需 Racket 源码环境即可运行; -- [ ] license payload 完整; +- [ ] 应用内 `app/keys/trust-store.json`(Permit 信任库)随包分发; - [ ] Doctor 与安装版语义一致。 -## 3. 真机 NIC / PHC +## 4. 真机 NIC / PHC 在支持 IEEE 1588/PTP hardware timestamp 的网卡上: @@ -86,7 +98,7 @@ gptp-studio --doctor - [ ] link DOWN 只产生 VERIFY/WARN,不作为结构性 FAIL; - [ ] 明确没有 HW timestamp 或 PHC 时真实 GM/Slave 被阻止启动。 -## 4. 权限路径 +## 5. 权限路径 分别验证至少一种生产可接受路径: @@ -101,7 +113,7 @@ gptp-studio --doctor - [ ] GM + system reference 单独检查 `phc2sys` 时钟写权限; - [ ] 被拒绝的新会话不会误停正在运行的健康会话。 -## 5. 双机 / ECU 真机 +## 6. 双机 / ECU 真机 - [ ] Studio GM → ECU/EthTSyn Slave 能进入同步; - [ ] ECU/设备 GM → Studio Slave 曲线收敛; @@ -109,7 +121,7 @@ gptp-studio --doctor - [ ] offset jump 时能回看同一时间附近的 Sync / Follow_Up / PDelay / Announce; - [ ] 抓包 timestamp source 与 NIC capability 分开显示。 -## 5b. Boundary Clock 双端口真机 +## 6b. Boundary Clock 双端口真机 在两张支持硬件时间戳的网卡上: @@ -121,27 +133,18 @@ gptp-studio --doctor - [ ] 上游接真实 GM、下游接 DUT 时,DUT 能同步,`pmc GET CURRENT_DATA_SET` 与 UI 一致; - [ ] 模拟器 Boundary 剧本双端口有流量且不影响其它角色。 -## 6. 工程质量 +## 7. 工程质量 ```bash -raco make main.rkt raco test tests/ -racket main.rkt --selfcheck -racket main.rkt --doctor-json | python3 -m json.tool >/dev/null -bash scripts/package-deb.sh 1.0.0 v1.0.0-local +raco rivet build +raco rivet generate && test -s linux/GeneratedBackend.hpp +raco rivet acceptance --json # 完整 Linux 产品门 ``` -GitHub Actions 必须在 **Ubuntu 22.04 + Ubuntu 24.04** 同时通过: - -- Compile -- 全部测试 -- selfcheck -- Doctor -- Linux relocatable tarball smoke -- Debian package metadata/extract/runtime smoke -- license payload smoke +GitHub Actions(ubuntu-24.04,amd64 + arm64)必须在 PR 与 main 上保持全绿:全部测试、GTK4 宿主构建、生成客户端存在性。发布流水线(tag 触发)额外执行:`raco rivet release --development` 的打包 + 自检 + Doctor 冒烟、amd64 deb 实装冒烟、签名 feed 生成与自验。 -## 7. 精度声明 +## 8. 精度声明 任何对外精度数字必须来自固定 reference platform + 外部参考仪器 characterization。以下事实本身都不能被写成精度保证: diff --git a/docs/roadmap.md b/docs/roadmap.md index 9b43adb..e216f67 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -25,7 +25,7 @@ - 固定 Ubuntu LTS reference image - 主动 timing capability benchmark - 参考平台 characterization 数据库 -- 自动更新通道与签名发布 +- ✅ 自动更新通道与签名发布(0.2.0 上线签名 feed 与应用内更新;0.3.0 起官方更新窗按 entitlement 收口) - systemd/udev/polkit 是否值得引入,以安全性和可维护性为前提评估 ## v2 — Linux + 专用硬件 diff --git a/docs/vendor-license-guide.md b/docs/vendor-license-guide.md index dc79bb2..b3ae696 100644 --- a/docs/vendor-license-guide.md +++ b/docs/vendor-license-guide.md @@ -75,6 +75,14 @@ raco permit keygen \ 字段语义:`use_expires_at` 设日期即订阅式(到期停用);留 null 即永久。 注意 jsexpr 世界的"无值"写 JSON `null`,不是 `false`。 +### 安装绑定(可选) + +`constraints.installation_id` 填客户的安装 ID(`ins_` + 32 位十六进制)即把凭据 +绑定到那一台安装:客户在应用内 License & Reports 页复制 Installation ID 发给 +你,签发时填入该字段。产品端验证时传入本机 ID,不匹配即拒绝(应用提示 +「许可证绑定其他安装实例」);保持 null 表示不绑定、任意安装可激活。重装系统 +或换机后 ID 会变,走「续订 / 变更」重签一份绑定新 ID 的凭据即可。 + 签发(签名覆盖 payload 字节本身,签发器会先做 schema 校验): ```bash From 34ac5e4800fb2a5e28106615424077f1a377ee01 Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Thu, 22 Oct 2026 23:57:30 +0800 Subject: [PATCH 5/6] chore: release 0.3.0 --- CHANGELOG.md | 26 +++++++++++++++++++++++++- app/state.rkt | 2 +- info.rkt | 2 +- rivet.rktd | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20435ef..e3dc8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ # 更新日志 / Changelog -## Unreleased +## 0.3.0 - 2026-10-23 + +**安装身份与本地试用落到 Permit 原语,官方更新窗完成收费闭环。** 安装 +ID、试用状态机、凭据验证全部接上 permit 协议;deb/rpm 运行时依赖从 +WebKitGTK 旧栈改为 GTK4 真实依赖。 ### Added +- **安装身份**:每台安装持有稳定 Installation ID(`ins_` + 32 hex,首次 + 使用时生成、持久于 `~/.gptp-studio/installation-id`)。凭据验证把本机 + ID 传给 permit 的安装绑定检查——`installation_id` 绑定的凭据自此可用, + 不匹配或未携带 ID 一律 fail-closed。License 页显示可复制的安装 ID + (含获取绑定凭据的指引文案),类型化 `GateInfo` RPC 增加 + `installation_id` 字段;`docs/vendor-license-guide.md` 记录签发侧的 + 绑定流程。 +- **permit 本地试用**:14 天试用从自制日期状态改用 permit local-trial + (固定起止窗口 + last_seen 高水位),时钟回拨超容差即失效、重启 + 不重置;旧版 `trial-started` 日期状态在首次读取时自动迁移——未到期 + 保留剩余窗口,已到期保持到期,Trial 不能重启已消费的窗口。 - **官方更新窗成为产品机制**(第一个收费范例的地基):Pro 永久 entitlement 的 `updates_through` 自此贯通发行链与产品——commerce 侧 offer (`gptp-studio-pro-perpetual` / `gptp-studio-pro-team-5`,定价见 @@ -17,9 +32,18 @@ 时效),明示「续购更新窗即为续费」。 - `docs/vendor-license-guide.md` 新增正式发行路径:手工签发仅限测试与补发, 正式销售走 commerce 的 offer/policy 映射与渠道 webhook。 +- CI 并发守卫:同一 ref 的新推送自动取消排队中的旧运行(main 不取消)。 ### Fixed +- **deb/rpm 运行时依赖**:从 `libgtk-3-0 + libwebkit2gtk-4.1-0`(0.1.0 + 已整体移除的旧栈)改为 `libgtk-4-1 (>= 4.10) + libsecret-1-0`——宿主 + 的真实链接面;rpm 的 `gtk3/webkit2gtk4.1` 同步改 `gtk4/libsecret`。 + 此前在精简系统上安装会缺 GTK4 运行库、反而多装 WebKit。 +- 宿主 License 页残留旧 `license.lic` 占位文案改为 `.permit.json`。 +- ACCEPTANCE.md 与旧栈对齐的章节(GTK3/WebKit 环境依赖、WebKitGTK + 窗口、旧页名、glaze 时代入口命令、22.04 门)改写为 Rivet/GTK4 产品 + 现状,并补许可证与更新的验收点。 - 凭据验证信息补齐 `updates-through` 字段(此前只暴露 `use_expires_at`), gate 测试覆盖窗口边界(截止日当天仍可更新、次日拒绝、Free 恒开)。 diff --git a/app/state.rkt b/app/state.rkt index d913d5f..da1ec96 100644 --- a/app/state.rkt +++ b/app/state.rkt @@ -33,7 +33,7 @@ settings-set! app-version) -(define app-version "0.2.0") +(define app-version "0.3.0") ;; ---- stores (created once) ----------------------------------------------------- diff --git a/info.rkt b/info.rkt index 2e435cd..0597ca9 100644 --- a/info.rkt +++ b/info.rkt @@ -2,7 +2,7 @@ (define collection "gptp-studio") (define pkg-desc "gPTP Studio - gPTP / IEEE 802.1AS debugging workstation (Racket + Glaze)") -(define version "0.2.0") +(define version "0.3.0") (define pkg-authors '("turinglambdaai")) (define license 'Apache-2.0) diff --git a/rivet.rktd b/rivet.rktd index e994859..ef158f0 100644 --- a/rivet.rktd +++ b/rivet.rktd @@ -1 +1 @@ -#hasheq((name . "gptp-studio") (display-name . "gPTP Studio") (version . "0.2.0") (build . 2) (identifier . "site.jrtx.gptpstudio") (release-channel . stable) (url-schemes . ()) (file-associations . ()) (resources . ("assets")) (linux-icon . "assets/icon.png") (device-rpcs . ()) (macos-min-version . "14.0") (windows-min-version . "10.0.19041.0") (macos-icon . "assets/icon.icns") (windows-icon . "assets/icon.ico") (linux-binary-name . "gptp-studio") (backend . "app/backend.rkt") (module . "backend") (entry . "start") (protocol . 1)) +#hasheq((name . "gptp-studio") (display-name . "gPTP Studio") (version . "0.3.0") (build . 3) (identifier . "site.jrtx.gptpstudio") (release-channel . stable) (url-schemes . ()) (file-associations . ()) (resources . ("assets")) (linux-icon . "assets/icon.png") (device-rpcs . ()) (macos-min-version . "14.0") (windows-min-version . "10.0.19041.0") (macos-icon . "assets/icon.icns") (windows-icon . "assets/icon.ico") (linux-binary-name . "gptp-studio") (backend . "app/backend.rkt") (module . "backend") (entry . "start") (protocol . 1)) From 83bc2a3bb1372fb8943688fd86a1f1c93e6b01fd Mon Sep 17 00:00:00 2001 From: turinglambdaai Date: Fri, 23 Oct 2026 00:07:28 +0800 Subject: [PATCH 6/6] chore: replace the schema baseline for the GateInfo installation_id field The 0.3.0 contract change is intentional: GateInfo carries the installation identity as a typed field, so the compatibility baseline moves with it. --- rivet-schema.json | 145 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/rivet-schema.json b/rivet-schema.json index 7951eb8..e19794a 100644 --- a/rivet-schema.json +++ b/rivet-schema.json @@ -530,6 +530,13 @@ "name": "Int64" } }, + { + "name": "installation_id", + "type": { + "kind": "scalar", + "name": "String" + } + }, { "name": "detail", "type": { @@ -868,9 +875,115 @@ } ], "name": "TimestampInfo" + }, + { + "fields": [ + { + "name": "status", + "type": { + "kind": "scalar", + "name": "String" + } + }, + { + "name": "message", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + }, + { + "name": "current_version", + "type": { + "kind": "scalar", + "name": "String" + } + }, + { + "name": "available_version", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + }, + { + "name": "url", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + } + ], + "name": "UpdateCheck" + }, + { + "fields": [ + { + "name": "state", + "type": { + "kind": "scalar", + "name": "String" + } + }, + { + "name": "percent", + "type": { + "kind": "scalar", + "name": "Int64" + } + }, + { + "name": "error", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + }, + { + "name": "staged_path", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + }, + { + "name": "install_command", + "type": { + "element": { + "kind": "scalar", + "name": "String" + }, + "kind": "optional" + } + } + ], + "name": "UpdateState" } ], "rpcs": [ + { + "arguments": [], + "name": "apply-update", + "result": { + "kind": "record", + "name": "UpdateState" + } + }, { "arguments": [], "name": "bootstrap", @@ -911,6 +1024,14 @@ "name": "CaptureStatus" } }, + { + "arguments": [], + "name": "check-updates", + "result": { + "kind": "record", + "name": "UpdateCheck" + } + }, { "arguments": [], "name": "conf-preview", @@ -956,6 +1077,14 @@ "name": "ActionResult" } }, + { + "arguments": [], + "name": "diagnostics-text", + "result": { + "kind": "scalar", + "name": "String" + } + }, { "arguments": [], "name": "doctor-report", @@ -1709,6 +1838,22 @@ "name": "Any" } }, + { + "arguments": [], + "name": "start-download", + "result": { + "kind": "record", + "name": "UpdateState" + } + }, + { + "arguments": [], + "name": "update-state", + "result": { + "kind": "record", + "name": "UpdateState" + } + }, { "arguments": [ {