use geth_auth::{AuthExplanation, AuthOp}; use geth_db::DbResource; use geth_document::DocumentResource; use geth_keychain::KeychainOp; use geth_kv::{KvEntry, KvResource}; use geth_resource::ResourceDescriptor; use geth_ssh_identity::{ SshCertApproval, SshCertRequest, SshCertificateRecord, SshRevocationEntry, }; 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, ResourceList, ResourceCreate { kind: String, name: String, }, CasAdd { path: PathBuf, }, CasGet { hash: BlobHash, out: PathBuf, }, CasHash { path: PathBuf, }, CasHas { hash: BlobHash, }, CasPin { hash: BlobHash, }, CasUnpin { hash: BlobHash, }, CasCleanup { dry_run: bool, }, CasList, KeychainInit { admin_key_path: Option, }, KeychainStatus, 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, }, SshCertRequests, SshCertApprove { request_id: String, ca_key_path: PathBuf, valid_for: Option, serial: Option, out: Option, }, SshCertImport { request_id: String, cert_path: PathBuf, }, SshCertList, SshRevocationAdd { kind: String, target: String, reason: Option, }, SshRevocationList, SshRevocationExport { out: PathBuf, }, DbAdd { name: String, path: PathBuf, }, DbStatus { name: String, }, KvCreate { name: String, }, KvSet { name: String, key: String, value: String, }, KvGet { name: String, key: String, }, DocumentCreate { name: String, }, DocumentStatus { name: String, }, ModuleStub { module: String, command: String, }, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "kebab-case")] pub enum ControlResponse { Status(StatusResponse), NodeId(NodeIdResponse), ResourceList { resources: Vec, }, ResourceCreated { resource: ResourceDescriptor, }, CasAdded { hash: BlobHash, size_bytes: u64, }, CasGot { hash: BlobHash, out: PathBuf, size_bytes: u64, }, 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, }, KeychainStatus(KeychainStatusResponse), KeychainInitialized { ops: Vec, }, AuthExplain(AuthExplanation), AuthOpRecorded { op: AuthOp, }, SshCertRequested { request: SshCertRequest, }, SshCertRequests { requests: Vec, }, SshCertApproved { approval: SshCertApproval, }, SshCertImported { certificate: SshCertificateRecord, }, SshCertList { requests: Vec, certificates: Vec, }, SshRevocationAdded { revocation: SshRevocationEntry, }, SshRevocationList { revocations: Vec, }, SshRevocationExported { out: PathBuf, count: usize, }, DbAdded { db: DbResource, }, DbStatus { db: DbResource, }, KvCreated { kv: KvResource, }, KvSet { entry: KvEntry, }, KvGet { entry: Option, }, DocumentCreated { document: DocumentResource, }, DocumentStatus { document: DocumentResource, }, 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 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(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) } #[cfg(test)] mod tests { use super::*; #[test] fn control_request_response_serialization_roundtrip() { 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 ); } }