use geth_auth::{AuthExplanation, AuthOp}; use geth_cas::{FileConflict, FileRoot, FileRootScan}; use geth_db::{CrSqliteChangeBatch, DbResource}; use geth_discovery::{DiscoveredPeer, PeerCard}; use geth_document::{DocumentResource, DocumentState}; use geth_keychain::{KeychainOp, KeychainOpSignature}; use geth_kv::{KvEntry, KvResource, KvSyncEntry}; use geth_pipe::{PipeConnection, PipeListener}; use geth_pubsub::PubsubMessage; use geth_resource::ResourceDescriptor; use geth_secrets::{BearerAccess, ResourceMasterSecret}; use geth_ssh_identity::{ SshCertApproval, SshCertRequest, SshCertificateRecord, SshRevocationEntry, }; use geth_ssh_proxy::SshProxyConnection; 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, PeerCardExport { out: Option, }, PeerCardImport { path: PathBuf, }, PeerCardList, PeerPing { node: String, }, PeerAuthCheck { node: String, resource: String, capability: String, }, ResourceList, ResourceCreate { kind: String, name: String, }, CasAdd { path: PathBuf, }, CasGet { hash: BlobHash, out: PathBuf, }, CasFetch { node: String, hash: BlobHash, }, CasHash { path: PathBuf, }, CasHas { hash: BlobHash, }, CasPin { hash: BlobHash, }, CasUnpin { hash: BlobHash, }, CasCleanup { dry_run: bool, }, CasProviders { hash: BlobHash, }, CasList, CasRootAdd { name: String, path: PathBuf, }, CasRootList, CasRootScan { name: String, }, CasConflictRecord { root: String, path: String, kind: String, detail: String, base_tree: Option, local_tree: Option, remote_tree: Option, }, CasConflictList { root: Option, }, CasConflictResolve { conflict_id: String, resolution: String, note: Option, }, KeychainInit { admin_key_path: Option, signing_key_path: Option, }, KeychainStatus, SecretStatus, SecretCreate { resource: String, }, SecretRotate { resource: String, }, SecretBearerCreate { resource: String, capabilities: Vec, expires_at_ms: Option, }, SecretBearerList, SecretBearerRevoke { resource: String, secret: String, }, AuthExplain { subject: String, resource: String, capability: String, }, AuthGrant { subject: String, resource: String, capability: String, grant_id: Option, }, AuthRevoke { resource: String, grant_id: String, }, SshCertRequest { public_key_path: PathBuf, cert_kind: String, principals: Vec, requested_validity: Option, renewal_of: Option, reason: Option, subject: Option, }, SshCertRequests { subject: Option, }, SshCertApprove { request_id: String, ca_key_path: PathBuf, valid_for: Option, serial: Option, out: Option, sign: bool, subject: Option, }, SshCertImport { request_id: String, cert_path: PathBuf, subject: Option, }, SshCertList { subject: Option, }, SshCertSync { node: String, }, SshRevocationAdd { kind: String, target: String, reason: Option, subject: Option, }, SshRevocationList { subject: Option, }, SshRevocationExport { out: PathBuf, format: String, ca_public: Option, subject: Option, }, SshRevocationImport { path: PathBuf, format: String, subject: Option, }, SshRevocationSync { node: String, }, SshProxyConnect { node: String, }, DbAdd { name: String, path: PathBuf, }, DbStatus { name: String, }, DbChanges { name: String, after_db_version: Option, limit: u32, }, DbSync { node: String, name: String, limit: u32, }, KvCreate { name: String, }, KvSet { name: String, key: String, value: String, subject: Option, }, KvGet { name: String, key: String, }, KvSync { node: String, name: String, }, DocumentCreate { name: String, }, DocumentStatus { name: String, }, DocumentSet { name: String, state_json: String, }, DocumentGet { name: String, }, DocumentSync { node: String, name: String, }, PubsubPub { topic: String, message: String, node: Option, }, PubsubSub { topic: String, node: Option, }, PipeListen { name: String, }, PipeConnect { target: String, node: Option, }, ModuleStub { module: String, command: String, }, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "kebab-case")] pub enum ControlResponse { Status(StatusResponse), NodeId(NodeIdResponse), PeerCardExported { card: PeerCard, out: Option, note: String, }, PeerCardImported { peer: DiscoveredPeer, note: String, }, PeerCardList { peers: Vec, note: String, }, PeerPinged { peer_node_id: String, peer_agent_id: String, endpoint_id: String, alpn: String, note: String, }, 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, }, ResourceList { resources: Vec, }, ResourceCreated { resource: ResourceDescriptor, }, CasAdded { hash: BlobHash, size_bytes: u64, }, CasGot { hash: BlobHash, out: PathBuf, size_bytes: u64, }, CasFetched { peer_node_id: String, peer_agent_id: String, endpoint_id: String, hash: BlobHash, size_bytes: u64, allowed: bool, reason: String, note: String, }, CasHash { hash: BlobHash, }, CasHas { hash: BlobHash, present: bool, }, CasPinned { hash: BlobHash, pinned: bool, }, CasCleanup { removed: Vec, retained_pinned: Vec, dry_run: bool, }, CasList { blobs: Vec, }, CasProviders { hash: BlobHash, providers: Vec, }, CasRootAdded { root: FileRoot, }, CasRootList { roots: Vec, }, CasRootScanned { scan: FileRootScan, }, CasConflictRecorded { conflict: FileConflict, }, CasConflictList { conflicts: Vec, }, CasConflictResolved { conflict: FileConflict, }, KeychainStatus(KeychainStatusResponse), KeychainInitialized { ops: Vec, signatures: Vec, }, SecretStatus { secrets: Vec, }, SecretCreated { secret: ResourceMasterSecret, }, SecretBearerCreated { access: BearerAccess, }, SecretBearerList { access: Vec, }, SecretBearerRevoked { resource: String, secret: String, }, AuthExplain(AuthExplanation), AuthOpRecorded { op: AuthOp, }, SshCertRequested { request: SshCertRequest, }, SshCertRequests { requests: Vec, }, SshCertApproved { approval: SshCertApproval, }, SshCertImported { certificate: SshCertificateRecord, }, SshCertList { requests: Vec, certificates: Vec, }, 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, }, SshRevocationExported { out: PathBuf, format: String, count: usize, note: String, }, SshRevocationImported { revocations: Vec, format: String, count: usize, note: String, }, SshRevocationSynced { peer_node_id: String, peer_agent_id: String, endpoint_id: String, revocations_imported: usize, allowed: bool, reason: String, note: String, }, DbAdded { db: DbResource, }, DbStatus { db: DbResource, }, DbChanges { db: DbResource, batch: CrSqliteChangeBatch, }, DbSynced { peer_node_id: String, peer_agent_id: String, endpoint_id: String, name: String, changes_received: usize, max_db_version: Option, schema_match: bool, allowed: bool, reason: String, note: String, }, KvCreated { kv: KvResource, }, KvSet { entry: KvEntry, }, KvGet { entry: Option, }, KvSynced { peer_node_id: String, peer_agent_id: String, endpoint_id: String, name: String, entries_imported: usize, allowed: bool, reason: String, note: String, }, DocumentCreated { document: DocumentResource, }, DocumentStatus { document: DocumentResource, }, DocumentSet { state: DocumentState, }, DocumentGet { state: DocumentState, }, DocumentSynced { peer_node_id: String, peer_agent_id: String, endpoint_id: String, name: String, updated: bool, allowed: bool, reason: String, note: String, }, PubsubPublished { message: PubsubMessage, }, PubsubRemotePublished { peer_node_id: String, peer_agent_id: String, endpoint_id: String, message: PubsubMessage, allowed: bool, reason: String, note: String, }, PubsubMessages { topic: String, messages: Vec, note: String, }, PubsubRemoteMessages { peer_node_id: String, peer_agent_id: String, endpoint_id: String, topic: String, messages: Vec, allowed: bool, reason: String, note: String, }, PipeListening { listener: PipeListener, }, PipeConnected { connection: PipeConnection, }, PipeRemoteConnected { peer_node_id: String, peer_agent_id: String, endpoint_id: String, connection: PipeConnection, allowed: bool, reason: String, note: String, }, SshProxyConnected { peer_node_id: String, peer_agent_id: String, endpoint_id: String, connection: Option, allowed: bool, reason: String, note: String, }, 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, pub iroh_enabled: bool, pub endpoint_id: Option, pub iroh_relay_mode: String, pub iroh_local_discovery: bool, pub iroh: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct NodeIdResponse { pub agent_id: String, pub node_id: String, pub endpoint_id: Option, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct KeychainStatusResponse { pub initialized: bool, pub admin_keys: usize, pub signatures: usize, 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, pub pinned: bool, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CasProvider { pub peer_node_id: String, pub endpoint_id: String, pub last_seen_ms: i64, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "kebab-case")] pub enum PeerControlRequest { Ping { peer_card: PeerCard, nonce: String, }, AuthCheck { peer_card: PeerCard, resource: String, capability: String, nonce: String, }, SyncStatus { peer_card: PeerCard, nonce: String, }, CasFetch { peer_card: PeerCard, hash: BlobHash, nonce: String, }, SshCertSync { peer_card: PeerCard, since_ms: i64, nonce: String, }, SshRevocationSync { peer_card: PeerCard, since_ms: i64, nonce: String, }, KvSync { peer_card: PeerCard, name: String, since_ms: i64, nonce: String, }, PubsubPublish { peer_card: PeerCard, topic: String, message: String, nonce: String, }, PubsubSubscribe { peer_card: PeerCard, topic: String, nonce: String, }, PipeConnect { peer_card: PeerCard, target: String, nonce: String, }, SshProxyConnect { peer_card: PeerCard, nonce: String, }, DocumentSync { peer_card: PeerCard, name: String, since_ms: i64, nonce: String, }, DbSync { peer_card: PeerCard, name: String, after_db_version: Option, limit: u32, nonce: String, }, } #[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, }, 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, }, SyncStatus { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, watermarks: Vec, nonce: String, note: String, }, CasFetched { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, hash: BlobHash, size_bytes: u64, content_base64: Option, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, SshCertSynced { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, requests: Vec, certificates: Vec, high_water_ms: i64, 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, high_water_ms: i64, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, KvSynced { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, name: String, entries: Vec, high_water_ms: i64, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, PubsubPublished { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, message: Option, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, PubsubSubscribed { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, topic: String, messages: Vec, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, PipeConnected { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, connection: Option, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, SshProxyConnected { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, connection: Option, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, DocumentSynced { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, name: String, state: Option, high_water_ms: i64, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, DbSynced { node_id: String, agent_id: String, endpoint_id: String, remote_endpoint_id: String, name: String, batch: Option, high_water_db_version: Option, allowed: bool, reason: String, evaluated_ops: usize, nonce: String, note: String, }, Error { message: String, }, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SyncWatermark { pub stream: String, pub high_water: i64, } #[derive(Debug, thiserror::Error)] pub enum ControlError { #[error("json error: {0}")] Json(#[from] serde_json::Error), } pub fn encode_request(request: &ControlRequest) -> Result { let mut line = serde_json::to_string(request)?; line.push('\n'); Ok(line) } pub fn decode_request(line: &str) -> Result { serde_json::from_str(line).map_err(ControlError::from) } pub fn encode_response(response: &ControlResponse) -> Result { let mut line = serde_json::to_string(response)?; line.push('\n'); Ok(line) } pub fn decode_response(line: &str) -> Result { serde_json::from_str(line).map_err(ControlError::from) } pub fn encode_peer_request(request: &PeerControlRequest) -> Result { let mut line = serde_json::to_string(request)?; line.push('\n'); Ok(line) } pub fn decode_peer_request(line: &str) -> Result { serde_json::from_str(line).map_err(ControlError::from) } pub fn encode_peer_response(response: &PeerControlResponse) -> Result { let mut line = serde_json::to_string(response)?; line.push('\n'); Ok(line) } pub fn decode_peer_response(line: &str) -> Result { serde_json::from_str(line).map_err(ControlError::from) } #[cfg(test)] mod tests { use super::*; #[test] fn control_request_response_serialization_roundtrip() { 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 ); let request = ControlRequest::CasHas { hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let response = ControlResponse::CasHas { hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(), present: true, }; assert_eq!( decode_response(&encode_response(&response).expect("encode")).expect("decode"), response ); let request = ControlRequest::CasFetch { node: "node:peer".to_owned(), hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); let request = ControlRequest::SshRevocationExport { out: PathBuf::from("revocations.krl-spec"), format: "openssh-krl-spec".to_owned(), ca_public: None, subject: None, }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::SshRevocationImport { path: PathBuf::from("revocations.jsonl"), format: "jsonl".to_owned(), subject: None, }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::SshCertSync { node: "node:ca".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::SshRevocationSync { node: "node:ca".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); 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 ); let request = ControlRequest::KvSync { node: "node:peer".to_owned(), name: "prefs".to_owned(), }; 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 ); 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 ); let request = ControlRequest::PipeListen { name: "inbox".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::PipeConnect { target: "inbox".to_owned(), node: Some("node:peer".to_owned()), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::PubsubPub { topic: "presence/test".to_owned(), message: "online".to_owned(), node: Some("node:peer".to_owned()), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::PubsubSub { topic: "presence/test".to_owned(), node: Some("node:peer".to_owned()), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); 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 ); 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 ); let request = ControlRequest::SshProxyConnect { node: "node:peer".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); 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 ); let request = ControlRequest::DbSync { node: "node:peer".to_owned(), name: "notes".to_owned(), limit: 50, }; 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, 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 ); let request = ControlRequest::CasRootScan { name: "notes".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); let request = ControlRequest::PeerCardExport { out: None }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); let request = ControlRequest::PeerPing { node: "node:peer".to_owned(), }; assert_eq!( decode_request(&encode_request(&request).expect("encode")).expect("decode"), request ); 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 ); 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 ); 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 ); 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 ); 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 ); 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(), }; assert_eq!( 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!( decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"), request ); 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(), high_water_ms: 42, 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(), high_water_ms: 42, 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 ); 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, 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 ); 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(), 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(), }; 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(), note: "lossy".to_owned(), }; assert_eq!( decode_peer_response(&encode_peer_response(&response).expect("encode")) .expect("decode"), response ); 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")) .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(), }; assert_eq!( decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"), request ); 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")) .expect("decode"), response ); 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 ); } }