use anyhow::{bail, Result};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use zeroize::Zeroize;
use keyquorum_core::protocol::{ActionResult, ClientMessage, DaemonMessage};
use keyquorum_core::types::ShareSubmission;
pub async fn run(user: Option<String>, socket: String) -> Result<()> {
let share_input = {
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
eprintln!("Enter share (then press Enter twice, or Ctrl+D):");
}
let mut buf = String::new();
let mut saw_content = false;
let mut in_envelope = false;
let mut saw_envelope_blank = false;
loop {
let mut line = String::new();
let n = std::io::stdin().read_line(&mut line)?;
if n == 0 {
break; }
let trimmed = line.trim();
if trimmed.starts_with("KEYQUORUM-SHARE-") {
in_envelope = true;
}
if trimmed.is_empty() {
if in_envelope && !saw_envelope_blank {
saw_envelope_blank = true;
buf.push_str(&line);
continue;
}
if saw_content {
break; }
continue; }
saw_content = true;
buf.push_str(&line);
}
buf.trim().to_string()
};
if share_input.is_empty() {
bail!("no share data provided");
}
let parsed = keyquorum_core::share_format::parse_share(&share_input)
.map_err(|e| anyhow::anyhow!("invalid share: {}", e))?;
if parsed.malformed_envelope {
eprintln!(
"warning: share extracted from malformed envelope (missing marker or headers)"
);
}
let index = parsed.index;
if sharks::Share::try_from(parsed.sharks_data.as_slice()).is_err() {
bail!("invalid share data");
}
let mut json = {
let msg = ClientMessage::SubmitShare {
share: ShareSubmission {
index,
data: share_input,
submitted_by: user,
},
};
let mut j = serde_json::to_string(&msg)?;
j.push('\n');
j
};
let conn = super::connect(&socket).await?;
let (reader, mut writer) = conn.split();
writer.write_all(json.as_bytes()).await?;
writer.flush().await?;
json.zeroize();
let mut lines = BufReader::new(reader).lines();
if let Some(line) = lines.next_line().await? {
let response: DaemonMessage = serde_json::from_str(&line)?;
print_response(&response);
} else {
bail!("daemon closed connection without responding");
}
Ok(())
}
fn print_response(msg: &DaemonMessage) {
match msg {
DaemonMessage::ShareAccepted { status } => {
eprintln!("Share accepted.");
eprintln!(" Shares: {}/{}", status.shares_received, status.threshold);
if status.shares_needed > 0 {
eprintln!(" Still need {} more share(s)", status.shares_needed);
} else {
eprintln!(" Quorum reached!");
}
let remaining = status.timeout_secs.saturating_sub(status.elapsed_secs);
eprintln!(" Time remaining: {}m {}s", remaining / 60, remaining % 60);
}
DaemonMessage::ShareRejected { reason } => {
eprintln!("Share rejected: {}", reason);
std::process::exit(1);
}
DaemonMessage::QuorumReached { action_result } => match action_result {
ActionResult::Success { message } => {
eprintln!("Quorum reached! Action succeeded: {}", message);
}
ActionResult::Failure { message } => {
eprintln!("Quorum reached but action failed: {}", message);
std::process::exit(1);
}
},
DaemonMessage::Error { message } => {
eprintln!("Error: {}", message);
std::process::exit(1);
}
DaemonMessage::Status { status } => {
eprintln!("Status: {:?}", status.state);
}
}
}