geth/crates/geth-control/src/lib.rs

3869 lines
118 KiB
Rust
Raw Normal View History

2026-05-21 18:01:38 +02:00
use geth_auth::{AuthExplanation, AuthOp, AuthOpSignature};
2026-05-18 03:57:26 +02:00
use geth_cas::{FileConflict, FileRoot, FileRootScan};
2026-05-17 20:32:36 +02:00
use geth_db::{CrSqliteChangeBatch, DbResource};
2026-05-18 04:03:52 +02:00
use geth_discovery::{DiscoveredPeer, PeerCard};
2026-05-17 19:59:03 +02:00
use geth_document::{DocumentResource, DocumentState};
use geth_keychain::{
KeychainAllowedSigner, KeychainOp, KeychainOpSignature, KeychainSigchainReport,
NodeEnrollmentRequest, NodeRecord,
};
2026-05-18 18:36:03 +02:00
use geth_kv::{KvEntry, KvResource, KvSyncEntry};
2026-05-23 02:08:51 +02:00
use geth_overlay::{
OverlayInterfacePlan, OverlayJoinPlan, OverlayNetworkStatus, OverlayPacket, OverlayPlan,
OverlayRuntimeStatus,
};
2026-05-20 13:57:14 +02:00
use geth_pipe::{PipeConnection, PipeListener, PipeMessage};
2026-05-17 18:23:51 +02:00
use geth_pubsub::PubsubMessage;
2026-05-15 15:08:20 +02:00
use geth_resource::ResourceDescriptor;
2026-05-19 19:08:08 +02:00
use geth_secrets::{BearerAccess, BearerChallenge, BearerProof, ResourceMasterSecret};
use geth_ssh_identity::{
SshCertApproval, SshCertRequest, SshCertificateRecord, SshDistributionLogEntry,
SshRevocationEntry,
};
2026-05-19 15:51:11 +02:00
use geth_ssh_proxy::SshProxyConnection;
2026-05-15 15:08:20 +02:00
use geth_types::BlobHash;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ControlRequest {
Status,
NodeId,
2026-05-18 04:03:52 +02:00
PeerCardExport {
out: Option<PathBuf>,
},
PeerCardImport {
path: PathBuf,
},
PeerCardList,
2026-05-18 12:09:50 +02:00
PeerPing {
node: String,
},
2026-05-18 17:01:50 +02:00
PeerAuthCheck {
node: String,
resource: String,
capability: String,
},
2026-05-15 15:08:20 +02:00
ResourceList,
ResourceCreate {
kind: String,
name: String,
},
2026-05-23 01:17:30 +02:00
OverlayStatus,
OverlayPlan {
name: String,
cidr: Option<String>,
},
OverlayJoin {
name: String,
secret: String,
cidr: Option<String>,
},
OverlayLeave {
name: String,
},
2026-05-23 02:08:51 +02:00
OverlayInterfacePlan {
name: String,
platform: Option<String>,
},
OverlayUp {
name: String,
bearer_secret: Option<String>,
mtu: Option<u16>,
},
OverlayDown {
name: String,
},
OverlayPeers {
name: String,
},
OverlaySend {
name: String,
node: String,
packet_base64: String,
bearer_secret: Option<String>,
},
OverlayRecv {
name: String,
peek: bool,
},
2026-05-15 15:08:20 +02:00
CasAdd {
path: PathBuf,
},
2026-05-21 01:35:00 +02:00
CasAddPrivate {
resource: String,
path: PathBuf,
},
2026-05-15 15:08:20 +02:00
CasGet {
hash: BlobHash,
out: PathBuf,
},
2026-05-21 01:35:00 +02:00
CasGetPrivate {
resource: String,
hash: BlobHash,
out: PathBuf,
},
2026-05-18 17:18:25 +02:00
CasFetch {
node: String,
hash: BlobHash,
bearer_secret: Option<String>,
2026-05-18 17:18:25 +02:00
},
2026-05-15 15:08:20 +02:00
CasHash {
path: PathBuf,
},
CasHas {
hash: BlobHash,
},
2026-05-16 16:36:35 +02:00
CasPin {
hash: BlobHash,
},
CasUnpin {
hash: BlobHash,
},
2026-05-16 21:10:25 +02:00
CasCleanup {
dry_run: bool,
},
2026-05-19 15:37:02 +02:00
CasProviders {
hash: BlobHash,
},
2026-05-15 15:08:20 +02:00
CasList,
2026-05-18 03:50:09 +02:00
CasRootAdd {
name: String,
path: PathBuf,
},
CasRootList,
CasRootScan {
name: String,
},
2026-05-20 13:22:40 +02:00
CasRootSync {
node: String,
name: String,
bearer_secret: Option<String>,
},
2026-05-20 13:30:57 +02:00
CasRootApply {
source: String,
target: PathBuf,
dry_run: bool,
},
2026-05-18 03:57:26 +02:00
CasConflictRecord {
root: String,
path: String,
kind: String,
detail: String,
base_tree: Option<BlobHash>,
local_tree: Option<BlobHash>,
remote_tree: Option<BlobHash>,
},
CasConflictList {
root: Option<String>,
},
CasConflictResolve {
conflict_id: String,
resolution: String,
note: Option<String>,
},
2026-05-21 11:29:29 +02:00
NodeList,
NodeRename {
node: String,
name: String,
signing_key_path: Option<PathBuf>,
},
NodeRevoke {
node: String,
signing_key_path: Option<PathBuf>,
},
NodeGrant {
node: String,
resource: String,
capability: String,
grant_id: Option<String>,
2026-05-21 18:15:10 +02:00
signing_key_path: Option<PathBuf>,
admin_key_path: Option<PathBuf>,
2026-05-21 11:29:29 +02:00
},
NodeRevokeGrant {
resource: String,
grant_id: String,
2026-05-21 18:15:10 +02:00
signing_key_path: Option<PathBuf>,
admin_key_path: Option<PathBuf>,
},
NodeEndpointAdd {
node: String,
endpoint: String,
signing_key_path: Option<PathBuf>,
},
NodeEndpointRevoke {
node: String,
endpoint: String,
signing_key_path: Option<PathBuf>,
2026-05-21 11:29:29 +02:00
},
2026-05-21 18:01:38 +02:00
NodeEnrollRequest {
node_name: String,
capabilities: Vec<String>,
reason: Option<String>,
out: Option<PathBuf>,
},
2026-07-18 16:48:35 +02:00
NodeEnrollJoin {
peer_card_path: PathBuf,
admin_key_path: PathBuf,
node_name: String,
capabilities: Vec<String>,
reason: Option<String>,
out: Option<PathBuf>,
},
2026-05-21 18:01:38 +02:00
NodeEnrollSubmit {
owner_node: String,
request_id: Option<String>,
path: Option<PathBuf>,
},
NodeEnrollImport {
path: PathBuf,
},
NodeEnrollList {
status: Option<String>,
},
NodeEnrollApprove {
request_id: String,
signing_key_path: PathBuf,
admin_key_path: Option<PathBuf>,
node_name: Option<String>,
capabilities: Vec<String>,
},
NodeEnrollSync {
owner_node: String,
},
KeychainInit {
admin_key_path: Option<PathBuf>,
2026-05-19 16:04:20 +02:00
signing_key_path: Option<PathBuf>,
},
2026-05-15 15:08:20 +02:00
KeychainStatus,
KeychainAdminAdd {
admin_key_path: PathBuf,
signing_key_path: PathBuf,
principal: Option<String>,
valid_after_ms: Option<i64>,
valid_before_ms: Option<i64>,
},
KeychainAdminRevoke {
key: String,
signing_key_path: PathBuf,
admin_key_path: Option<PathBuf>,
},
KeychainAllowedSigners {
out: Option<PathBuf>,
},
KeychainSignFile {
input: PathBuf,
out: Option<PathBuf>,
namespace: Option<String>,
signing_key_path: Option<PathBuf>,
admin_key_path: Option<PathBuf>,
},
KeychainVerifyFile {
input: PathBuf,
signature: PathBuf,
namespace: Option<String>,
allowed_signers_path: Option<PathBuf>,
principal: Option<String>,
},
KeychainVerifySigchain {
input: PathBuf,
chain_id: String,
root_key_path: PathBuf,
namespace: Option<String>,
},
KeychainExplain {
op_id: String,
},
KeychainExplainSigner {
key: String,
},
KeychainVerify,
2026-05-21 11:29:29 +02:00
KeychainSync {
node: String,
},
2026-05-21 18:01:38 +02:00
AuthSync {
node: String,
},
SyncStatus,
SyncNow {
node: Option<String>,
},
2026-05-16 22:18:49 +02:00
SecretStatus,
SecretCreate {
resource: String,
},
SecretRotate {
resource: String,
},
SecretBearerCreate {
resource: String,
capabilities: Vec<String>,
expires_at_ms: Option<i64>,
},
SecretBearerList,
2026-05-19 19:08:08 +02:00
SecretBearerChallenge {
resource: String,
capabilities: Vec<String>,
},
SecretBearerProve {
secret: String,
resource: String,
capabilities: Vec<String>,
nonce: String,
},
SecretBearerVerify {
secret: String,
resource: String,
capabilities: Vec<String>,
nonce: String,
response: String,
},
SecretBearerRevoke {
resource: String,
secret: String,
},
2026-05-15 15:08:20 +02:00
AuthExplain {
subject: String,
resource: String,
capability: String,
},
2026-05-16 16:32:03 +02:00
AuthGrant {
subject: String,
resource: String,
capability: String,
grant_id: Option<String>,
2026-05-21 18:15:10 +02:00
signing_key_path: Option<PathBuf>,
admin_key_path: Option<PathBuf>,
2026-05-16 16:32:03 +02:00
},
AuthRevoke {
resource: String,
grant_id: String,
2026-05-21 18:15:10 +02:00
signing_key_path: Option<PathBuf>,
admin_key_path: Option<PathBuf>,
2026-05-16 16:32:03 +02:00
},
SshCertRequest {
public_key_path: PathBuf,
cert_kind: String,
principals: Vec<String>,
requested_validity: Option<String>,
renewal_of: Option<String>,
reason: Option<String>,
subject: Option<String>,
},
SshCertRequests {
subject: Option<String>,
},
SshCertApprove {
request_id: String,
ca_key_path: PathBuf,
valid_for: Option<String>,
serial: Option<u64>,
out: Option<PathBuf>,
2026-05-19 15:56:47 +02:00
sign: bool,
subject: Option<String>,
},
SshCertImport {
request_id: String,
cert_path: PathBuf,
subject: Option<String>,
},
SshCertList {
subject: Option<String>,
},
2026-05-18 17:24:10 +02:00
SshCertSync {
node: String,
bearer_secret: Option<String>,
2026-05-18 17:24:10 +02:00
},
SshRevocationAdd {
kind: String,
target: String,
reason: Option<String>,
subject: Option<String>,
},
SshRevocationList {
subject: Option<String>,
},
SshRevocationExport {
out: PathBuf,
2026-05-17 18:29:47 +02:00
format: String,
2026-05-18 11:51:12 +02:00
ca_public: Option<PathBuf>,
subject: Option<String>,
},
2026-05-18 11:56:42 +02:00
SshRevocationImport {
path: PathBuf,
format: String,
subject: Option<String>,
2026-05-18 11:56:42 +02:00
},
2026-05-18 17:24:10 +02:00
SshRevocationSync {
node: String,
bearer_secret: Option<String>,
2026-05-18 17:24:10 +02:00
},
2026-05-19 15:51:11 +02:00
SshProxyConnect {
node: String,
bearer_secret: Option<String>,
2026-05-19 15:51:11 +02:00
},
2026-05-21 01:03:38 +02:00
SshProxyStream {
node: String,
bearer_secret: Option<String>,
},
2026-05-21 01:49:48 +02:00
SshAdminShell {
node: String,
command: String,
bearer_secret: Option<String>,
},
2026-05-16 21:13:33 +02:00
DbAdd {
name: String,
path: PathBuf,
},
DbStatus {
name: String,
},
2026-05-17 20:32:36 +02:00
DbChanges {
name: String,
after_db_version: Option<i64>,
limit: u32,
},
2026-05-18 22:11:57 +02:00
DbSync {
node: String,
name: String,
limit: u32,
bearer_secret: Option<String>,
2026-05-18 22:11:57 +02:00
},
2026-05-16 21:52:35 +02:00
KvCreate {
name: String,
},
KvSet {
name: String,
key: String,
value: String,
2026-05-18 04:06:41 +02:00
subject: Option<String>,
2026-05-16 21:52:35 +02:00
},
KvGet {
name: String,
key: String,
},
2026-05-18 18:36:03 +02:00
KvSync {
node: String,
name: String,
bearer_secret: Option<String>,
2026-05-18 18:36:03 +02:00
},
2026-05-16 22:15:18 +02:00
DocumentCreate {
name: String,
},
DocumentStatus {
name: String,
},
2026-05-17 19:59:03 +02:00
DocumentSet {
name: String,
state_json: String,
},
DocumentGet {
name: String,
},
2026-05-18 18:49:34 +02:00
DocumentSync {
node: String,
name: String,
bearer_secret: Option<String>,
2026-05-18 18:49:34 +02:00
},
2026-05-17 18:23:51 +02:00
PubsubPub {
topic: String,
message: String,
2026-05-18 18:41:04 +02:00
node: Option<String>,
bearer_secret: Option<String>,
2026-05-17 18:23:51 +02:00
},
PubsubSub {
topic: String,
2026-05-19 15:28:48 +02:00
node: Option<String>,
bearer_secret: Option<String>,
2026-05-17 18:23:51 +02:00
},
2026-05-17 20:17:26 +02:00
PipeListen {
name: String,
2026-05-19 19:21:42 +02:00
node: Option<String>,
bearer_secret: Option<String>,
2026-05-17 20:17:26 +02:00
},
PipeConnect {
target: String,
2026-05-18 18:45:10 +02:00
node: Option<String>,
bearer_secret: Option<String>,
2026-05-17 20:17:26 +02:00
},
2026-05-21 01:12:01 +02:00
PipeTcpForward {
node: String,
listen_addr: String,
target_addr: String,
bearer_secret: Option<String>,
},
PipeTcpStream {
node: String,
target_addr: String,
bearer_secret: Option<String>,
},
2026-05-21 01:15:51 +02:00
PipeUnixForward {
node: String,
listen_path: PathBuf,
target_path: PathBuf,
bearer_secret: Option<String>,
},
PipeUnixStream {
node: String,
target_path: PathBuf,
bearer_secret: Option<String>,
},
2026-05-20 13:57:14 +02:00
PipeSend {
target: String,
data_base64: String,
node: Option<String>,
bearer_secret: Option<String>,
},
PipeRecv {
name: String,
peek: bool,
},
2026-05-15 15:08:20 +02:00
ModuleStub {
module: String,
command: String,
},
}
2026-05-17 20:32:36 +02:00
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2026-05-15 15:08:20 +02:00
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ControlResponse {
Status(StatusResponse),
NodeId(NodeIdResponse),
2026-05-18 04:03:52 +02:00
PeerCardExported {
card: PeerCard,
out: Option<PathBuf>,
note: String,
},
PeerCardImported {
peer: DiscoveredPeer,
note: String,
},
PeerCardList {
peers: Vec<DiscoveredPeer>,
note: String,
},
2026-05-18 12:09:50 +02:00
PeerPinged {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
alpn: String,
note: String,
},
2026-05-18 17:01:50 +02:00
PeerAuthChecked {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
resource: String,
capability: String,
allowed: bool,
reason: String,
evaluated_ops: usize,
note: String,
},
2026-05-15 15:08:20 +02:00
ResourceList {
resources: Vec<ResourceDescriptor>,
},
ResourceCreated {
resource: ResourceDescriptor,
},
2026-05-23 01:17:30 +02:00
OverlayStatus {
networks: Vec<OverlayNetworkStatus>,
note: String,
},
OverlayPlanned {
plan: OverlayPlan,
},
OverlayJoined {
join: OverlayJoinPlan,
},
OverlayLeft {
name: String,
stopped: bool,
note: String,
},
2026-05-23 02:08:51 +02:00
OverlayInterfacePlanned {
plan: OverlayInterfacePlan,
},
OverlayRuntimeStarted {
status: OverlayRuntimeStatus,
},
OverlayRuntimeStopped {
name: String,
stopped: bool,
note: String,
},
OverlayPeers {
name: String,
peers: Vec<OverlayPeer>,
note: String,
},
OverlayPacketSent {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
packet: Option<OverlayPacket>,
allowed: bool,
reason: String,
note: String,
},
OverlayPackets {
name: String,
packets: Vec<OverlayPacket>,
drained: bool,
note: String,
},
2026-05-15 15:08:20 +02:00
CasAdded {
hash: BlobHash,
size_bytes: u64,
},
2026-05-21 01:35:00 +02:00
CasPrivateAdded {
resource: String,
epoch: u64,
plaintext_hash: BlobHash,
encrypted_hash: BlobHash,
size_bytes: u64,
note: String,
},
2026-05-15 15:08:20 +02:00
CasGot {
hash: BlobHash,
out: PathBuf,
size_bytes: u64,
},
2026-05-21 01:35:00 +02:00
CasPrivateGot {
resource: String,
hash: BlobHash,
plaintext_hash: BlobHash,
out: PathBuf,
size_bytes: u64,
note: String,
},
2026-05-18 17:18:25 +02:00
CasFetched {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
hash: BlobHash,
size_bytes: u64,
allowed: bool,
reason: String,
note: String,
},
2026-05-15 15:08:20 +02:00
CasHash {
hash: BlobHash,
},
CasHas {
hash: BlobHash,
present: bool,
},
2026-05-16 16:36:35 +02:00
CasPinned {
hash: BlobHash,
pinned: bool,
},
2026-05-16 21:10:25 +02:00
CasCleanup {
removed: Vec<BlobHash>,
retained_pinned: Vec<BlobHash>,
dry_run: bool,
},
2026-05-15 15:08:20 +02:00
CasList {
blobs: Vec<CasBlob>,
},
2026-05-19 15:37:02 +02:00
CasProviders {
hash: BlobHash,
providers: Vec<CasProvider>,
},
2026-05-18 03:50:09 +02:00
CasRootAdded {
root: FileRoot,
},
CasRootList {
roots: Vec<FileRoot>,
},
CasRootScanned {
scan: FileRootScan,
},
2026-05-20 13:22:40 +02:00
CasRootSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
name: String,
root: Option<FileRoot>,
tree_bytes_imported: bool,
2026-05-21 01:40:50 +02:00
sync_conflicts: Vec<FileConflict>,
2026-05-20 13:22:40 +02:00
allowed: bool,
reason: String,
note: String,
},
2026-05-20 13:30:57 +02:00
CasRootApplied {
source: String,
target: PathBuf,
files_written: usize,
dirs_created: usize,
conflicts: Vec<FileConflict>,
dry_run: bool,
note: String,
},
2026-05-18 03:57:26 +02:00
CasConflictRecorded {
conflict: FileConflict,
},
CasConflictList {
conflicts: Vec<FileConflict>,
},
CasConflictResolved {
conflict: FileConflict,
},
2026-05-15 15:08:20 +02:00
KeychainStatus(KeychainStatusResponse),
KeychainInitialized {
ops: Vec<KeychainOp>,
2026-05-19 16:04:20 +02:00
signatures: Vec<KeychainOpSignature>,
},
KeychainAdminUpdated {
op: KeychainOp,
signatures: Vec<KeychainOpSignature>,
note: String,
},
KeychainAllowedSigners {
entries: Vec<KeychainAllowedSigner>,
allowed_signers: String,
out: Option<PathBuf>,
note: String,
},
KeychainFileSigned {
input: PathBuf,
out: Option<PathBuf>,
namespace: String,
signer: String,
note: String,
},
KeychainFileVerified {
input: PathBuf,
signature: PathBuf,
namespace: String,
verified: bool,
principal: Option<String>,
note: String,
},
KeychainSigchainVerified {
input: PathBuf,
chain_id: String,
records: usize,
head: String,
active_keys: usize,
devices: usize,
disclosed_profiles: usize,
incomplete_profiles: usize,
anchor_attester_threshold: u16,
anchor_backend_threshold: u16,
required_anchor_backends: usize,
required_anchor_classes: usize,
note: String,
},
KeychainExplained {
subject: String,
lines: Vec<String>,
},
KeychainVerified {
report: KeychainSigchainReport,
},
2026-05-21 11:29:29 +02:00
KeychainSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
ops_imported: usize,
signatures_imported: usize,
invalid_ops_rejected: usize,
high_water_ms: i64,
2026-05-21 11:29:29 +02:00
note: String,
},
2026-05-21 18:01:38 +02:00
AuthSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
ops_imported: usize,
signatures_imported: usize,
invalid_ops_rejected: usize,
high_water_ms: i64,
note: String,
},
SyncStatus {
peers: Vec<SyncPeerStatus>,
note: String,
},
SyncRan {
peers: Vec<SyncPeerRun>,
2026-05-21 18:01:38 +02:00
note: String,
},
2026-05-16 22:18:49 +02:00
SecretStatus {
secrets: Vec<ResourceMasterSecret>,
},
SecretCreated {
secret: ResourceMasterSecret,
},
SecretBearerCreated {
access: BearerAccess,
},
SecretBearerList {
access: Vec<BearerAccess>,
},
2026-05-19 19:08:08 +02:00
SecretBearerChallenge {
challenge: BearerChallenge,
},
SecretBearerProof {
proof: BearerProof,
},
SecretBearerVerified {
secret: String,
resource: String,
capabilities: Vec<String>,
verified: bool,
reason: String,
},
SecretBearerRevoked {
resource: String,
secret: String,
},
2026-05-15 15:08:20 +02:00
AuthExplain(AuthExplanation),
2026-05-16 16:32:03 +02:00
AuthOpRecorded {
op: AuthOp,
2026-05-21 18:15:10 +02:00
signatures: Vec<AuthOpSignature>,
2026-05-16 16:32:03 +02:00
},
2026-05-21 11:29:29 +02:00
NodeList {
nodes: Vec<NodeRecord>,
note: String,
},
NodeKeychainUpdated {
ops: Vec<KeychainOp>,
signatures: Vec<KeychainOpSignature>,
note: String,
},
NodeGrantUpdated {
op: AuthOp,
2026-05-21 18:01:38 +02:00
signatures: Vec<AuthOpSignature>,
note: String,
},
NodeEnrollmentRequested {
request: NodeEnrollmentRequest,
out: Option<PathBuf>,
note: String,
},
2026-07-18 16:48:35 +02:00
NodeEnrollmentJoined {
peer: DiscoveredPeer,
request: NodeEnrollmentRequest,
out: Option<PathBuf>,
owner_node_id: String,
admin_key_id: String,
accepted: bool,
note: String,
},
2026-05-21 18:01:38 +02:00
NodeEnrollmentSubmitted {
request_id: String,
owner_node_id: String,
accepted: bool,
note: String,
},
NodeEnrollmentImported {
request: NodeEnrollmentRequest,
note: String,
},
NodeEnrollmentList {
requests: Vec<NodeEnrollmentRequest>,
note: String,
},
NodeEnrollmentApproved {
request: NodeEnrollmentRequest,
keychain_ops: Vec<KeychainOp>,
keychain_signatures: Vec<KeychainOpSignature>,
auth_ops: Vec<AuthOp>,
auth_signatures: Vec<AuthOpSignature>,
note: String,
},
NodeEnrollmentSynced {
owner_node: String,
keychain_ops_imported: usize,
keychain_signatures_imported: usize,
auth_ops_imported: usize,
auth_signatures_imported: usize,
invalid_ops_rejected: usize,
2026-05-21 11:29:29 +02:00
note: String,
},
SshCertRequested {
request: SshCertRequest,
},
SshCertRequests {
requests: Vec<SshCertRequest>,
},
SshCertApproved {
approval: SshCertApproval,
},
SshCertImported {
certificate: SshCertificateRecord,
},
SshCertList {
requests: Vec<SshCertRequest>,
certificates: Vec<SshCertificateRecord>,
},
2026-05-18 17:24:10 +02:00
SshCertSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
requests_imported: usize,
certificates_imported: usize,
allowed: bool,
reason: String,
note: String,
},
SshRevocationAdded {
revocation: SshRevocationEntry,
},
SshRevocationList {
revocations: Vec<SshRevocationEntry>,
},
SshRevocationExported {
out: PathBuf,
2026-05-17 18:29:47 +02:00
format: String,
count: usize,
2026-05-17 18:29:47 +02:00
note: String,
},
2026-05-18 11:56:42 +02:00
SshRevocationImported {
revocations: Vec<SshRevocationEntry>,
format: String,
count: usize,
note: String,
},
2026-05-18 17:24:10 +02:00
SshRevocationSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
revocations_imported: usize,
allowed: bool,
reason: String,
note: String,
},
2026-05-16 21:13:33 +02:00
DbAdded {
db: DbResource,
},
DbStatus {
db: DbResource,
},
2026-05-17 20:32:36 +02:00
DbChanges {
db: DbResource,
batch: CrSqliteChangeBatch,
},
2026-05-18 22:11:57 +02:00
DbSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
name: String,
changes_received: usize,
2026-05-19 19:02:39 +02:00
changes_applied: usize,
2026-05-18 22:11:57 +02:00
max_db_version: Option<i64>,
schema_match: bool,
allowed: bool,
reason: String,
note: String,
},
2026-05-16 21:52:35 +02:00
KvCreated {
kv: KvResource,
},
KvSet {
entry: KvEntry,
},
KvGet {
entry: Option<KvEntry>,
},
2026-05-18 18:36:03 +02:00
KvSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
name: String,
entries_imported: usize,
allowed: bool,
reason: String,
note: String,
},
2026-05-16 22:15:18 +02:00
DocumentCreated {
document: DocumentResource,
},
DocumentStatus {
document: DocumentResource,
},
2026-05-17 19:59:03 +02:00
DocumentSet {
state: DocumentState,
},
DocumentGet {
state: DocumentState,
},
2026-05-18 18:49:34 +02:00
DocumentSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
name: String,
updated: bool,
allowed: bool,
reason: String,
note: String,
},
2026-05-17 18:23:51 +02:00
PubsubPublished {
message: PubsubMessage,
},
2026-05-18 18:41:04 +02:00
PubsubRemotePublished {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
message: PubsubMessage,
allowed: bool,
reason: String,
note: String,
},
2026-05-17 18:23:51 +02:00
PubsubMessages {
topic: String,
messages: Vec<PubsubMessage>,
note: String,
},
2026-05-19 15:28:48 +02:00
PubsubRemoteMessages {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
topic: String,
messages: Vec<PubsubMessage>,
allowed: bool,
reason: String,
note: String,
},
2026-05-17 20:17:26 +02:00
PipeListening {
listener: PipeListener,
},
2026-05-19 19:21:42 +02:00
PipeRemoteListening {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
listener: Option<PipeListener>,
allowed: bool,
reason: String,
note: String,
},
2026-05-17 20:17:26 +02:00
PipeConnected {
connection: PipeConnection,
},
2026-05-18 18:45:10 +02:00
PipeRemoteConnected {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
connection: PipeConnection,
allowed: bool,
reason: String,
note: String,
},
2026-05-20 13:57:14 +02:00
PipeSent {
message: Option<PipeMessage>,
listener_found: bool,
note: String,
},
PipeRemoteSent {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
message: Option<PipeMessage>,
listener_found: bool,
allowed: bool,
reason: String,
note: String,
},
PipeMessages {
name: String,
messages: Vec<PipeMessage>,
drained: bool,
note: String,
},
2026-05-19 15:51:11 +02:00
SshProxyConnected {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
connection: Option<SshProxyConnection>,
allowed: bool,
reason: String,
note: String,
},
2026-05-21 01:49:48 +02:00
SshAdminShellOutput {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
command: String,
output: String,
allowed: bool,
reason: String,
note: String,
},
2026-05-15 15:08:20 +02:00
NotImplemented {
module: String,
command: String,
},
Error {
message: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StatusResponse {
pub home: PathBuf,
pub socket: PathBuf,
pub agent_id: String,
pub node_id: String,
2026-07-05 18:14:52 +02:00
pub daemon_started_at_ms: i64,
pub daemon_uptime_seconds: u64,
2026-07-05 18:10:41 +02:00
pub store_schema_version: i64,
pub store_current_schema_version: i64,
pub store_journal_mode: String,
pub store_synchronous: String,
pub store_status: String,
pub store_note: String,
2026-05-16 01:54:00 +02:00
pub iroh_enabled: bool,
pub endpoint_id: Option<String>,
2026-05-16 03:17:45 +02:00
pub iroh_relay_mode: String,
2026-05-16 14:33:45 +02:00
pub iroh_local_discovery: bool,
2026-05-15 15:08:20 +02:00
pub iroh: String,
2026-05-22 14:33:46 +02:00
#[serde(default)]
pub native_backends: Vec<NativeBackendStatus>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct NativeBackendStatus {
pub module: String,
pub current_backend: String,
pub target_crate: String,
pub target_version: String,
pub status: String,
pub blocker: String,
2026-05-15 15:08:20 +02:00
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct NodeIdResponse {
pub agent_id: String,
pub node_id: String,
pub endpoint_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeychainStatusResponse {
pub initialized: bool,
pub admin_keys: usize,
2026-05-19 16:05:57 +02:00
pub signatures: usize,
2026-05-19 18:58:07 +02:00
pub verified_signatures: usize,
pub failed_signatures: usize,
2026-05-15 15:08:20 +02:00
pub users: usize,
pub devices: usize,
pub nodes: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CasBlob {
pub hash: BlobHash,
pub size_bytes: u64,
2026-05-16 16:36:35 +02:00
pub pinned: bool,
2026-05-15 15:08:20 +02:00
}
2026-05-19 15:37:02 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CasProvider {
pub peer_node_id: String,
pub endpoint_id: String,
pub last_seen_ms: i64,
}
2026-05-23 02:08:51 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OverlayPeer {
pub node_id: String,
pub endpoint_id: Option<String>,
pub virtual_ip: Option<String>,
pub state: String,
}
2026-05-18 12:09:50 +02:00
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PeerControlRequest {
2026-05-18 17:01:50 +02:00
Ping {
peer_card: PeerCard,
nonce: String,
},
AuthCheck {
peer_card: PeerCard,
resource: String,
capability: String,
nonce: String,
},
2026-05-18 22:17:27 +02:00
SyncStatus {
peer_card: PeerCard,
nonce: String,
},
2026-05-21 11:29:29 +02:00
KeychainSync {
peer_card: PeerCard,
since_ms: i64,
2026-05-21 11:29:29 +02:00
nonce: String,
},
2026-05-21 18:01:38 +02:00
AuthSync {
peer_card: PeerCard,
since_ms: i64,
2026-05-21 18:01:38 +02:00
nonce: String,
},
NodeEnrollmentSubmit {
peer_card: PeerCard,
request: NodeEnrollmentRequest,
nonce: String,
},
2026-05-18 17:18:25 +02:00
CasFetch {
peer_card: PeerCard,
hash: BlobHash,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 17:18:25 +02:00
},
2026-05-20 13:22:40 +02:00
CasRootSync {
peer_card: PeerCard,
name: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-18 17:24:10 +02:00
SshCertSync {
peer_card: PeerCard,
2026-05-18 18:29:45 +02:00
since_ms: i64,
2026-05-18 17:24:10 +02:00
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 17:24:10 +02:00
},
SshRevocationSync {
peer_card: PeerCard,
2026-05-18 18:29:45 +02:00
since_ms: i64,
2026-05-18 17:24:10 +02:00
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 17:24:10 +02:00
},
2026-05-18 18:36:03 +02:00
KvSync {
peer_card: PeerCard,
name: String,
since_ms: i64,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 18:36:03 +02:00
},
2026-05-18 18:41:04 +02:00
PubsubPublish {
peer_card: PeerCard,
topic: String,
message: String,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 18:41:04 +02:00
},
2026-05-19 15:28:48 +02:00
PubsubSubscribe {
peer_card: PeerCard,
topic: String,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-19 15:28:48 +02:00
},
2026-05-18 18:45:10 +02:00
PipeConnect {
peer_card: PeerCard,
target: String,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 18:45:10 +02:00
},
2026-05-19 19:21:42 +02:00
PipeListen {
peer_card: PeerCard,
name: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-19 15:51:11 +02:00
SshProxyConnect {
peer_card: PeerCard,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-19 15:51:11 +02:00
},
2026-05-21 01:49:48 +02:00
SshAdminShell {
peer_card: PeerCard,
command: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-18 18:49:34 +02:00
DocumentSync {
peer_card: PeerCard,
name: String,
since_ms: i64,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 18:49:34 +02:00
},
2026-05-18 22:11:57 +02:00
DbSync {
peer_card: PeerCard,
name: String,
after_db_version: Option<i64>,
limit: u32,
nonce: String,
bearer_proof: Option<BearerProof>,
2026-05-18 22:11:57 +02:00
},
2026-05-18 12:09:50 +02:00
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PeerControlResponse {
Pong {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
alpn: String,
nonce: String,
note: String,
},
2026-05-18 17:01:50 +02:00
AuthChecked {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
resource: String,
capability: String,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 22:17:27 +02:00
SyncStatus {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
watermarks: Vec<SyncWatermark>,
nonce: String,
note: String,
},
2026-05-21 11:29:29 +02:00
KeychainSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
ops: Vec<KeychainOp>,
signatures: Vec<KeychainOpSignature>,
high_water_ms: i64,
2026-05-21 11:29:29 +02:00
nonce: String,
note: String,
},
2026-05-21 18:01:38 +02:00
AuthSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
ops: Vec<AuthOp>,
signatures: Vec<AuthOpSignature>,
high_water_ms: i64,
2026-05-21 18:01:38 +02:00
nonce: String,
note: String,
},
NodeEnrollmentSubmitted {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
request_id: String,
accepted: bool,
nonce: String,
note: String,
},
2026-05-18 17:18:25 +02:00
CasFetched {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
hash: BlobHash,
size_bytes: u64,
content_base64: Option<String>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-20 13:22:40 +02:00
CasRootSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
name: String,
root: Option<FileRoot>,
tree_content_base64: Option<String>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 17:24:10 +02:00
SshCertSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
requests: Vec<SshCertRequest>,
certificates: Vec<SshCertificateRecord>,
log_entries: Vec<SshDistributionLogEntry>,
2026-05-18 18:29:45 +02:00
high_water_ms: i64,
2026-05-18 17:24:10 +02:00
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
SshRevocationSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
revocations: Vec<SshRevocationEntry>,
log_entries: Vec<SshDistributionLogEntry>,
2026-05-18 18:29:45 +02:00
high_water_ms: i64,
2026-05-18 17:24:10 +02:00
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 18:36:03 +02:00
KvSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
name: String,
entries: Vec<KvSyncEntry>,
high_water_ms: i64,
2026-05-22 16:24:11 +02:00
docs_ticket: Option<String>,
2026-05-18 18:36:03 +02:00
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 18:41:04 +02:00
PubsubPublished {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
message: Option<PubsubMessage>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-19 15:28:48 +02:00
PubsubSubscribed {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
topic: String,
messages: Vec<PubsubMessage>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 18:45:10 +02:00
PipeConnected {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
connection: Option<PipeConnection>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-19 19:21:42 +02:00
PipeListening {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
listener: Option<PipeListener>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-19 15:51:11 +02:00
SshProxyConnected {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
connection: Option<SshProxyConnection>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-21 01:49:48 +02:00
SshAdminShellOutput {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
command: String,
output: String,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 18:49:34 +02:00
DocumentSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
name: String,
state: Option<DocumentState>,
high_water_ms: i64,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 22:11:57 +02:00
DbSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
name: String,
batch: Option<CrSqliteChangeBatch>,
high_water_db_version: Option<i64>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-18 12:09:50 +02:00
Error {
message: String,
},
}
2026-05-20 13:57:14 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PipeWireRequest {
Send {
peer_card: PeerCard,
target: String,
data_base64: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-21 01:12:01 +02:00
TcpConnect {
peer_card: PeerCard,
target_addr: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-21 01:15:51 +02:00
UnixConnect {
peer_card: PeerCard,
target_path: PathBuf,
nonce: String,
bearer_proof: Option<BearerProof>,
},
2026-05-20 13:57:14 +02:00
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PipeWireResponse {
Sent {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
message: Box<Option<PipeMessage>>,
listener_found: bool,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-21 01:12:01 +02:00
Connected {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
connection: Option<PipeConnection>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
2026-05-20 13:57:14 +02:00
Error {
message: String,
},
}
2026-05-23 02:08:51 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum OverlayWireRequest {
Packet {
peer_card: PeerCard,
network: String,
packet_base64: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum OverlayWireResponse {
PacketAccepted {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
packet: Option<Box<OverlayPacket>>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
Error {
message: String,
},
}
2026-05-18 22:17:27 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncWatermark {
pub stream: String,
pub high_water: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncPeerStatus {
pub peer_node_id: String,
pub streams: Vec<SyncStreamStatus>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncStreamStatus {
pub stream: String,
pub cursor_ms: i64,
2026-05-22 15:00:09 +02:00
pub state: String,
pub stale: bool,
pub stale_after_ms: i64,
pub last_attempt_ms: Option<i64>,
pub last_success_ms: Option<i64>,
pub last_error: Option<String>,
pub last_imported: usize,
pub last_rejected: usize,
2026-07-05 22:57:52 +02:00
#[serde(default)]
pub consecutive_failures: u32,
#[serde(default)]
pub retry_after_ms: Option<i64>,
#[serde(default)]
pub retry_in_ms: Option<i64>,
2026-05-22 15:00:09 +02:00
pub next_action: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncPeerRun {
pub peer_node_id: String,
pub streams: Vec<SyncStreamRun>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncStreamRun {
pub stream: String,
pub attempted: bool,
pub success: bool,
pub imported: usize,
pub rejected: usize,
pub cursor_ms: i64,
pub error: Option<String>,
}
2026-05-15 15:08:20 +02:00
#[derive(Debug, thiserror::Error)]
pub enum ControlError {
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
}
pub fn encode_request(request: &ControlRequest) -> Result<String, ControlError> {
let mut line = serde_json::to_string(request)?;
line.push('\n');
Ok(line)
}
pub fn decode_request(line: &str) -> Result<ControlRequest, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_response(response: &ControlResponse) -> Result<String, ControlError> {
let mut line = serde_json::to_string(response)?;
line.push('\n');
Ok(line)
}
pub fn decode_response(line: &str) -> Result<ControlResponse, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
2026-05-18 12:09:50 +02:00
pub fn encode_peer_request(request: &PeerControlRequest) -> Result<String, ControlError> {
let mut line = serde_json::to_string(request)?;
line.push('\n');
Ok(line)
}
pub fn decode_peer_request(line: &str) -> Result<PeerControlRequest, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_peer_response(response: &PeerControlResponse) -> Result<String, ControlError> {
let mut line = serde_json::to_string(response)?;
line.push('\n');
Ok(line)
}
pub fn decode_peer_response(line: &str) -> Result<PeerControlResponse, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
2026-05-20 13:57:14 +02:00
pub fn encode_pipe_wire_request(request: &PipeWireRequest) -> Result<String, ControlError> {
let mut line = serde_json::to_string(request)?;
line.push('\n');
Ok(line)
}
pub fn decode_pipe_wire_request(line: &str) -> Result<PipeWireRequest, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_pipe_wire_response(response: &PipeWireResponse) -> Result<String, ControlError> {
let mut line = serde_json::to_string(response)?;
line.push('\n');
Ok(line)
}
pub fn decode_pipe_wire_response(line: &str) -> Result<PipeWireResponse, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
2026-05-23 02:08:51 +02:00
pub fn encode_overlay_wire_request(request: &OverlayWireRequest) -> Result<String, ControlError> {
let mut line = serde_json::to_string(request)?;
line.push('\n');
Ok(line)
}
pub fn decode_overlay_wire_request(line: &str) -> Result<OverlayWireRequest, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_overlay_wire_response(
response: &OverlayWireResponse,
) -> Result<String, ControlError> {
let mut line = serde_json::to_string(response)?;
line.push('\n');
Ok(line)
}
pub fn decode_overlay_wire_response(line: &str) -> Result<OverlayWireResponse, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
2026-05-15 15:08:20 +02:00
#[cfg(test)]
mod tests {
use super::*;
2026-07-05 22:30:15 +02:00
use serde_json::{Map, Value, json};
const CONTROL_REQUEST_VARIANTS: usize = 118;
const CONTROL_RESPONSE_VARIANTS: usize = 110;
2026-07-05 22:30:15 +02:00
const PEER_CONTROL_REQUEST_VARIANTS: usize = 19;
const PEER_CONTROL_RESPONSE_VARIANTS: usize = 20;
const PIPE_WIRE_REQUEST_VARIANTS: usize = 3;
const PIPE_WIRE_RESPONSE_VARIANTS: usize = 3;
const OVERLAY_WIRE_REQUEST_VARIANTS: usize = 1;
const OVERLAY_WIRE_RESPONSE_VARIANTS: usize = 2;
#[test]
fn all_protocol_variants_have_roundtrip_samples() {
roundtrip_declared_variants::<ControlRequest>(
"ControlRequest",
CONTROL_REQUEST_VARIANTS,
decode_request,
encode_request,
);
roundtrip_declared_variants::<ControlResponse>(
"ControlResponse",
CONTROL_RESPONSE_VARIANTS,
decode_response,
encode_response,
);
roundtrip_declared_variants::<PeerControlRequest>(
"PeerControlRequest",
PEER_CONTROL_REQUEST_VARIANTS,
decode_peer_request,
encode_peer_request,
);
roundtrip_declared_variants::<PeerControlResponse>(
"PeerControlResponse",
PEER_CONTROL_RESPONSE_VARIANTS,
decode_peer_response,
encode_peer_response,
);
roundtrip_declared_variants::<PipeWireRequest>(
"PipeWireRequest",
PIPE_WIRE_REQUEST_VARIANTS,
decode_pipe_wire_request,
encode_pipe_wire_request,
);
roundtrip_declared_variants::<PipeWireResponse>(
"PipeWireResponse",
PIPE_WIRE_RESPONSE_VARIANTS,
decode_pipe_wire_response,
encode_pipe_wire_response,
);
roundtrip_declared_variants::<OverlayWireRequest>(
"OverlayWireRequest",
OVERLAY_WIRE_REQUEST_VARIANTS,
decode_overlay_wire_request,
encode_overlay_wire_request,
);
roundtrip_declared_variants::<OverlayWireResponse>(
"OverlayWireResponse",
OVERLAY_WIRE_RESPONSE_VARIANTS,
decode_overlay_wire_response,
encode_overlay_wire_response,
);
}
fn roundtrip_declared_variants<T>(
enum_name: &str,
expected_count: usize,
decode: fn(&str) -> Result<T, ControlError>,
encode: fn(&T) -> Result<String, ControlError>,
) where
T: std::fmt::Debug + PartialEq,
{
let variants = declared_enum_variants(enum_name);
assert_eq!(variants.len(), expected_count, "{enum_name} variant count");
for variant in variants {
let sample = protocol_sample(&variant);
let encoded_sample = serde_json::to_string(&sample).expect("sample json");
let decoded = decode(&encoded_sample).unwrap_or_else(|err| {
panic!("{enum_name}::{} sample decodes: {err}", variant.name)
});
let encoded = encode(&decoded).unwrap_or_else(|err| {
panic!("{enum_name}::{} sample encodes: {err}", variant.name)
});
let decoded_again = decode(&encoded).unwrap_or_else(|err| {
panic!(
"{enum_name}::{} encoded sample decodes: {err}",
variant.name
)
});
assert_eq!(
decoded_again, decoded,
"{enum_name}::{} roundtrips through line codec",
variant.name
);
}
}
#[derive(Debug)]
struct EnumVariant {
name: String,
body: VariantBody,
}
#[derive(Debug)]
enum VariantBody {
Unit,
Tuple(String),
Struct(Vec<(String, String)>),
}
fn declared_enum_variants(enum_name: &str) -> Vec<EnumVariant> {
let source = include_str!("lib.rs");
let needle = format!("pub enum {enum_name}");
let start = source.find(&needle).expect("enum exists");
let after_name = &source[start + needle.len()..];
let open = after_name.find('{').expect("enum opens");
let block = enum_block(&after_name[open..]);
split_top_level(block)
.into_iter()
.filter_map(|decl| parse_variant_decl(decl.trim()))
.collect()
}
fn enum_block(input: &str) -> &str {
let mut depth = 0_i32;
let mut start = None;
for (idx, ch) in input.char_indices() {
match ch {
'{' => {
depth += 1;
if start.is_none() {
start = Some(idx + 1);
}
}
'}' => {
depth -= 1;
if depth == 0 {
return &input[start.expect("block start")..idx];
}
}
_ => {}
}
}
panic!("unterminated enum block");
}
fn split_top_level(input: &str) -> Vec<&str> {
let mut parts = Vec::new();
let mut start = 0;
let mut brace_depth = 0_i32;
let mut paren_depth = 0_i32;
let mut angle_depth = 0_i32;
for (idx, ch) in input.char_indices() {
match ch {
'{' => brace_depth += 1,
'}' => brace_depth -= 1,
'(' => paren_depth += 1,
')' => paren_depth -= 1,
'<' => angle_depth += 1,
'>' => angle_depth -= 1,
',' if brace_depth == 0 && paren_depth == 0 && angle_depth == 0 => {
parts.push(&input[start..idx]);
start = idx + 1;
}
_ => {}
}
}
if start < input.len() {
parts.push(&input[start..]);
}
parts
}
fn parse_variant_decl(decl: &str) -> Option<EnumVariant> {
let decl = decl.trim();
if decl.is_empty() || decl.starts_with("#[") {
return None;
}
let name_len = decl
.find(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '_'))
.unwrap_or(decl.len());
let name = decl[..name_len].to_owned();
let rest = decl[name_len..].trim();
let body = if rest.starts_with('{') {
let fields = split_top_level(enum_block(rest))
.into_iter()
.filter_map(|field| {
let (name, ty) = field.trim().split_once(':')?;
Some((name.trim().to_owned(), ty.trim().to_owned()))
})
.collect();
VariantBody::Struct(fields)
} else if let Some(tuple) = rest
.strip_prefix('(')
.and_then(|rest| rest.split(')').next())
{
VariantBody::Tuple(tuple.trim().to_owned())
} else {
VariantBody::Unit
};
Some(EnumVariant { name, body })
}
fn protocol_sample(variant: &EnumVariant) -> Value {
let mut object = Map::new();
object.insert("type".to_owned(), Value::String(kebab_case(&variant.name)));
match &variant.body {
VariantBody::Unit => {}
VariantBody::Tuple(ty) => {
let Value::Object(fields) = sample_for_type(ty) else {
panic!("tuple variant {} must use object sample", variant.name);
};
object.extend(fields);
}
VariantBody::Struct(fields) => {
for (name, ty) in fields {
object.insert(name.clone(), sample_for_type(ty));
}
}
}
Value::Object(object)
}
fn kebab_case(name: &str) -> String {
let mut out = String::new();
for (idx, ch) in name.chars().enumerate() {
if ch.is_ascii_uppercase() {
if idx > 0 {
out.push('-');
}
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
fn sample_for_type(ty: &str) -> Value {
let ty = compact_type(ty);
if let Some(inner) = ty
.strip_prefix("Option<")
.and_then(|ty| ty.strip_suffix('>'))
{
let _ = inner;
return Value::Null;
}
if ty.starts_with("Vec<") {
return json!([]);
}
if let Some(inner) = ty.strip_prefix("Box<").and_then(|ty| ty.strip_suffix('>')) {
return sample_for_type(inner);
}
match ty.as_str() {
"String" | "PathBuf" => json!("sample"),
"bool" => json!(true),
"usize" | "u64" | "u32" | "u16" | "i64" => json!(1),
"BlobHash" => json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
"StatusResponse" => json!({
"home": "/tmp/geth-home",
"socket": "/tmp/geth.sock",
"agent_id": "agent:local",
"node_id": "node:local",
"daemon_started_at_ms": 1,
"daemon_uptime_seconds": 1,
"store_schema_version": 1,
"store_current_schema_version": 1,
"store_journal_mode": "wal",
"store_synchronous": "normal",
"store_status": "ok",
"store_note": "sample",
"iroh_enabled": true,
"endpoint_id": "endpoint:local",
"iroh_relay_mode": "default",
"iroh_local_discovery": true,
"iroh": "enabled",
"native_backends": []
}),
"NodeIdResponse" => json!({
"agent_id": "agent:local",
"node_id": "node:local",
"endpoint_id": "endpoint:local"
}),
"KeychainStatusResponse" => json!({
"initialized": true,
"admin_keys": 1,
"signatures": 1,
"verified_signatures": 1,
"failed_signatures": 0,
"users": 1,
"devices": 1,
"nodes": 1
}),
"ResourceDescriptor" => json!({
"id": "resource:sample",
"kind": "kv",
"name": "sample",
"authority": {"kind": "local"},
"local_role": "owner",
"replication": "local-only",
"retention": "keep",
"status": "active"
}),
"PeerCard" => peer_card_json(),
"DiscoveredPeer" => json!({
"card": peer_card_json(),
"discovered_at": 1,
"source": "manual",
"trust_state": "candidate-only"
}),
"OverlayPlan" => json!({
"name": "home",
"resource": "resource:overlay:home",
"cidr": "172.22.0.0/24",
"alpn": "/geth/overlay/1",
"capabilities": [],
"discovery": "manual",
"runtime": "planned",
"security": [],
"implementation_notes": []
}),
"OverlayJoinPlan" => json!({
"plan": sample_for_type("OverlayPlan"),
"network": sample_for_type("OverlayNetworkStatus"),
"enabled": true,
"note": "sample"
}),
"OverlayNetworkStatus" => json!({
"name": "home",
"resource": "resource:overlay:home",
"cidr": "172.22.0.0/24",
"state": "joined",
"virtual_ip": "172.22.0.2",
"peers": [],
"note": "sample"
}),
"OverlayInterfacePlan" => json!({
"name": "home",
"platform": "linux",
"interface_name": "geth-home",
"cidr": "172.22.0.0/24",
"virtual_ip": "172.22.0.2",
"requires_privileges": true,
"commands": [],
"notes": []
}),
"OverlayRuntimeStatus" => json!({
"name": "home",
"interface_name": "geth-home",
"virtual_ip": "172.22.0.2",
"cidr": "172.22.0.0/24",
"mtu": 1280,
"started_at_ms": 1,
"packets_from_tun": 0,
"packets_to_tun": 0,
"packets_to_peers": 0,
"last_error": null,
"note": "sample"
}),
"OverlayPacket" => json!({
"id": "overlay-packet:sample",
"network": "home",
"source_node": "node:local",
"destination_node": "node:peer",
"packet_base64": "AA==",
"size_bytes": 1,
"received_at_ms": 1,
"note": "sample"
}),
"FileRoot" => json!({
"id": "file-root:sample",
"resource": "resource:cas-root:sample",
"name": "sample",
"path": "/tmp/sample",
"latest_tree": null,
"updated_at_ms": 1
}),
"FileRootScan" => json!({
"root": sample_for_type("FileRoot"),
"tree": {
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"size_bytes": 1
},
"changes": [],
"note": "sample"
}),
"FileConflict" => json!({
"id": "file-conflict:sample",
"root": "sample",
"resource": "resource:cas-root:sample",
"path": "file.txt",
"kind": "concurrent-edit",
"status": "open",
"base_tree": null,
"local_tree": null,
"remote_tree": null,
"detail": "sample",
"resolution": null,
"resolution_note": null,
"created_at_ms": 1,
"resolved_at_ms": null
}),
"KeychainOp" => json!({
"id": "auth-op:sample",
"created_at": 1,
"kind": {"kind": "keychain-init"}
}),
"KeychainOpSignature" => json!({
"op_id": "auth-op:sample",
"signer": "key:sample",
"signer_public_key": "public",
"namespace": "geth.keychain.v1@geth.local",
"signature": [1],
"created_at": 1
}),
"KeychainAllowedSigner" => json!({
"key": "key:sample",
"principal": "admin",
"public_key": "public",
"valid_after_ms": null,
"valid_before_ms": null
}),
"KeychainSigchainReport" => json!({
"ops": 1,
"signatures": 1,
"accepted_ops": 1,
"rejected_ops": 0,
"active_admin_keys": 1,
"accepted_head": null,
"note": "sample"
}),
"AuthExplanation" => json!({
"subject": "node:peer",
"resource": "resource:sample",
"capability": "sample.read",
"allowed": true,
"reason": "sample",
"evaluated_ops": 1,
"diagnostics": []
}),
"AuthOp" => json!({
"id": "auth-op:sample",
"resource": "resource:sample",
"created_at": 1,
"kind": {"kind": "resource-create"}
}),
"AuthOpSignature" => json!({
"op_id": "auth-op:sample",
"signer": "key:sample",
"signer_public_key": "public",
"namespace": "geth.auth.v1@geth.local",
"signature": [1],
"created_at": 1
}),
"NodeRecord" => json!({
"id": "node:peer",
"device": "device:peer",
"name": "peer",
"endpoints": []
}),
"NodeEnrollmentRequest" => json!({
"id": "auth-op:enroll",
"requester_node": "node:peer",
"requester_agent": "agent:peer",
"requester_agent_public_key": "public",
"requested_node_name": "peer",
"requested_capabilities": [],
"endpoint_id": null,
"reason": null,
"status": "pending",
"created_at": 1,
"provenance": null
}),
"ResourceMasterSecret" => json!({
"id": "secret:sample",
"resource": "resource:sample",
"epoch": 1,
"created_at": 1
}),
"BearerAccess" => json!({
"secret": "bearer:sample",
"token": null,
"token_hash": "hash",
"resource": "resource:sample",
"capabilities": ["sample.read"],
"expires_at": null,
"may_delegate": false
}),
"BearerChallenge" => json!({
"resource": "resource:sample",
"capabilities": ["sample.read"],
"nonce": "nonce",
"issued_at": 1
}),
"BearerProof" => json!({
"secret": "bearer:sample",
"resource": "resource:sample",
"capabilities": ["sample.read"],
"nonce": "nonce",
"response": "response"
}),
"SshCertRequest" => json!({
"id": "ssh-cert-request:sample",
"requester_node": "node:peer",
"public_key": "ssh-ed25519 AAAA sample",
"public_key_fingerprint": "SHA256:sample",
"cert_kind": "user",
"principals": [],
"requested_validity": null,
"renewal_of": null,
"reason": null,
"status": "pending",
"created_at": 1,
"provenance": null
}),
"SshCertApproval" => json!({
"request_id": "ssh-cert-request:sample",
"approved_by_node": "node:local",
"ca_key_path": "/tmp/ca",
"key_id": "geth",
"valid_for": "+1h",
"serial": null,
"output_path": null,
"signing_command": [],
"signed": false,
"certificate_id": null,
"note": "sample"
}),
"SshCertificateRecord" => json!({
"id": "ssh-cert:sample",
"request_id": "ssh-cert-request:sample",
"certificate": "cert",
"certificate_fingerprint": "SHA256:sample",
"imported_at": 1,
"provenance": null
}),
"SshRevocationEntry" => json!({
"id": "ssh-revocation:sample",
"kind": "public-key",
"target": "ssh-ed25519 AAAA sample",
"reason": null,
"created_at": 1,
"published": false,
"provenance": null
}),
"DbResource" => json!({
"id": "db:sample",
"resource": "resource:db:sample",
"name": "sample",
"path": "/tmp/sample.sqlite",
"path_exists": true,
"size_bytes": 1,
"schema_metadata": "schema",
"crsqlite_changes": {
"available": false,
"change_count": null,
"max_db_version": null,
"columns": [],
"error": null
},
"sync_status": "local"
}),
"CrSqliteChangeBatch" => json!({
"schema_metadata": "schema",
"max_db_version": null,
"changes": []
}),
"KvResource" => json!({
"id": "kv:sample",
"resource": "resource:kv:sample",
"name": "sample",
"sync_status": "local"
}),
"KvEntry" => json!({
"store": "kv:sample",
"key": "key",
"value": "value"
}),
"DocumentResource" => json!({
"id": "document:sample",
"resource": "resource:document:sample",
"name": "sample",
"sync_status": "local",
"state_bytes": 2
}),
"DocumentState" => json!({
"document": sample_for_type("DocumentResource"),
"state_json": "{}",
"updated_at": 1
}),
"PubsubMessage" => json!({
"topic": "topic:sample",
"message": "sample",
"published_at": 1
}),
"PipeListener" => json!({
"id": "pipe:sample",
"name": "sample",
"listened_at": 1,
"note": "sample"
}),
"PipeConnection" => json!({
"target": "sample",
"connected_at": 1,
"local_listener_found": true,
"note": "sample"
}),
"PipeMessage" => json!({
"pipe": "sample",
"data_base64": "AA==",
"received_at": 1,
"source_node": null,
"note": "sample"
}),
"SshProxyConnection" => json!({
"target_node": "node:peer",
"connected_at": 1,
"local_sshd_target": "127.0.0.1:22",
"admin_shell_available": false,
"note": "sample"
}),
other => panic!("missing protocol sample for type {other}"),
}
}
fn compact_type(ty: &str) -> String {
ty.chars().filter(|ch| !ch.is_whitespace()).collect()
}
fn peer_card_json() -> Value {
json!({
"node_id": "node:peer",
"agent_id": "agent:peer",
"endpoints": [],
"issued_at": 1,
"signature": {
"namespace": "geth.peer-card.v1@geth.local",
"signer": "agent:peer",
"public_key": "public",
"signature": "signature"
}
})
}
2026-05-15 15:08:20 +02:00
#[test]
fn control_request_response_serialization_roundtrip() {
2026-05-19 16:04:20 +02:00
let request = ControlRequest::KeychainInit {
admin_key_path: Some(PathBuf::from("admin.pub")),
signing_key_path: Some(PathBuf::from("admin")),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 11:29:29 +02:00
let request = ControlRequest::NodeRename {
node: "laptop".to_owned(),
name: "work-laptop".to_owned(),
signing_key_path: Some(PathBuf::from("admin")),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 18:01:38 +02:00
let request = ControlRequest::NodeEnrollRequest {
node_name: "workstation".to_owned(),
capabilities: vec!["resource:ssh-proxy:local=ssh_proxy.connect".to_owned()],
reason: Some("new machine".to_owned()),
out: Some(PathBuf::from("enrollment.json")),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-07-18 16:48:35 +02:00
let request = ControlRequest::NodeEnrollJoin {
peer_card_path: PathBuf::from("owner.peer.json"),
admin_key_path: PathBuf::from("owner-admin.pub"),
node_name: "workstation".to_owned(),
capabilities: vec!["resource:ssh-proxy:local=ssh_proxy.connect".to_owned()],
reason: Some("new machine".to_owned()),
out: Some(PathBuf::from("enrollment.json")),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 18:01:38 +02:00
let request = ControlRequest::NodeEnrollSync {
owner_node: "owner-laptop".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 11:29:29 +02:00
let request = ControlRequest::KeychainSync {
node: "work-laptop".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-15 15:08:20 +02:00
let request = ControlRequest::CasHas {
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-23 01:17:30 +02:00
let request = ControlRequest::OverlayPlan {
name: "home-lan".to_owned(),
cidr: Some("172.22.0.0/24".to_owned()),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-23 02:08:51 +02:00
let request = ControlRequest::OverlayUp {
name: "home-lan".to_owned(),
bearer_secret: Some("gbt_route".to_owned()),
mtu: Some(1280),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::OverlaySend {
name: "home-lan".to_owned(),
node: "node:peer".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
bearer_secret: Some("gbt_invite".to_owned()),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-15 15:08:20 +02:00
let response = ControlResponse::CasHas {
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
present: true,
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-17 18:29:47 +02:00
2026-05-23 01:17:30 +02:00
let response = ControlResponse::OverlayPlanned {
plan: geth_overlay::plan_overlay("home-lan", None, geth_overlay::OVERLAY_ALPN)
.expect("overlay plan"),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-23 02:08:51 +02:00
let response = ControlResponse::OverlayJoined {
join: geth_overlay::OverlayJoinPlan {
plan: geth_overlay::plan_overlay("home-lan", None, geth_overlay::OVERLAY_ALPN)
.expect("overlay plan"),
network: geth_overlay::membership_status(
&geth_overlay::joined_overlay_membership(
"home-lan",
None,
"node:local",
"invite",
10,
)
.expect("overlay membership"),
),
enabled: true,
note: "membership persisted".to_owned(),
},
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::OverlayRuntimeStarted {
status: geth_overlay::OverlayRuntimeStatus {
name: "home-lan".to_owned(),
interface_name: "geth-homelan".to_owned(),
virtual_ip: "172.22.0.10".to_owned(),
cidr: "172.22.0.0/24".to_owned(),
mtu: 1280,
started_at_ms: 12,
packets_from_tun: 1,
packets_to_tun: 2,
packets_to_peers: 3,
last_error: None,
note: "active".to_owned(),
},
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::OverlayPackets {
name: "home-lan".to_owned(),
packets: vec![geth_overlay::OverlayPacket {
id: "overlay-packet:test".to_owned(),
network: "home-lan".to_owned(),
source_node: "node:peer".to_owned(),
destination_node: "node:local".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
size_bytes: 20,
received_at_ms: 11,
note: "received".to_owned(),
}],
drained: true,
note: "packets".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-21 11:29:29 +02:00
let response = ControlResponse::NodeList {
nodes: vec![NodeRecord {
id: geth_types::NodeId::new("node:local"),
device: geth_types::DeviceId::new("device:local"),
name: "work-laptop".to_owned(),
endpoints: Vec::new(),
}],
note: "reduced keychain view".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::KeychainSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
ops_imported: 2,
signatures_imported: 2,
invalid_ops_rejected: 1,
high_water_ms: 42,
2026-05-21 11:29:29 +02:00
note: "trusted admin signatures only".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-21 18:01:38 +02:00
let response = ControlResponse::NodeEnrollmentSynced {
owner_node: "owner-laptop".to_owned(),
keychain_ops_imported: 3,
keychain_signatures_imported: 3,
auth_ops_imported: 1,
auth_signatures_imported: 1,
invalid_ops_rejected: 0,
note: "synced".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::AuthSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
ops_imported: 1,
signatures_imported: 1,
invalid_ops_rejected: 0,
high_water_ms: 42,
2026-05-21 18:01:38 +02:00
note: "trusted admin signatures only".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::SyncStatus {
peers: vec![SyncPeerStatus {
peer_node_id: "node:peer".to_owned(),
streams: vec![SyncStreamStatus {
stream: "keychain".to_owned(),
cursor_ms: 42,
2026-05-22 15:00:09 +02:00
state: "ok".to_owned(),
stale: false,
stale_after_ms: 120_000,
last_attempt_ms: Some(43),
last_success_ms: Some(43),
last_error: None,
last_imported: 2,
last_rejected: 0,
2026-07-05 22:57:52 +02:00
consecutive_failures: 0,
retry_after_ms: None,
retry_in_ms: None,
2026-05-22 15:00:09 +02:00
next_action: "none".to_owned(),
}],
}],
note: "local health".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::SyncRan {
peers: vec![SyncPeerRun {
peer_node_id: "node:peer".to_owned(),
streams: vec![SyncStreamRun {
stream: "auth".to_owned(),
attempted: true,
success: true,
imported: 1,
rejected: 0,
cursor_ms: 44,
error: None,
}],
}],
note: "ran".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-19 19:08:08 +02:00
let request = ControlRequest::SecretBearerVerify {
secret: "bearer:test".to_owned(),
resource: "resource:cas:local".to_owned(),
capabilities: vec!["cas.fetch".to_owned()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::SecretBearerVerified {
secret: "bearer:test".to_owned(),
resource: "resource:cas:local".to_owned(),
capabilities: vec!["cas.fetch".to_owned()],
verified: true,
reason: "ok".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 17:18:25 +02:00
let request = ControlRequest::CasFetch {
node: "node:peer".to_owned(),
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
bearer_secret: Some("bearer:test".to_owned()),
2026-05-18 17:18:25 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-19 15:37:02 +02:00
let request = ControlRequest::CasProviders {
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::CasProviders {
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
providers: vec![CasProvider {
peer_node_id: "node:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
last_seen_ms: 42,
}],
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-17 18:29:47 +02:00
let request = ControlRequest::SshRevocationExport {
out: PathBuf::from("revocations.krl-spec"),
format: "openssh-krl-spec".to_owned(),
2026-05-18 11:51:12 +02:00
ca_public: None,
subject: None,
2026-05-17 18:29:47 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 11:56:42 +02:00
let request = ControlRequest::SshRevocationImport {
path: PathBuf::from("revocations.jsonl"),
format: "jsonl".to_owned(),
subject: None,
2026-05-18 11:56:42 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 17:24:10 +02:00
let request = ControlRequest::SshCertSync {
node: "node:ca".to_owned(),
bearer_secret: None,
2026-05-18 17:24:10 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::SshRevocationSync {
node: "node:ca".to_owned(),
bearer_secret: None,
2026-05-18 17:24:10 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-17 18:29:47 +02:00
let response = ControlResponse::SshRevocationExported {
out: PathBuf::from("revocations.krl-spec"),
format: "openssh-krl-spec".to_owned(),
count: 2,
note: "OpenSSH KRL specification".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-17 19:59:03 +02:00
2026-05-18 17:24:10 +02:00
let response = ControlResponse::SshCertSynced {
peer_node_id: "node:ca".to_owned(),
peer_agent_id: "agent:ca".to_owned(),
endpoint_id: "endpoint:ca".to_owned(),
requests_imported: 1,
certificates_imported: 0,
allowed: true,
reason: "direct grant".to_owned(),
note: "cert sync".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::SshRevocationSynced {
peer_node_id: "node:ca".to_owned(),
peer_agent_id: "agent:ca".to_owned(),
endpoint_id: "endpoint:ca".to_owned(),
revocations_imported: 2,
allowed: true,
reason: "direct grant".to_owned(),
note: "revocation sync".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 18:36:03 +02:00
let request = ControlRequest::KvSync {
node: "node:peer".to_owned(),
name: "prefs".to_owned(),
bearer_secret: None,
2026-05-18 18:36:03 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::KvSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
name: "prefs".to_owned(),
entries_imported: 1,
allowed: true,
reason: "direct grant".to_owned(),
note: "kv sync".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-17 19:59:03 +02:00
let request = ControlRequest::DocumentSet {
name: "notes".to_owned(),
state_json: r#"{"title":"notes"}"#.to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-17 20:17:26 +02:00
let request = ControlRequest::PipeListen {
name: "inbox".to_owned(),
2026-05-19 19:21:42 +02:00
node: Some("node:peer".to_owned()),
bearer_secret: Some("bearer:test".to_owned()),
2026-05-17 20:17:26 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-17 20:32:36 +02:00
2026-05-18 18:45:10 +02:00
let request = ControlRequest::PipeConnect {
target: "inbox".to_owned(),
node: Some("node:peer".to_owned()),
bearer_secret: None,
2026-05-18 18:45:10 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-20 13:57:14 +02:00
let request = ControlRequest::PipeSend {
target: "inbox".to_owned(),
data_base64: "aGVsbG8=".to_owned(),
node: Some("node:peer".to_owned()),
bearer_secret: None,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::PipeRecv {
name: "inbox".to_owned(),
peek: true,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::PipeMessages {
name: "inbox".to_owned(),
messages: vec![PipeMessage {
pipe: "inbox".to_owned(),
data_base64: "aGVsbG8=".to_owned(),
received_at: geth_types::UnixMillis(1),
source_node: Some("node:peer".to_owned()),
note: "pipe".to_owned(),
}],
drained: false,
note: "pipe messages".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 18:41:04 +02:00
let request = ControlRequest::PubsubPub {
topic: "presence/test".to_owned(),
message: "online".to_owned(),
node: Some("node:peer".to_owned()),
bearer_secret: None,
2026-05-18 18:41:04 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-19 15:28:48 +02:00
let request = ControlRequest::PubsubSub {
topic: "presence/test".to_owned(),
node: Some("node:peer".to_owned()),
bearer_secret: None,
2026-05-19 15:28:48 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 18:41:04 +02:00
let response = ControlResponse::PubsubRemotePublished {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
message: PubsubMessage {
topic: "presence/test".into(),
message: "online".to_owned(),
published_at: geth_types::UnixMillis(1),
},
allowed: true,
reason: "direct grant".to_owned(),
note: "lossy".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-19 15:28:48 +02:00
let response = ControlResponse::PubsubRemoteMessages {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
topic: "presence/test".to_owned(),
messages: vec![PubsubMessage {
topic: "presence/test".into(),
message: "online".to_owned(),
published_at: geth_types::UnixMillis(1),
}],
allowed: true,
reason: "direct grant".to_owned(),
note: "lossy".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 18:45:10 +02:00
let response = ControlResponse::PipeRemoteConnected {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
connection: PipeConnection {
target: "inbox".to_owned(),
connected_at: geth_types::UnixMillis(1),
local_listener_found: true,
note: "remote".to_owned(),
},
allowed: true,
reason: "direct grant".to_owned(),
note: "pipe connect".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-19 19:21:42 +02:00
let response = ControlResponse::PipeRemoteListening {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
listener: Some(PipeListener {
id: "pipe:inbox".into(),
name: "inbox".to_owned(),
listened_at: geth_types::UnixMillis(1),
note: "remote".to_owned(),
}),
allowed: true,
reason: "direct grant".to_owned(),
note: "pipe listen".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-19 15:51:11 +02:00
let request = ControlRequest::SshProxyConnect {
node: "node:peer".to_owned(),
bearer_secret: None,
2026-05-19 15:51:11 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 01:03:38 +02:00
let request = ControlRequest::SshProxyStream {
node: "node:peer".to_owned(),
bearer_secret: Some("bearer:test".to_owned()),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 01:49:48 +02:00
let request = ControlRequest::SshAdminShell {
node: "node:peer".to_owned(),
command: "status".to_owned(),
bearer_secret: None,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-19 15:51:11 +02:00
let response = ControlResponse::SshProxyConnected {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
connection: Some(SshProxyConnection {
target_node: "node:peer".into(),
connected_at: geth_types::UnixMillis(1),
local_sshd_target: Some("127.0.0.1:22".to_owned()),
admin_shell_available: false,
note: "proxy".to_owned(),
}),
allowed: true,
reason: "direct grant".to_owned(),
note: "ssh proxy".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-21 01:49:48 +02:00
let response = ControlResponse::SshAdminShellOutput {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
command: "status".to_owned(),
output: "node_id=node:peer".to_owned(),
allowed: true,
reason: "direct grant".to_owned(),
note: "restricted admin shell".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-17 20:32:36 +02:00
let request = ControlRequest::DbChanges {
name: "notes".to_owned(),
after_db_version: Some(7),
limit: 10,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 03:50:09 +02:00
2026-05-18 22:11:57 +02:00
let request = ControlRequest::DbSync {
node: "node:peer".to_owned(),
name: "notes".to_owned(),
limit: 50,
bearer_secret: None,
2026-05-18 22:11:57 +02:00
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::DbSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
name: "notes".to_owned(),
changes_received: 1,
2026-05-19 19:02:39 +02:00
changes_applied: 1,
2026-05-18 22:11:57 +02:00
max_db_version: Some(7),
schema_match: true,
allowed: true,
reason: "direct grant".to_owned(),
note: "exchange only".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 03:50:09 +02:00
let request = ControlRequest::CasRootScan {
name: "notes".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 03:57:26 +02:00
2026-05-20 13:22:40 +02:00
let request = ControlRequest::CasRootSync {
node: "node:peer".to_owned(),
name: "notes".to_owned(),
bearer_secret: None,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::CasRootSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
name: "notes".to_owned(),
root: None,
tree_bytes_imported: false,
2026-05-21 01:40:50 +02:00
sync_conflicts: Vec::new(),
2026-05-20 13:22:40 +02:00
allowed: false,
reason: "no grant".to_owned(),
note: "file-root sync".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-20 13:30:57 +02:00
let request = ControlRequest::CasRootApply {
source: "remote-node-peer-notes".to_owned(),
target: PathBuf::from("/tmp/notes"),
dry_run: true,
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::CasRootApplied {
source: "remote-node-peer-notes".to_owned(),
target: PathBuf::from("/tmp/notes"),
files_written: 1,
dirs_created: 1,
conflicts: Vec::new(),
dry_run: true,
note: "safe apply".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
2026-05-18 03:57:26 +02:00
let request = ControlRequest::CasConflictResolve {
conflict_id: "file-conflict:notes:1".to_owned(),
resolution: "keep-local".to_owned(),
note: Some("local file is authoritative".to_owned()),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 04:03:52 +02:00
let request = ControlRequest::PeerCardExport { out: None };
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 12:09:50 +02:00
let request = ControlRequest::PeerPing {
node: "node:peer".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 17:01:50 +02:00
let request = ControlRequest::PeerAuthCheck {
node: "node:peer".to_owned(),
resource: "resource:cas:local".to_owned(),
capability: "cas.fetch".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 12:09:50 +02:00
let response = PeerControlResponse::Pong {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
alpn: "/geth/control/1".to_owned(),
nonce: "nonce".to_owned(),
note: "candidate only".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 17:01:50 +02:00
let response = PeerControlResponse::AuthChecked {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
resource: "resource:cas:local".to_owned(),
capability: "cas.fetch".to_owned(),
allowed: false,
reason: "no grant".to_owned(),
evaluated_ops: 0,
nonce: "nonce".to_owned(),
note: "protected".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 17:18:25 +02:00
2026-05-18 22:17:27 +02:00
let response = PeerControlResponse::SyncStatus {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
watermarks: vec![SyncWatermark {
stream: "kv:prefs".to_owned(),
high_water: 42,
}],
nonce: "nonce".to_owned(),
note: "status".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 17:18:25 +02:00
let response = PeerControlResponse::CasFetched {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
size_bytes: 5,
content_base64: Some("aGVsbG8".to_owned()),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "cas fetch".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 17:24:10 +02:00
2026-05-20 13:22:40 +02:00
let response = PeerControlResponse::CasRootSynced {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
name: "notes".to_owned(),
root: None,
tree_content_base64: None,
allowed: false,
reason: "no grant".to_owned(),
evaluated_ops: 0,
nonce: "nonce".to_owned(),
note: "file-root sync".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 22:11:57 +02:00
let request = PeerControlRequest::DbSync {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
name: "notes".to_owned(),
after_db_version: Some(7),
limit: 10,
nonce: "nonce".to_owned(),
bearer_proof: None,
2026-05-18 22:11:57 +02:00
};
assert_eq!(
2026-05-18 22:17:27 +02:00
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
let request = PeerControlRequest::SyncStatus {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
nonce: "nonce".to_owned(),
};
assert_eq!(
2026-05-18 22:11:57 +02:00
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-18 17:24:10 +02:00
let response = PeerControlResponse::SshCertSynced {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
requests: Vec::new(),
certificates: Vec::new(),
log_entries: Vec::new(),
2026-05-18 18:29:45 +02:00
high_water_ms: 42,
2026-05-18 17:24:10 +02:00
allowed: false,
reason: "no grant".to_owned(),
evaluated_ops: 0,
nonce: "nonce".to_owned(),
note: "cert sync".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
let response = PeerControlResponse::SshRevocationSynced {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
revocations: Vec::new(),
log_entries: Vec::new(),
2026-05-18 18:29:45 +02:00
high_water_ms: 42,
2026-05-18 17:24:10 +02:00
allowed: false,
reason: "no grant".to_owned(),
evaluated_ops: 0,
nonce: "nonce".to_owned(),
note: "revocation sync".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 18:36:03 +02:00
let response = PeerControlResponse::KvSynced {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
name: "prefs".to_owned(),
entries: vec![KvSyncEntry {
key: "apps/foo/theme".to_owned(),
value: "dark".to_owned(),
updated_at_ms: 42,
}],
high_water_ms: 43,
2026-05-22 16:24:11 +02:00
docs_ticket: Some("doc-ticket".to_owned()),
2026-05-18 18:36:03 +02:00
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "kv sync".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 18:41:04 +02:00
let response = PeerControlResponse::PubsubPublished {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
message: Some(PubsubMessage {
topic: "presence/test".into(),
message: "online".to_owned(),
published_at: geth_types::UnixMillis(1),
}),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
2026-05-19 15:28:48 +02:00
note: "lossy".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
let request = PeerControlRequest::PubsubSubscribe {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
topic: "presence/test".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: None,
2026-05-19 15:28:48 +02:00
};
assert_eq!(
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
let response = PeerControlResponse::PubsubSubscribed {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
topic: "presence/test".to_owned(),
messages: vec![PubsubMessage {
topic: "presence/test".into(),
message: "online".to_owned(),
published_at: geth_types::UnixMillis(1),
}],
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
2026-05-18 18:41:04 +02:00
note: "lossy".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-18 18:45:10 +02:00
let response = PeerControlResponse::PipeConnected {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
connection: Some(PipeConnection {
target: "inbox".to_owned(),
connected_at: geth_types::UnixMillis(1),
local_listener_found: true,
note: "remote".to_owned(),
}),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "pipe connect".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
2026-05-19 15:51:11 +02:00
.expect("decode"),
response
);
let request = PeerControlRequest::SshProxyConnect {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
nonce: "nonce".to_owned(),
bearer_proof: Some(BearerProof {
secret: "bearer:test".into(),
resource: "resource:ssh-proxy:local".into(),
capabilities: vec!["ssh_proxy.connect".into()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
}),
2026-05-19 15:51:11 +02:00
};
2026-05-19 19:21:42 +02:00
assert_eq!(
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-21 01:49:48 +02:00
let request = PeerControlRequest::SshAdminShell {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
command: "status".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: Some(BearerProof {
secret: "bearer:test".into(),
resource: "resource:ssh-proxy:local".into(),
capabilities: vec!["ssh_proxy.admin_shell".into()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
}),
};
assert_eq!(
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-19 19:21:42 +02:00
let request = PeerControlRequest::PipeListen {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
name: "inbox".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: None,
};
2026-05-19 15:51:11 +02:00
assert_eq!(
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
request
);
2026-05-20 13:57:14 +02:00
let request = PipeWireRequest::Send {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
target: "inbox".to_owned(),
data_base64: "aGVsbG8=".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: None,
};
assert_eq!(
decode_pipe_wire_request(&encode_pipe_wire_request(&request).expect("encode"))
.expect("decode"),
request
);
let response = PipeWireResponse::Sent {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
message: Box::new(Some(PipeMessage {
pipe: "inbox".to_owned(),
data_base64: "aGVsbG8=".to_owned(),
received_at: geth_types::UnixMillis(1),
source_node: Some("node:caller".to_owned()),
note: "pipe".to_owned(),
})),
listener_found: true,
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "pipe wire".to_owned(),
};
assert_eq!(
decode_pipe_wire_response(&encode_pipe_wire_response(&response).expect("encode"))
2026-05-21 01:12:01 +02:00
.expect("decode"),
response
);
2026-05-23 02:08:51 +02:00
let request = OverlayWireRequest::Packet {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
network: "home".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: Some(BearerProof {
secret: "bearer:test".into(),
resource: "resource:overlay:home".into(),
capabilities: vec!["overlay.route".into()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
}),
};
assert_eq!(
decode_overlay_wire_request(&encode_overlay_wire_request(&request).expect("encode"))
.expect("decode"),
request
);
let response = OverlayWireResponse::PacketAccepted {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
packet: Some(Box::new(geth_overlay::OverlayPacket {
id: "overlay-packet:test".to_owned(),
network: "home".to_owned(),
source_node: "node:caller".to_owned(),
destination_node: "node:peer".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
size_bytes: 20,
received_at_ms: 12,
note: "overlay".to_owned(),
})),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "overlay wire".to_owned(),
};
assert_eq!(
decode_overlay_wire_response(&encode_overlay_wire_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-21 01:12:01 +02:00
let request = PipeWireRequest::TcpConnect {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
target_addr: "127.0.0.1:22".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: Some(BearerProof {
secret: "bearer:test".into(),
resource: "resource:pipe-tcp:127.0.0.1:22".into(),
capabilities: vec!["pipe.forward".into()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
}),
};
assert_eq!(
decode_pipe_wire_request(&encode_pipe_wire_request(&request).expect("encode"))
.expect("decode"),
request
);
let response = PipeWireResponse::Connected {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
connection: Some(PipeConnection {
target: "127.0.0.1:22".to_owned(),
connected_at: geth_types::UnixMillis(1),
local_listener_found: true,
note: "tcp".to_owned(),
}),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "pipe tcp".to_owned(),
};
assert_eq!(
decode_pipe_wire_response(&encode_pipe_wire_response(&response).expect("encode"))
2026-05-20 13:57:14 +02:00
.expect("decode"),
response
);
2026-05-21 01:15:51 +02:00
let request = PipeWireRequest::UnixConnect {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
target_path: PathBuf::from("/tmp/geth-test.sock"),
nonce: "nonce".to_owned(),
bearer_proof: None,
};
assert_eq!(
decode_pipe_wire_request(&encode_pipe_wire_request(&request).expect("encode"))
.expect("decode"),
request
);
let response = PipeWireResponse::Error {
message: "pipe denied".to_owned(),
};
assert_eq!(
decode_pipe_wire_response(&encode_pipe_wire_response(&response).expect("encode"))
.expect("decode"),
response
);
let response = OverlayWireResponse::Error {
message: "overlay denied".to_owned(),
};
assert_eq!(
decode_overlay_wire_response(&encode_overlay_wire_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-19 15:51:11 +02:00
let response = PeerControlResponse::SshProxyConnected {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
connection: Some(SshProxyConnection {
target_node: "node:peer".into(),
connected_at: geth_types::UnixMillis(1),
local_sshd_target: Some("127.0.0.1:22".to_owned()),
admin_shell_available: false,
note: "proxy".to_owned(),
}),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "ssh proxy".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
2026-05-21 01:49:48 +02:00
.expect("decode"),
response
);
let response = PeerControlResponse::SshAdminShellOutput {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
command: "status".to_owned(),
output: "node_id=node:peer".to_owned(),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "restricted admin shell".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
2026-05-18 18:45:10 +02:00
.expect("decode"),
response
);
2026-05-18 22:11:57 +02:00
let response = PeerControlResponse::DbSynced {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
name: "notes".to_owned(),
batch: Some(CrSqliteChangeBatch {
schema_metadata: "tables=1 schema_hash=abc".to_owned(),
max_db_version: Some(7),
changes: Vec::new(),
}),
high_water_db_version: Some(7),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "db sync".to_owned(),
};
assert_eq!(
decode_peer_response(&encode_peer_response(&response).expect("encode"))
.expect("decode"),
response
);
2026-05-15 15:08:20 +02:00
}
#[test]
fn malformed_and_unknown_protocol_inputs_fail() {
for input in [
"{",
"{}",
r#"{"type":"unknown"}"#,
r#"{"type":"status","extra":}"#,
] {
assert!(decode_request(input).is_err());
assert!(decode_response(input).is_err());
assert!(decode_peer_request(input).is_err());
assert!(decode_peer_response(input).is_err());
assert!(decode_pipe_wire_request(input).is_err());
assert!(decode_pipe_wire_response(input).is_err());
assert!(decode_overlay_wire_request(input).is_err());
assert!(decode_overlay_wire_response(input).is_err());
}
}
2026-05-15 15:08:20 +02:00
}