2026-05-16 16:32:03 +02:00
|
|
|
use geth_auth::{AuthExplanation, AuthOp};
|
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};
|
2026-05-16 16:34:20 +02:00
|
|
|
use geth_keychain::KeychainOp;
|
2026-05-16 21:52:35 +02:00
|
|
|
use geth_kv::{KvEntry, KvResource};
|
2026-05-17 20:17:26 +02:00
|
|
|
use geth_pipe::{PipeConnection, PipeListener};
|
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-17 02:58:58 +02:00
|
|
|
use geth_secrets::{BearerAccess, ResourceMasterSecret};
|
2026-05-16 00:17:08 +02:00
|
|
|
use geth_ssh_identity::{
|
|
|
|
|
SshCertApproval, SshCertRequest, SshCertificateRecord, SshRevocationEntry,
|
|
|
|
|
};
|
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-15 15:08:20 +02:00
|
|
|
ResourceList,
|
|
|
|
|
ResourceCreate {
|
|
|
|
|
kind: String,
|
|
|
|
|
name: String,
|
|
|
|
|
},
|
|
|
|
|
CasAdd {
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
CasGet {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
out: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
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-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-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-16 16:34:20 +02:00
|
|
|
KeychainInit {
|
|
|
|
|
admin_key_path: Option<PathBuf>,
|
|
|
|
|
},
|
2026-05-15 15:08:20 +02:00
|
|
|
KeychainStatus,
|
2026-05-16 22:18:49 +02:00
|
|
|
SecretStatus,
|
|
|
|
|
SecretCreate {
|
|
|
|
|
resource: String,
|
|
|
|
|
},
|
|
|
|
|
SecretRotate {
|
|
|
|
|
resource: String,
|
|
|
|
|
},
|
2026-05-17 02:58:58 +02:00
|
|
|
SecretBearerCreate {
|
|
|
|
|
resource: String,
|
|
|
|
|
capabilities: Vec<String>,
|
|
|
|
|
expires_at_ms: Option<i64>,
|
|
|
|
|
},
|
|
|
|
|
SecretBearerList,
|
|
|
|
|
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>,
|
|
|
|
|
},
|
|
|
|
|
AuthRevoke {
|
|
|
|
|
resource: String,
|
|
|
|
|
grant_id: String,
|
|
|
|
|
},
|
2026-05-16 00:17:08 +02:00
|
|
|
SshCertRequest {
|
|
|
|
|
public_key_path: PathBuf,
|
|
|
|
|
cert_kind: String,
|
|
|
|
|
principals: Vec<String>,
|
|
|
|
|
requested_validity: Option<String>,
|
|
|
|
|
renewal_of: Option<String>,
|
|
|
|
|
reason: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
SshCertRequests,
|
|
|
|
|
SshCertApprove {
|
|
|
|
|
request_id: String,
|
|
|
|
|
ca_key_path: PathBuf,
|
|
|
|
|
valid_for: Option<String>,
|
|
|
|
|
serial: Option<u64>,
|
|
|
|
|
out: Option<PathBuf>,
|
|
|
|
|
},
|
|
|
|
|
SshCertImport {
|
|
|
|
|
request_id: String,
|
|
|
|
|
cert_path: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
SshCertList,
|
|
|
|
|
SshRevocationAdd {
|
|
|
|
|
kind: String,
|
|
|
|
|
target: String,
|
|
|
|
|
reason: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationList,
|
|
|
|
|
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>,
|
2026-05-16 00:17:08 +02:00
|
|
|
},
|
2026-05-18 11:56:42 +02:00
|
|
|
SshRevocationImport {
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
format: 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-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-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-17 18:23:51 +02:00
|
|
|
PubsubPub {
|
|
|
|
|
topic: String,
|
|
|
|
|
message: String,
|
|
|
|
|
},
|
|
|
|
|
PubsubSub {
|
|
|
|
|
topic: String,
|
|
|
|
|
},
|
2026-05-17 20:17:26 +02:00
|
|
|
PipeListen {
|
|
|
|
|
name: String,
|
|
|
|
|
},
|
|
|
|
|
PipeConnect {
|
|
|
|
|
target: String,
|
|
|
|
|
},
|
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-15 15:08:20 +02:00
|
|
|
ResourceList {
|
|
|
|
|
resources: Vec<ResourceDescriptor>,
|
|
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
},
|
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-18 03:50:09 +02:00
|
|
|
CasRootAdded {
|
|
|
|
|
root: FileRoot,
|
|
|
|
|
},
|
|
|
|
|
CasRootList {
|
|
|
|
|
roots: Vec<FileRoot>,
|
|
|
|
|
},
|
|
|
|
|
CasRootScanned {
|
|
|
|
|
scan: FileRootScan,
|
|
|
|
|
},
|
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),
|
2026-05-16 16:34:20 +02:00
|
|
|
KeychainInitialized {
|
|
|
|
|
ops: Vec<KeychainOp>,
|
|
|
|
|
},
|
2026-05-16 22:18:49 +02:00
|
|
|
SecretStatus {
|
|
|
|
|
secrets: Vec<ResourceMasterSecret>,
|
|
|
|
|
},
|
|
|
|
|
SecretCreated {
|
|
|
|
|
secret: ResourceMasterSecret,
|
|
|
|
|
},
|
2026-05-17 02:58:58 +02:00
|
|
|
SecretBearerCreated {
|
|
|
|
|
access: BearerAccess,
|
|
|
|
|
},
|
|
|
|
|
SecretBearerList {
|
|
|
|
|
access: Vec<BearerAccess>,
|
|
|
|
|
},
|
|
|
|
|
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-16 00:17:08 +02:00
|
|
|
SshCertRequested {
|
|
|
|
|
request: SshCertRequest,
|
|
|
|
|
},
|
|
|
|
|
SshCertRequests {
|
|
|
|
|
requests: Vec<SshCertRequest>,
|
|
|
|
|
},
|
|
|
|
|
SshCertApproved {
|
|
|
|
|
approval: SshCertApproval,
|
|
|
|
|
},
|
|
|
|
|
SshCertImported {
|
|
|
|
|
certificate: SshCertificateRecord,
|
|
|
|
|
},
|
|
|
|
|
SshCertList {
|
|
|
|
|
requests: Vec<SshCertRequest>,
|
|
|
|
|
certificates: Vec<SshCertificateRecord>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationAdded {
|
|
|
|
|
revocation: SshRevocationEntry,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationList {
|
|
|
|
|
revocations: Vec<SshRevocationEntry>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationExported {
|
|
|
|
|
out: PathBuf,
|
2026-05-17 18:29:47 +02:00
|
|
|
format: String,
|
2026-05-16 00:17:08 +02:00
|
|
|
count: usize,
|
2026-05-17 18:29:47 +02:00
|
|
|
note: String,
|
2026-05-16 00:17:08 +02:00
|
|
|
},
|
2026-05-18 11:56:42 +02:00
|
|
|
SshRevocationImported {
|
|
|
|
|
revocations: Vec<SshRevocationEntry>,
|
|
|
|
|
format: String,
|
|
|
|
|
count: usize,
|
|
|
|
|
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-16 21:52:35 +02:00
|
|
|
KvCreated {
|
|
|
|
|
kv: KvResource,
|
|
|
|
|
},
|
|
|
|
|
KvSet {
|
|
|
|
|
entry: KvEntry,
|
|
|
|
|
},
|
|
|
|
|
KvGet {
|
|
|
|
|
entry: Option<KvEntry>,
|
|
|
|
|
},
|
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-17 18:23:51 +02:00
|
|
|
PubsubPublished {
|
|
|
|
|
message: PubsubMessage,
|
|
|
|
|
},
|
|
|
|
|
PubsubMessages {
|
|
|
|
|
topic: String,
|
|
|
|
|
messages: Vec<PubsubMessage>,
|
|
|
|
|
note: String,
|
|
|
|
|
},
|
2026-05-17 20:17:26 +02:00
|
|
|
PipeListening {
|
|
|
|
|
listener: PipeListener,
|
|
|
|
|
},
|
|
|
|
|
PipeConnected {
|
|
|
|
|
connection: PipeConnection,
|
|
|
|
|
},
|
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-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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
|
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-18 12:09:50 +02:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
|
|
|
|
pub enum PeerControlRequest {
|
|
|
|
|
Ping { peer_card: PeerCard, 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,
|
|
|
|
|
},
|
|
|
|
|
Error {
|
|
|
|
|
message: 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-15 15:08:20 +02:00
|
|
|
#[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
|
|
|
|
|
);
|
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,
|
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(),
|
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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(),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
|
|
|
|
request
|
|
|
|
|
);
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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-15 15:08:20 +02:00
|
|
|
}
|
|
|
|
|
}
|