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 -----------------------------------------------
|
// -- 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.
|
/// 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<()> {
|
pub async fn cmd_ping(server: &str, ca_cert: &Path, server_name: &str) -> anyhow::Result<()> {
|
||||||
let sent_at = current_timestamp_ms();
|
let sent_at = current_timestamp_ms();
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ use std::path::PathBuf;
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use quicnprotochat_client::{
|
use quicnprotochat_client::{
|
||||||
cmd_create_group, cmd_demo_group, cmd_fetch_key, cmd_invite, cmd_join, cmd_login, cmd_ping,
|
cmd_check_key, cmd_create_group, cmd_demo_group, cmd_fetch_key, cmd_health, cmd_invite,
|
||||||
cmd_recv, cmd_register, cmd_register_state, cmd_register_user, cmd_send, init_auth, ClientAuth,
|
cmd_join, cmd_login, cmd_ping, cmd_recv, cmd_register, cmd_register_state, cmd_register_user,
|
||||||
|
cmd_send, cmd_whoami, init_auth, ClientAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── CLI ───────────────────────────────────────────────────────────────────────
|
// ── CLI ───────────────────────────────────────────────────────────────────────
|
||||||
@@ -79,6 +80,34 @@ enum Command {
|
|||||||
password: String,
|
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.
|
/// Send a Ping to the server and print the round-trip time.
|
||||||
Ping {
|
Ping {
|
||||||
/// Server address (host:port).
|
/// Server address (host:port).
|
||||||
@@ -262,6 +291,27 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
))
|
))
|
||||||
.await
|
.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::Ping { server } => cmd_ping(&server, &args.ca_cert, &args.server_name).await,
|
||||||
Command::Register { server } => {
|
Command::Register { server } => {
|
||||||
let local = tokio::task::LocalSet::new();
|
let local = tokio::task::LocalSet::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user