feat: add whoami, health, and check-key CLI subcommands
Three new subcommands for M4 CLI groundwork: - whoami: show local identity key, fingerprint, hybrid key and group status - health: check server connectivity via health RPC - check-key: non-consuming lookup of a peer hybrid public key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,85 @@ pub fn init_auth(ctx: ClientAuth) {
|
||||
|
||||
// -- Subcommand implementations -----------------------------------------------
|
||||
|
||||
/// Print local identity information from the state file (no server connection).
|
||||
pub fn cmd_whoami(state_path: &Path, password: Option<&str>) -> anyhow::Result<()> {
|
||||
let state = load_existing_state(state_path, password)?;
|
||||
let identity = IdentityKeypair::from_seed(state.identity_seed);
|
||||
|
||||
let pk_bytes = identity.public_key_bytes();
|
||||
let fingerprint = sha256(&pk_bytes);
|
||||
|
||||
println!("identity_key : {}", hex::encode(&pk_bytes));
|
||||
println!("fingerprint : {}", hex::encode(&fingerprint));
|
||||
println!(
|
||||
"hybrid_key : {}",
|
||||
if state.hybrid_key.is_some() {
|
||||
"present (X25519 + ML-KEM-768)"
|
||||
} else {
|
||||
"not generated"
|
||||
}
|
||||
);
|
||||
println!(
|
||||
"group : {}",
|
||||
if state.group.is_some() {
|
||||
"active"
|
||||
} else {
|
||||
"none"
|
||||
}
|
||||
);
|
||||
println!("state_file : {}", state_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check server connectivity via the health RPC.
|
||||
pub async fn cmd_health(server: &str, ca_cert: &Path, server_name: &str) -> anyhow::Result<()> {
|
||||
let sent_at = current_timestamp_ms();
|
||||
let client = connect_node(server, ca_cert, server_name).await?;
|
||||
|
||||
let req = client.health_request();
|
||||
let resp = req.send().promise.await.context("health RPC failed")?;
|
||||
|
||||
let status = resp
|
||||
.get()
|
||||
.context("health: bad response")?
|
||||
.get_status()
|
||||
.context("health: missing status")?
|
||||
.to_str()
|
||||
.unwrap_or("invalid");
|
||||
|
||||
let rtt_ms = current_timestamp_ms().saturating_sub(sent_at);
|
||||
|
||||
println!("server : {server}");
|
||||
println!("status : {status}");
|
||||
println!("rtt : {rtt_ms}ms");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a peer identity has registered a hybrid public key (non-consuming).
|
||||
pub async fn cmd_check_key(
|
||||
server: &str,
|
||||
ca_cert: &Path,
|
||||
server_name: &str,
|
||||
identity_key_hex: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let identity_key = decode_identity_key(identity_key_hex)?;
|
||||
let node_client = connect_node(server, ca_cert, server_name).await?;
|
||||
|
||||
let hybrid_pk = fetch_hybrid_key(&node_client, &identity_key).await?;
|
||||
|
||||
println!("identity_key : {identity_key_hex}");
|
||||
println!(
|
||||
"hybrid_key : {}",
|
||||
if hybrid_pk.is_some() {
|
||||
"available (X25519 + ML-KEM-768)"
|
||||
} else {
|
||||
"not found"
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connect to `server`, call health, and print RTT over QUIC/TLS.
|
||||
pub async fn cmd_ping(server: &str, ca_cert: &Path, server_name: &str) -> anyhow::Result<()> {
|
||||
let sent_at = current_timestamp_ms();
|
||||
|
||||
@@ -5,8 +5,9 @@ use std::path::PathBuf;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use quicnprotochat_client::{
|
||||
cmd_create_group, cmd_demo_group, cmd_fetch_key, cmd_invite, cmd_join, cmd_login, cmd_ping,
|
||||
cmd_recv, cmd_register, cmd_register_state, cmd_register_user, cmd_send, init_auth, ClientAuth,
|
||||
cmd_check_key, cmd_create_group, cmd_demo_group, cmd_fetch_key, cmd_health, cmd_invite,
|
||||
cmd_join, cmd_login, cmd_ping, cmd_recv, cmd_register, cmd_register_state, cmd_register_user,
|
||||
cmd_send, cmd_whoami, init_auth, ClientAuth,
|
||||
};
|
||||
|
||||
// ── CLI ───────────────────────────────────────────────────────────────────────
|
||||
@@ -79,6 +80,34 @@ enum Command {
|
||||
password: String,
|
||||
},
|
||||
|
||||
/// Show local identity key, fingerprint, group status, and hybrid key status.
|
||||
Whoami {
|
||||
/// State file path (identity + MLS state).
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "quicnprotochat-state.bin",
|
||||
env = "QUICNPROTOCHAT_STATE"
|
||||
)]
|
||||
state: PathBuf,
|
||||
},
|
||||
|
||||
/// Check server connectivity and print status.
|
||||
Health {
|
||||
/// Server address (host:port).
|
||||
#[arg(long, default_value = "127.0.0.1:7000", env = "QUICNPROTOCHAT_SERVER")]
|
||||
server: String,
|
||||
},
|
||||
|
||||
/// Check if a peer has registered a hybrid key (non-consuming lookup).
|
||||
CheckKey {
|
||||
/// Server address (host:port).
|
||||
#[arg(long, default_value = "127.0.0.1:7000", env = "QUICNPROTOCHAT_SERVER")]
|
||||
server: String,
|
||||
|
||||
/// Peer's Ed25519 identity public key (64 hex chars = 32 bytes).
|
||||
identity_key: String,
|
||||
},
|
||||
|
||||
/// Send a Ping to the server and print the round-trip time.
|
||||
Ping {
|
||||
/// Server address (host:port).
|
||||
@@ -262,6 +291,27 @@ async fn main() -> anyhow::Result<()> {
|
||||
))
|
||||
.await
|
||||
}
|
||||
Command::Whoami { state } => cmd_whoami(&state, state_pw),
|
||||
Command::Health { server } => {
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local
|
||||
.run_until(cmd_health(&server, &args.ca_cert, &args.server_name))
|
||||
.await
|
||||
}
|
||||
Command::CheckKey {
|
||||
server,
|
||||
identity_key,
|
||||
} => {
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local
|
||||
.run_until(cmd_check_key(
|
||||
&server,
|
||||
&args.ca_cert,
|
||||
&args.server_name,
|
||||
&identity_key,
|
||||
))
|
||||
.await
|
||||
}
|
||||
Command::Ping { server } => cmd_ping(&server, &args.ca_cert, &args.server_name).await,
|
||||
Command::Register { server } => {
|
||||
let local = tokio::task::LocalSet::new();
|
||||
|
||||
Reference in New Issue
Block a user