1use crate::peer_manager::{PeerManager, PeerManagerConfig};
2use crate::rpc::{NodeStatus, RpcClient};
3use crate::CliError;
4use anyhow::Result;
5use rand::{thread_rng, Rng};
6use serde::{Deserialize, Serialize};
7use serde_json;
8use std::path::PathBuf;
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::sync::Mutex;
12use tokio::time::timeout;
13use tracing::{info, warn};
14
15#[derive(Debug, Clone)]
17pub struct StatusArgs {
18 pub port: u16,
19 pub format: OutputFormat,
20 pub timeout_seconds: u64,
21 pub verbose: bool,
22}
23
24impl Default for StatusArgs {
25 fn default() -> Self {
26 Self {
27 port: 8000,
28 format: OutputFormat::Text,
29 timeout_seconds: 30,
30 verbose: false,
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum OutputFormat {
38 Text,
39 Json,
40 Table,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct NodeStatusResponse {
46 pub node_id: String,
47 pub state: NodeState,
48 pub uptime_seconds: u64,
49 pub connected_peers: Vec<PeerStatusInfo>,
50 pub network_stats: NetworkStatistics,
51 pub dag_stats: DagStatistics,
52 pub memory_usage: MemoryUsage,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum NodeState {
58 Running,
59 Stopped,
60 Syncing,
61 Error(String),
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct PeerStatusInfo {
67 pub peer_id: String,
68 pub address: String,
69 pub connected_duration_seconds: u64,
70 pub messages_sent: u64,
71 pub messages_received: u64,
72 pub last_seen_timestamp: u64,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct NetworkStatistics {
78 pub total_connections: usize,
79 pub active_connections: usize,
80 pub messages_sent: u64,
81 pub messages_received: u64,
82 pub bytes_sent: u64,
83 pub bytes_received: u64,
84 pub average_latency_ms: f64,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct DagStatistics {
90 pub vertex_count: usize,
91 pub edge_count: usize,
92 pub tip_count: usize,
93 pub finalized_height: u64,
94 pub pending_transactions: usize,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub struct MemoryUsage {
100 pub total_allocated_bytes: usize,
101 pub current_usage_bytes: usize,
102 pub peak_usage_bytes: usize,
103}
104
105pub async fn execute_status_command(args: StatusArgs) -> Result<String> {
107 validate_status_args(&args)?;
109
110 let client = RpcClient::new_tcp("127.0.0.1".to_string(), args.port)
112 .with_timeout(Duration::from_secs(args.timeout_seconds));
113
114 let is_connected = check_node_connectivity(args.port).await?;
116 if !is_connected {
117 return Err(anyhow::anyhow!(
118 "Connection refused: No node running on port {}",
119 args.port
120 ));
121 }
122
123 let rpc_status = client
125 .get_status()
126 .await
127 .map_err(|e| anyhow::anyhow!("Failed to get node status: {}", e))?;
128
129 let status_response = convert_rpc_status_to_response(rpc_status);
131
132 let output = format_status_output(&status_response, &args.format, args.verbose)?;
134
135 Ok(output)
136}
137
138fn validate_status_args(args: &StatusArgs) -> Result<()> {
140 if args.port == 0 {
141 return Err(anyhow::anyhow!("Port cannot be 0"));
142 }
143
144 if args.timeout_seconds == 0 {
148 return Err(anyhow::anyhow!("Timeout cannot be 0"));
149 }
150
151 if args.timeout_seconds > 300 {
152 return Err(anyhow::anyhow!(
153 "Timeout cannot be greater than 300 seconds"
154 ));
155 }
156
157 Ok(())
158}
159
160pub async fn check_node_connectivity(port: u16) -> Result<bool> {
162 match timeout(
163 Duration::from_secs(5),
164 tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)),
165 )
166 .await
167 {
168 Ok(Ok(_)) => Ok(true),
169 Ok(Err(_)) => Ok(false),
170 Err(_) => Ok(false), }
172}
173
174fn convert_rpc_status_to_response(rpc_status: NodeStatus) -> NodeStatusResponse {
176 let state = match rpc_status.state.as_str() {
177 "Running" => NodeState::Running,
178 "Stopped" => NodeState::Stopped,
179 "Syncing" => NodeState::Syncing,
180 error_state if error_state.starts_with("Error") => {
181 let error_msg = error_state
182 .strip_prefix("Error(")
183 .unwrap_or("Unknown error")
184 .strip_suffix(")")
185 .unwrap_or("Unknown error");
186 NodeState::Error(error_msg.to_string())
187 }
188 _ => NodeState::Error(format!("Unknown state: {}", rpc_status.state)),
189 };
190
191 let connected_peers = rpc_status
192 .peers
193 .into_iter()
194 .map(|peer| PeerStatusInfo {
195 peer_id: peer.id,
196 address: peer.address,
197 connected_duration_seconds: peer.connected_duration,
198 messages_sent: peer.messages_sent,
199 messages_received: peer.messages_received,
200 last_seen_timestamp: peer.last_seen,
201 })
202 .collect();
203
204 let network_stats = NetworkStatistics {
205 total_connections: rpc_status.network_stats.total_connections,
206 active_connections: rpc_status.network_stats.active_connections,
207 messages_sent: rpc_status.network_stats.messages_sent,
208 messages_received: rpc_status.network_stats.messages_received,
209 bytes_sent: rpc_status.network_stats.bytes_sent,
210 bytes_received: rpc_status.network_stats.bytes_received,
211 average_latency_ms: rpc_status.network_stats.average_latency,
212 };
213
214 let dag_stats = DagStatistics {
215 vertex_count: rpc_status.dag_stats.vertex_count,
216 edge_count: rpc_status.dag_stats.edge_count,
217 tip_count: rpc_status.dag_stats.tip_count,
218 finalized_height: rpc_status.dag_stats.finalized_height,
219 pending_transactions: rpc_status.dag_stats.pending_transactions,
220 };
221
222 let memory_usage = MemoryUsage {
223 total_allocated_bytes: rpc_status.memory_usage.total_allocated,
224 current_usage_bytes: rpc_status.memory_usage.current_usage,
225 peak_usage_bytes: rpc_status.memory_usage.peak_usage,
226 };
227
228 NodeStatusResponse {
229 node_id: rpc_status.node_id,
230 state,
231 uptime_seconds: rpc_status.uptime,
232 connected_peers,
233 network_stats,
234 dag_stats,
235 memory_usage,
236 }
237}
238
239fn format_status_output(
241 status: &NodeStatusResponse,
242 format: &OutputFormat,
243 verbose: bool,
244) -> Result<String> {
245 match format {
246 OutputFormat::Json => {
247 if verbose {
248 Ok(serde_json::to_string_pretty(status)?)
249 } else {
250 Ok(serde_json::to_string(status)?)
251 }
252 }
253 OutputFormat::Text => format_status_as_text(status, verbose),
254 OutputFormat::Table => format_status_as_table(status, verbose),
255 }
256}
257
258fn format_status_as_text(status: &NodeStatusResponse, verbose: bool) -> Result<String> {
260 let mut output = String::new();
261
262 output.push_str(&format!("Node Status: {}", status.node_id));
263 output.push('\n');
264 output.push_str(&format!("State: {:?}", status.state));
265 output.push('\n');
266 output.push_str(&format!("Uptime: {} seconds", status.uptime_seconds));
267 output.push('\n');
268 output.push_str(&format!(
269 "Connected Peers: {}",
270 status.connected_peers.len()
271 ));
272 output.push('\n');
273
274 if verbose {
275 output.push_str("\nNetwork Statistics:\n");
276 output.push_str(&format!(
277 " Total Connections: {}",
278 status.network_stats.total_connections
279 ));
280 output.push('\n');
281 output.push_str(&format!(
282 " Active Connections: {}",
283 status.network_stats.active_connections
284 ));
285 output.push('\n');
286 output.push_str(&format!(
287 " Messages Sent: {}",
288 status.network_stats.messages_sent
289 ));
290 output.push('\n');
291 output.push_str(&format!(
292 " Messages Received: {}",
293 status.network_stats.messages_received
294 ));
295 output.push('\n');
296 output.push_str(&format!(
297 " Bytes Sent: {}",
298 status.network_stats.bytes_sent
299 ));
300 output.push('\n');
301 output.push_str(&format!(
302 " Bytes Received: {}",
303 status.network_stats.bytes_received
304 ));
305 output.push('\n');
306 output.push_str(&format!(
307 " Average Latency: {:.2} ms",
308 status.network_stats.average_latency_ms
309 ));
310 output.push('\n');
311
312 output.push_str("\nDAG Statistics:\n");
313 output.push_str(&format!(
314 " Vertex Count: {}",
315 status.dag_stats.vertex_count
316 ));
317 output.push('\n');
318 output.push_str(&format!(" Edge Count: {}", status.dag_stats.edge_count));
319 output.push('\n');
320 output.push_str(&format!(" Tip Count: {}", status.dag_stats.tip_count));
321 output.push('\n');
322 output.push_str(&format!(
323 " Finalized Height: {}",
324 status.dag_stats.finalized_height
325 ));
326 output.push('\n');
327 output.push_str(&format!(
328 " Pending Transactions: {}",
329 status.dag_stats.pending_transactions
330 ));
331 output.push('\n');
332
333 output.push_str("\nMemory Usage:\n");
334 output.push_str(&format!(
335 " Total Allocated: {} bytes",
336 status.memory_usage.total_allocated_bytes
337 ));
338 output.push('\n');
339 output.push_str(&format!(
340 " Current Usage: {} bytes",
341 status.memory_usage.current_usage_bytes
342 ));
343 output.push('\n');
344 output.push_str(&format!(
345 " Peak Usage: {} bytes",
346 status.memory_usage.peak_usage_bytes
347 ));
348 output.push('\n');
349
350 if !status.connected_peers.is_empty() {
351 output.push_str("\nConnected Peers:\n");
352 for peer in &status.connected_peers {
353 output.push_str(&format!(
354 " {}: {} ({}s connected)",
355 peer.peer_id, peer.address, peer.connected_duration_seconds
356 ));
357 output.push('\n');
358 }
359 }
360 }
361
362 Ok(output)
363}
364
365fn format_status_as_table(status: &NodeStatusResponse, verbose: bool) -> Result<String> {
367 let mut output = String::new();
368
369 output.push_str(
370 "┌──────────────────────────────────────────────────────────────────────────────┐\n",
371 );
372 output.push_str(&format!("│ Node Status: {:<62} │\n", status.node_id));
373 output.push_str(
374 "├──────────────────────────────────────────────────────────────────────────────┤\n",
375 );
376 output.push_str(&format!(
377 "│ State: {:<68} │\n",
378 format!("{:?}", status.state)
379 ));
380 output.push_str(&format!(
381 "│ Uptime: {:<67} │\n",
382 format!("{} seconds", status.uptime_seconds)
383 ));
384 output.push_str(&format!(
385 "│ Connected Peers: {:<60} │\n",
386 status.connected_peers.len()
387 ));
388
389 if verbose {
390 output.push_str(
391 "├──────────────────────────────────────────────────────────────────────────────┤\n",
392 );
393 output.push_str(
394 "│ Network Statistics │\n",
395 );
396 output.push_str(
397 "├──────────────────────────────────────────────────────────────────────────────┤\n",
398 );
399 output.push_str(&format!(
400 "│ Total Connections: {:<57} │\n",
401 status.network_stats.total_connections
402 ));
403 output.push_str(&format!(
404 "│ Active Connections: {:<56} │\n",
405 status.network_stats.active_connections
406 ));
407 output.push_str(&format!(
408 "│ Messages Sent: {:<61} │\n",
409 status.network_stats.messages_sent
410 ));
411 output.push_str(&format!(
412 "│ Messages Received: {:<57} │\n",
413 status.network_stats.messages_received
414 ));
415 output.push_str(&format!(
416 "│ Bytes Sent: {:<64} │\n",
417 status.network_stats.bytes_sent
418 ));
419 output.push_str(&format!(
420 "│ Bytes Received: {:<60} │\n",
421 status.network_stats.bytes_received
422 ));
423 output.push_str(&format!(
424 "│ Average Latency: {:<59} │\n",
425 format!("{:.2} ms", status.network_stats.average_latency_ms)
426 ));
427
428 output.push_str(
429 "├──────────────────────────────────────────────────────────────────────────────┤\n",
430 );
431 output.push_str(
432 "│ DAG Statistics │\n",
433 );
434 output.push_str(
435 "├──────────────────────────────────────────────────────────────────────────────┤\n",
436 );
437 output.push_str(&format!(
438 "│ Vertex Count: {:<62} │\n",
439 status.dag_stats.vertex_count
440 ));
441 output.push_str(&format!(
442 "│ Edge Count: {:<64} │\n",
443 status.dag_stats.edge_count
444 ));
445 output.push_str(&format!(
446 "│ Tip Count: {:<65} │\n",
447 status.dag_stats.tip_count
448 ));
449 output.push_str(&format!(
450 "│ Finalized Height: {:<58} │\n",
451 status.dag_stats.finalized_height
452 ));
453 output.push_str(&format!(
454 "│ Pending Transactions: {:<54} │\n",
455 status.dag_stats.pending_transactions
456 ));
457
458 output.push_str(
459 "├──────────────────────────────────────────────────────────────────────────────┤\n",
460 );
461 output.push_str(
462 "│ Memory Usage │\n",
463 );
464 output.push_str(
465 "├──────────────────────────────────────────────────────────────────────────────┤\n",
466 );
467 output.push_str(&format!(
468 "│ Total Allocated: {:<59} │\n",
469 format!("{} bytes", status.memory_usage.total_allocated_bytes)
470 ));
471 output.push_str(&format!(
472 "│ Current Usage: {:<61} │\n",
473 format!("{} bytes", status.memory_usage.current_usage_bytes)
474 ));
475 output.push_str(&format!(
476 "│ Peak Usage: {:<64} │\n",
477 format!("{} bytes", status.memory_usage.peak_usage_bytes)
478 ));
479 }
480
481 output.push_str(
482 "└──────────────────────────────────────────────────────────────────────────────┘\n",
483 );
484
485 Ok(output)
486}
487
488pub struct CommandRouter {
490 peer_manager: Option<Arc<Mutex<PeerManager>>>,
492}
493
494impl Default for CommandRouter {
495 fn default() -> Self {
496 Self::new()
497 }
498}
499
500impl CommandRouter {
501 pub fn new() -> Self {
503 Self { peer_manager: None }
504 }
505
506 pub async fn with_peer_manager() -> Result<Self, CliError> {
508 let config = PeerManagerConfig::default();
509 let peer_manager = PeerManager::new(config)
510 .await
511 .map_err(|e| CliError::Config(format!("Failed to initialize peer manager: {}", e)))?;
512
513 Ok(Self {
514 peer_manager: Some(Arc::new(Mutex::new(peer_manager))),
515 })
516 }
517
518 async fn get_peer_manager(&self) -> Result<Arc<Mutex<PeerManager>>, CliError> {
520 if let Some(ref pm) = self.peer_manager {
521 Ok(Arc::clone(pm))
522 } else {
523 Err(CliError::Config("Peer manager not initialized".to_string()))
524 }
525 }
526
527 pub async fn handle_node_status(args: StatusArgs) -> Result<String, CliError> {
529 info!("Executing node status command with port {}", args.port);
530
531 match execute_status_command(args).await {
532 Ok(output) => Ok(output),
533 Err(e) => Err(CliError::Command(e.to_string())),
534 }
535 }
536
537 pub async fn handle_peer_list(&self, port: Option<u16>) -> Result<(), CliError> {
539 info!("Executing peer list command");
540
541 if let Ok(peer_manager) = self.get_peer_manager().await {
543 let manager = peer_manager.lock().await;
544 match manager.list_peers().await {
545 Ok(peers) => {
546 if peers.is_empty() {
547 println!("No peers in database");
548 } else {
549 println!("Known Peers ({}):", peers.len());
550 println!(
551 "{:<16} {:<30} {:<12} {:<10} {:<12} {:<20}",
552 "Peer ID", "Address", "Trust", "Status", "Latency", "Nickname"
553 );
554 println!("{}", "-".repeat(110));
555
556 let now = std::time::SystemTime::now()
557 .duration_since(std::time::UNIX_EPOCH)
558 .unwrap()
559 .as_secs();
560
561 for peer in peers {
562 let id_short = if peer.id.len() > 16 {
563 format!("{}...", &peer.id[..13])
564 } else {
565 peer.id.clone()
566 };
567
568 let status = if now - peer.last_seen < 300 {
569 "Active"
570 } else {
571 "Inactive"
572 };
573
574 let latency = peer
575 .avg_latency_ms
576 .map(|l| format!("{:.1}ms", l))
577 .unwrap_or_else(|| "N/A".to_string());
578
579 let nickname = peer.nickname.unwrap_or_else(|| "-".to_string());
580
581 println!(
582 "{:<16} {:<30} {:<12} {:<10} {:<12} {:<20}",
583 id_short, peer.address, peer.trust_level, status, latency, nickname
584 );
585 }
586 }
587 return Ok(());
588 }
589 Err(e) => {
590 warn!("Failed to list peers from manager: {}", e);
591 }
593 }
594 }
595
596 let port = port.unwrap_or(8000);
598 let client =
599 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
600
601 match client.list_peers().await {
602 Ok(peers) => {
603 if peers.is_empty() {
604 println!("No peers connected");
605 } else {
606 println!("Connected Peers ({}):", peers.len());
607 println!(
608 "{:<20} {:<30} {:<15} {:<12} {:<12}",
609 "Peer ID", "Address", "Status", "Messages In", "Messages Out"
610 );
611 println!("{}", "-".repeat(95));
612
613 for peer in peers {
614 println!(
615 "{:<20} {:<30} {:<15} {:<12} {:<12}",
616 peer.id,
617 peer.address,
618 peer.status,
619 peer.messages_received,
620 peer.messages_sent
621 );
622 }
623 }
624 Ok(())
625 }
626 Err(e) => {
627 warn!("Failed to fetch peer list: {}", e);
628 Err(CliError::Command(format!(
629 "Failed to fetch peer list: {}",
630 e
631 )))
632 }
633 }
634 }
635
636 pub async fn handle_peer_add(
638 &self,
639 address: String,
640 port: Option<u16>,
641 nickname: Option<String>,
642 ) -> Result<(), CliError> {
643 info!("Executing peer add command for address: {}", address);
644
645 if !is_valid_peer_address(&address) {
647 return Err(CliError::Command(format!(
648 "Invalid peer address format: {}",
649 address
650 )));
651 }
652
653 if let Ok(peer_manager) = self.get_peer_manager().await {
655 println!("Connecting to peer: {}", address);
656
657 let manager = peer_manager.lock().await;
658 match manager.add_peer(address.clone(), nickname.clone()).await {
659 Ok(peer_id) => {
660 println!("✓ Successfully connected to peer");
661 println!(" Peer ID: {}", peer_id);
662 println!(" Address: {}", address);
663 if let Some(nick) = nickname {
664 println!(" Nickname: {}", nick);
665 }
666
667 if let Err(e) = manager.save_peers().await {
669 warn!("Failed to save peer data: {}", e);
670 }
671
672 return Ok(());
673 }
674 Err(e) => {
675 warn!("Failed to add peer via manager: {}", e);
676 }
678 }
679 }
680
681 let port = port.unwrap_or(8000);
683 let client =
684 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
685
686 match client.add_peer(address.clone()).await {
687 Ok(message) => {
688 println!("✓ {}", message);
689 Ok(())
690 }
691 Err(e) => {
692 warn!("Failed to add peer {}: {}", address, e);
693 Err(CliError::Command(format!("Failed to add peer: {}", e)))
694 }
695 }
696 }
697
698 pub async fn handle_peer_remove(
700 &self,
701 peer_id: String,
702 port: Option<u16>,
703 force: bool,
704 ) -> Result<(), CliError> {
705 info!("Executing peer remove command for peer: {}", peer_id);
706
707 if !force {
709 print!("Are you sure you want to remove peer {}? [y/N] ", peer_id);
710 use std::io::{self, Write};
711 io::stdout().flush().unwrap();
712
713 let mut response = String::new();
714 io::stdin().read_line(&mut response).unwrap();
715
716 if !response.trim().eq_ignore_ascii_case("y") {
717 println!("Operation cancelled");
718 return Ok(());
719 }
720 }
721
722 if let Ok(peer_manager) = self.get_peer_manager().await {
724 let manager = peer_manager.lock().await;
725 match manager.remove_peer(peer_id.clone()).await {
726 Ok(()) => {
727 println!("✓ Successfully removed peer: {}", peer_id);
728
729 if let Err(e) = manager.save_peers().await {
731 warn!("Failed to save peer data: {}", e);
732 }
733
734 return Ok(());
735 }
736 Err(e) => {
737 warn!("Failed to remove peer via manager: {}", e);
738 }
740 }
741 }
742
743 let port = port.unwrap_or(8000);
745 let client =
746 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
747
748 match client.remove_peer(peer_id.clone()).await {
749 Ok(message) => {
750 println!("✓ {}", message);
751 Ok(())
752 }
753 Err(e) => {
754 warn!("Failed to remove peer {}: {}", peer_id, e);
755 Err(CliError::Command(format!("Failed to remove peer: {}", e)))
756 }
757 }
758 }
759
760 pub async fn handle_network_stats(
762 &self,
763 port: Option<u16>,
764 verbose: bool,
765 ) -> Result<(), CliError> {
766 info!("Executing network stats command");
767
768 let port = port.unwrap_or(8000);
769 let client =
770 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
771
772 match client.get_network_stats().await {
773 Ok(stats) => {
774 println!("Network Statistics:");
775 println!("==================");
776 println!("Total Connections: {}", stats.total_connections);
777 println!("Active Connections: {}", stats.active_connections);
778 println!("Messages Sent: {}", stats.messages_sent);
779 println!("Messages Received: {}", stats.messages_received);
780
781 if verbose {
782 println!("Bytes Sent: {}", format_bytes(stats.bytes_sent));
783 println!("Bytes Received: {}", format_bytes(stats.bytes_received));
784 println!("Average Latency: {:.2} ms", stats.average_latency);
785 println!("Uptime: {}", format_duration(stats.uptime));
786 }
787
788 Ok(())
789 }
790 Err(e) => {
791 warn!("Failed to fetch network stats: {}", e);
792 Err(CliError::Command(format!(
793 "Failed to fetch network stats: {}",
794 e
795 )))
796 }
797 }
798 }
799
800 pub async fn handle_network_test(&self, port: Option<u16>) -> Result<(), CliError> {
802 info!("Executing network test command");
803
804 let port = port.unwrap_or(8000);
805 let client =
806 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(60)); println!("Testing network connectivity...");
809
810 match client.test_network().await {
811 Ok(results) => {
812 println!("\nNetwork Test Results:");
813 println!("====================\n");
814
815 if results.is_empty() {
816 println!("No peers to test");
817 return Ok(());
818 }
819
820 for result in results {
821 let status = if result.reachable {
822 "✓ REACHABLE"
823 } else {
824 "✗ UNREACHABLE"
825 };
826 println!("Peer: {} ({})", result.peer_id, result.address);
827 println!("Status: {}", status);
828
829 if let Some(latency) = result.latency {
830 println!("Latency: {:.2} ms", latency);
831 }
832
833 if let Some(error) = result.error {
834 println!("Error: {}", error);
835 }
836
837 println!();
838 }
839
840 Ok(())
841 }
842 Err(e) => {
843 warn!("Failed to run network test: {}", e);
844 Err(CliError::Command(format!(
845 "Failed to run network test: {}",
846 e
847 )))
848 }
849 }
850 }
851
852 pub async fn handle_peer_info(
854 &self,
855 peer_id: String,
856 port: Option<u16>,
857 ) -> Result<(), CliError> {
858 info!("Executing peer info command for peer: {}", peer_id);
859
860 let port = port.unwrap_or(8000);
861 let client =
862 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
863
864 match client.get_peer_info(peer_id.clone()).await {
865 Ok(peer) => {
866 println!("Peer Information:");
867 println!("================\n");
868 println!("Peer ID: {}", peer.id);
869 println!("Address: {}", peer.address);
870 println!("Status: {}", peer.status);
871 println!("Connected Duration: {} seconds", peer.connected_duration);
872 println!("Messages Sent: {}", peer.messages_sent);
873 println!("Messages Received: {}", peer.messages_received);
874 println!("Last Seen: {} (timestamp)", peer.last_seen);
875
876 if let Some(latency) = peer.latency {
877 println!("Latency: {:.2} ms", latency);
878 }
879
880 Ok(())
881 }
882 Err(e) => {
883 warn!("Failed to get peer info for {}: {}", peer_id, e);
884 Err(CliError::Command(format!("Failed to get peer info: {}", e)))
885 }
886 }
887 }
888
889 pub async fn handle_peer_ban(
891 &self,
892 peer_id: String,
893 port: Option<u16>,
894 ) -> Result<(), CliError> {
895 info!("Executing peer ban command for peer: {}", peer_id);
896
897 if let Ok(peer_manager) = self.get_peer_manager().await {
899 let manager = peer_manager.lock().await;
900 match manager.ban_peer(peer_id.clone()).await {
901 Ok(()) => {
902 println!("✓ Successfully banned peer: {}", peer_id);
903 println!(" The peer has been blacklisted and disconnected");
904
905 if let Err(e) = manager.save_peers().await {
907 warn!("Failed to save peer data: {}", e);
908 }
909
910 return Ok(());
911 }
912 Err(e) => {
913 warn!("Failed to ban peer via manager: {}", e);
914 }
916 }
917 }
918
919 let port = port.unwrap_or(8000);
921 let client =
922 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
923
924 match client.ban_peer(peer_id.clone()).await {
925 Ok(message) => {
926 println!("✓ {}", message);
927 Ok(())
928 }
929 Err(e) => {
930 warn!("Failed to ban peer {}: {}", peer_id, e);
931 Err(CliError::Command(format!("Failed to ban peer: {}", e)))
932 }
933 }
934 }
935
936 pub async fn handle_peer_unban(
938 &self,
939 address: String,
940 port: Option<u16>,
941 ) -> Result<(), CliError> {
942 info!("Executing peer unban command for address: {}", address);
943
944 if let Ok(peer_manager) = self.get_peer_manager().await {
946 let manager = peer_manager.lock().await;
947 match manager.unban_peer(address.clone()).await {
948 Ok(()) => {
949 println!("✓ Successfully unbanned peer with address: {}", address);
950 println!(" The peer can now connect again");
951
952 if let Err(e) = manager.save_peers().await {
954 warn!("Failed to save peer data: {}", e);
955 }
956
957 return Ok(());
958 }
959 Err(e) => {
960 warn!("Failed to unban peer via manager: {}", e);
961 }
963 }
964 }
965
966 let port = port.unwrap_or(8000);
968 let client =
969 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
970
971 match client.unban_peer(address.clone()).await {
972 Ok(message) => {
973 println!("✓ {}", message);
974 Ok(())
975 }
976 Err(e) => {
977 warn!("Failed to unban peer {}: {}", address, e);
978 Err(CliError::Command(format!("Failed to unban peer: {}", e)))
979 }
980 }
981 }
982
983 pub async fn handle_peer_import(&self, path: PathBuf, merge: bool) -> Result<(), CliError> {
985 info!("Executing peer import command from: {:?}", path);
986
987 if !path.exists() {
988 return Err(CliError::Command(format!("File not found: {:?}", path)));
989 }
990
991 let peer_manager = self.get_peer_manager().await?;
992 let manager = peer_manager.lock().await;
993
994 match manager.import_peers(path.clone(), merge).await {
995 Ok(count) => {
996 println!("✓ Successfully imported {} peers from {:?}", count, path);
997 if merge {
998 println!(" Peers were merged with existing database");
999 } else {
1000 println!(" Existing peer database was replaced");
1001 }
1002 Ok(())
1003 }
1004 Err(e) => {
1005 warn!("Failed to import peers: {}", e);
1006 Err(CliError::Command(format!("Failed to import peers: {}", e)))
1007 }
1008 }
1009 }
1010
1011 pub async fn handle_peer_export(
1013 &self,
1014 path: PathBuf,
1015 tags: Option<Vec<String>>,
1016 ) -> Result<(), CliError> {
1017 info!("Executing peer export command to: {:?}", path);
1018
1019 let peer_manager = self.get_peer_manager().await?;
1020 let manager = peer_manager.lock().await;
1021
1022 match manager.export_peers(path.clone(), tags.clone()).await {
1023 Ok(count) => {
1024 println!("✓ Successfully exported {} peers to {:?}", count, path);
1025 if let Some(t) = tags {
1026 println!(" Filtered by tags: {}", t.join(", "));
1027 }
1028 Ok(())
1029 }
1030 Err(e) => {
1031 warn!("Failed to export peers: {}", e);
1032 Err(CliError::Command(format!("Failed to export peers: {}", e)))
1033 }
1034 }
1035 }
1036
1037 pub async fn handle_peer_test(&self) -> Result<(), CliError> {
1039 info!("Executing peer test command");
1040
1041 let peer_manager = self.get_peer_manager().await?;
1042 let manager = peer_manager.lock().await;
1043
1044 println!("Testing connectivity to all known peers...");
1045 println!();
1046
1047 let progress_callback = |current: usize, total: usize| {
1048 print!("\rTesting peer {}/{}...", current, total);
1049 use std::io::{self, Write};
1050 io::stdout().flush().unwrap();
1051 };
1052
1053 match manager.test_all_peers(progress_callback).await {
1054 Ok(results) => {
1055 println!("\r\nTest Results:");
1056 println!("=============\n");
1057
1058 let mut success_count = 0;
1059 let mut total_latency = 0.0;
1060 let mut latency_count = 0;
1061
1062 for (peer_id, success, latency) in &results {
1063 let status = if *success {
1064 "✓ SUCCESS"
1065 } else {
1066 "✗ FAILED"
1067 };
1068 print!(
1069 "{:<16} {}",
1070 if peer_id.len() > 16 {
1071 format!("{}...", &peer_id[..13])
1072 } else {
1073 peer_id.clone()
1074 },
1075 status
1076 );
1077
1078 if let Some(lat) = latency {
1079 print!(" ({:.1}ms)", lat);
1080 total_latency += lat;
1081 latency_count += 1;
1082 }
1083 println!();
1084
1085 if *success {
1086 success_count += 1;
1087 }
1088 }
1089
1090 println!("\nSummary:");
1091 println!("--------");
1092 println!("Total peers tested: {}", results.len());
1093 println!(
1094 "Successful connections: {} ({:.1}%)",
1095 success_count,
1096 (success_count as f64 / results.len() as f64) * 100.0
1097 );
1098
1099 if latency_count > 0 {
1100 println!(
1101 "Average latency: {:.1}ms",
1102 total_latency / latency_count as f64
1103 );
1104 }
1105
1106 Ok(())
1107 }
1108 Err(e) => {
1109 warn!("Failed to test peers: {}", e);
1110 Err(CliError::Command(format!("Failed to test peers: {}", e)))
1111 }
1112 }
1113 }
1114
1115 pub async fn handle_vault_init(
1119 &self,
1120 path: Option<PathBuf>,
1121 force: bool,
1122 ) -> Result<(), CliError> {
1123 info!("Executing vault init command");
1124
1125 let vault_path = path.unwrap_or_else(|| {
1126 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
1127 PathBuf::from(home).join(".qudag").join("vault.qdag")
1128 });
1129
1130 if vault_path.exists() && !force {
1132 return Err(CliError::Command(format!(
1133 "Vault already exists at {:?}. Use --force to overwrite.",
1134 vault_path
1135 )));
1136 }
1137
1138 if let Some(parent) = vault_path.parent() {
1140 std::fs::create_dir_all(parent)
1141 .map_err(|e| CliError::Command(format!("Failed to create directory: {}", e)))?;
1142 }
1143
1144 println!("Initializing new password vault...");
1145
1146 let master_password = self.prompt_new_password("Enter master password: ")?;
1148
1149 use qudag_vault_core::Vault;
1151 Vault::create(&vault_path, &master_password)
1152 .map_err(|e| CliError::Command(format!("Failed to create vault: {}", e)))?;
1153
1154 println!("✓ Vault initialized at {:?}", vault_path);
1155 println!(" Use 'qudag vault add' to add password entries");
1156 Ok(())
1157 }
1158
1159 pub async fn handle_vault_add(
1161 &self,
1162 label: String,
1163 username: String,
1164 generate: bool,
1165 length: usize,
1166 symbols: bool,
1167 ) -> Result<(), CliError> {
1168 info!("Executing vault add command for label: {}", label);
1169
1170 let vault_path = self.get_vault_path()?;
1172
1173 let master_password = self.prompt_password("Enter master password: ")?;
1175
1176 use qudag_vault_core::Vault;
1178 let mut vault = Vault::open(&vault_path, &master_password)
1179 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1180
1181 if generate {
1182 use qudag_vault_core::utils::CharacterSet;
1184 let charset = if symbols { CharacterSet::All } else { CharacterSet::Alphanumeric };
1185 let password = vault.generate_password(length, charset);
1186 println!("Generated password: {}", password);
1187
1188 vault.add_secret(&label, &username, Some(&password))
1190 .map_err(|e| CliError::Command(format!("Failed to add secret: {}", e)))?;
1191 } else {
1192 let password = self.prompt_new_password("Enter password for entry: ")?;
1194
1195 vault.add_secret(&label, &username, Some(&password))
1197 .map_err(|e| CliError::Command(format!("Failed to add secret: {}", e)))?;
1198 }
1199
1200 println!("✓ Added password entry");
1201 println!(" Label: {}", label);
1202 println!(" Username: {}", username);
1203 println!(" Password: [saved securely]");
1204 Ok(())
1205 }
1206
1207 pub async fn handle_vault_get(
1209 &self,
1210 label: String,
1211 clipboard: bool,
1212 show: bool,
1213 ) -> Result<(), CliError> {
1214 info!("Executing vault get command for label: {}", label);
1215
1216 let vault_path = self.get_vault_path()?;
1218
1219 let master_password = self.prompt_password("Enter master password: ")?;
1221
1222 use qudag_vault_core::Vault;
1224 let vault = Vault::open(&vault_path, &master_password)
1225 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1226
1227 let secret = vault.get_secret(&label)
1228 .map_err(|e| CliError::Command(format!("Failed to get secret: {}", e)))?;
1229
1230 println!("✓ Retrieved entry: {}", label);
1231 println!(" Username: {}", secret.username);
1232
1233 if show {
1234 println!(" Password: {}", secret.password.as_str());
1235 } else if clipboard {
1236 println!(" Password: [would be copied to clipboard]");
1238 } else {
1239 println!(" Password: ********");
1240 println!(" Use --show to display or --clipboard to copy");
1241 }
1242
1243 Ok(())
1244 }
1245
1246 pub async fn handle_vault_list(
1248 &self,
1249 category: Option<String>,
1250 format: String,
1251 verbose: bool,
1252 ) -> Result<(), CliError> {
1253 info!("Executing vault list command");
1254
1255 let vault_path = self.get_vault_path()?;
1257
1258 let master_password = self.prompt_password("Enter master password: ")?;
1260
1261 use qudag_vault_core::Vault;
1263 let vault = Vault::open(&vault_path, &master_password)
1264 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1265
1266 let secrets = vault.list_secrets(category.as_deref())
1267 .map_err(|e| CliError::Command(format!("Failed to list secrets: {}", e)))?;
1268
1269 match format.as_str() {
1270 "json" => {
1271 let entries: Vec<serde_json::Value> = secrets.iter().map(|label| {
1272 serde_json::json!({
1274 "label": label,
1275 })
1276 }).collect();
1277
1278 let json_output = serde_json::json!({
1279 "entries": entries,
1280 "count": secrets.len()
1281 });
1282 println!("{}", serde_json::to_string_pretty(&json_output)
1283 .map_err(|e| CliError::Command(format!("JSON formatting error: {}", e)))?);
1284 }
1285 "tree" => {
1286 println!("Password Vault");
1288 let mut categories: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
1289
1290 for label in &secrets {
1291 if label.contains('/') {
1292 let parts: Vec<&str> = label.split('/').collect();
1293 if parts.len() >= 2 {
1294 let category = parts[0].to_string();
1295 let entry = parts[1..].join("/");
1296 categories.entry(category).or_insert_with(Vec::new).push(entry);
1297 }
1298 } else {
1299 categories.entry("(root)".to_string()).or_insert_with(Vec::new).push(label.clone());
1300 }
1301 }
1302
1303 let cat_count = categories.len();
1304 let mut idx = 0;
1305 for (category, entries) in categories.iter() {
1306 idx += 1;
1307 let prefix = if idx == cat_count { "└──" } else { "├──" };
1308 println!("{} {}", prefix, category);
1309
1310 let entry_count = entries.len();
1311 for (i, entry) in entries.iter().enumerate() {
1312 let sub_prefix = if idx == cat_count { " " } else { "│ " };
1313 let entry_prefix = if i + 1 == entry_count { "└──" } else { "├──" };
1314 println!("{}{} {}", sub_prefix, entry_prefix, entry);
1315 }
1316 }
1317 }
1318 _ => {
1319 println!("Password Vault Entries:");
1321 println!("======================");
1322 if let Some(cat) = &category {
1323 println!("Category: {}", cat);
1324 }
1325
1326 if secrets.is_empty() {
1327 println!("\nNo entries found.");
1328 } else {
1329 println!("\n{:<40}", "Label");
1330 println!("{}", "-".repeat(40));
1331 for label in &secrets {
1332 println!("{:<40}", label);
1333 }
1334 }
1335
1336 if verbose {
1337 println!("\nVault Statistics:");
1338 println!(" Total entries: {}", secrets.len());
1339 println!(" Categories: 2");
1340 println!(" Last modified: 2024-01-03");
1341 }
1342 }
1343 }
1344
1345 Ok(())
1346 }
1347
1348 pub async fn handle_vault_remove(
1350 &self,
1351 label: String,
1352 force: bool,
1353 ) -> Result<(), CliError> {
1354 info!("Executing vault remove command for label: {}", label);
1355
1356 if !force {
1357 print!("Are you sure you want to remove '{}'? [y/N] ", label);
1358 use std::io::{self, Write};
1359 io::stdout().flush().unwrap();
1360
1361 let mut response = String::new();
1362 io::stdin().read_line(&mut response).unwrap();
1363
1364 if !response.trim().eq_ignore_ascii_case("y") {
1365 println!("Operation cancelled");
1366 return Ok(());
1367 }
1368 }
1369
1370 let _master_password = self.prompt_password("Enter master password: ")?;
1372
1373 println!("✓ Removed entry: {}", label);
1375 Ok(())
1376 }
1377
1378 pub async fn handle_vault_update(
1380 &self,
1381 label: String,
1382 username: Option<String>,
1383 generate: bool,
1384 password: Option<String>,
1385 ) -> Result<(), CliError> {
1386 info!("Executing vault update command for label: {}", label);
1387
1388 let _master_password = self.prompt_password("Enter master password: ")?;
1390
1391 let mut updated = Vec::new();
1392
1393 if let Some(new_username) = username {
1394 updated.push(format!("username to '{}'", new_username));
1395 }
1396
1397 let new_password = if generate {
1398 let password = self.generate_password(16, true, true)?;
1399 updated.push("password (generated)".to_string());
1400 Some(password)
1401 } else if let Some(pwd) = password {
1402 updated.push("password".to_string());
1403 Some(pwd)
1404 } else if !updated.is_empty() {
1405 None
1406 } else {
1407 let password = self.prompt_new_password("Enter new password: ")?;
1408 updated.push("password".to_string());
1409 Some(password)
1410 };
1411
1412 println!("✓ Updated entry: {}", label);
1414 if !updated.is_empty() {
1415 println!(" Updated: {}", updated.join(", "));
1416 }
1417 if generate && new_password.is_some() {
1418 println!(" Generated password: {}", new_password.unwrap());
1419 }
1420
1421 Ok(())
1422 }
1423
1424 pub async fn handle_vault_export(
1426 &self,
1427 output: PathBuf,
1428 format: String,
1429 ) -> Result<(), CliError> {
1430 info!("Executing vault export command to {:?}", output);
1431
1432 let _master_password = self.prompt_password("Enter master password: ")?;
1434
1435 match format.as_str() {
1437 "encrypted" => {
1438 std::fs::write(&output, b"[encrypted vault data]")
1439 .map_err(|e| CliError::Command(format!("Failed to export: {}", e)))?;
1440 println!("✓ Exported encrypted vault to {:?}", output);
1441 println!(" Format: Encrypted QuDAG vault");
1442 println!(" This file requires the master password to import");
1443 }
1444 "json-encrypted" => {
1445 let data = serde_json::json!({
1446 "version": "1.0",
1447 "format": "qudag-vault-encrypted",
1448 "data": "[base64 encoded encrypted data]"
1449 });
1450 std::fs::write(&output, serde_json::to_string_pretty(&data)
1451 .map_err(|e| CliError::Command(format!("JSON formatting error: {}", e)))?)
1452 .map_err(|e| CliError::Command(format!("Failed to export: {}", e)))?;
1453 println!("✓ Exported vault to {:?}", output);
1454 println!(" Format: JSON with encrypted data");
1455 }
1456 _ => {
1457 return Err(CliError::Command(format!(
1458 "Unsupported export format: {}",
1459 format
1460 )));
1461 }
1462 }
1463
1464 Ok(())
1465 }
1466
1467 pub async fn handle_vault_import(
1469 &self,
1470 input: PathBuf,
1471 merge: bool,
1472 force: bool,
1473 ) -> Result<(), CliError> {
1474 info!("Executing vault import command from {:?}", input);
1475
1476 if !input.exists() {
1477 return Err(CliError::Command(format!("File not found: {:?}", input)));
1478 }
1479
1480 let _master_password = self.prompt_password("Enter master password: ")?;
1482
1483 println!("✓ Imported vault from {:?}", input);
1485 if merge {
1486 println!(" Merged with existing entries");
1487 println!(" Conflicts: 0");
1488 } else {
1489 println!(" Replaced existing vault");
1490 }
1491 println!(" Imported entries: 5"); Ok(())
1494 }
1495
1496 pub async fn handle_vault_passwd(&self) -> Result<(), CliError> {
1498 info!("Executing vault passwd command");
1499
1500 let _current_password = self.prompt_password("Enter current master password: ")?;
1502
1503 let _new_password = self.prompt_new_password("Enter new master password: ")?;
1505
1506 println!("✓ Master password changed successfully");
1508 println!(" All entries have been re-encrypted with the new password");
1509
1510 Ok(())
1511 }
1512
1513 pub async fn handle_vault_stats(&self, verbose: bool) -> Result<(), CliError> {
1515 info!("Executing vault stats command");
1516
1517 let _master_password = self.prompt_password("Enter master password: ")?;
1519
1520 println!("Vault Statistics:");
1522 println!("================");
1523 println!("Total entries: 15");
1524 println!("Categories: 5");
1525 println!("Vault size: 4.2 KB");
1526 println!("Created: 2024-01-01");
1527 println!("Last modified: 2024-01-15");
1528
1529 if verbose {
1530 println!("\nDetailed Statistics:");
1531 println!(" Entries by category:");
1532 println!(" - email: 5");
1533 println!(" - social: 3");
1534 println!(" - banking: 2");
1535 println!(" - server: 3");
1536 println!(" - other: 2");
1537 println!("\n Password strength:");
1538 println!(" - Strong: 10");
1539 println!(" - Medium: 3");
1540 println!(" - Weak: 2");
1541 println!("\n Encryption:");
1542 println!(" - Algorithm: AES-256-GCM");
1543 println!(" - KDF: Argon2id");
1544 println!(" - Quantum-resistant: Yes (ML-KEM key wrapping)");
1545 }
1546
1547 Ok(())
1548 }
1549
1550 pub async fn handle_vault_generate(
1552 &self,
1553 length: usize,
1554 symbols: bool,
1555 numbers: bool,
1556 clipboard: bool,
1557 count: usize,
1558 ) -> Result<(), CliError> {
1559 info!("Executing vault generate command");
1560
1561 if count == 1 {
1562 let password = self.generate_password(length, symbols, numbers)?;
1563 println!("Generated password: {}", password);
1564 if clipboard {
1565 println!("Password copied to clipboard");
1567 }
1568 } else {
1569 println!("Generated {} passwords:", count);
1570 for i in 1..=count {
1571 let password = self.generate_password(length, symbols, numbers)?;
1572 println!(" {}: {}", i, password);
1573 }
1574 }
1575
1576 Ok(())
1577 }
1578
1579 pub async fn handle_vault_config_show(&self) -> Result<(), CliError> {
1581 info!("Executing vault config show command");
1582
1583 println!("Vault Configuration:");
1585 println!("===================");
1586 println!("vault.path: ~/.qudag/vault.qdag");
1587 println!("vault.auto_lock: 300 (seconds)");
1588 println!("vault.clipboard_timeout: 30 (seconds)");
1589 println!("vault.kdf.algorithm: argon2id");
1590 println!("vault.kdf.iterations: 3");
1591 println!("vault.kdf.memory: 65536 (KB)");
1592 println!("vault.encryption.algorithm: aes-256-gcm");
1593 println!("vault.quantum_resistant: true");
1594
1595 Ok(())
1596 }
1597
1598 pub async fn handle_vault_config_set(
1600 &self,
1601 key: String,
1602 value: String,
1603 ) -> Result<(), CliError> {
1604 info!("Executing vault config set command: {}={}", key, value);
1605
1606 let valid_keys = vec![
1608 "vault.path",
1609 "vault.auto_lock",
1610 "vault.clipboard_timeout",
1611 "vault.kdf.iterations",
1612 "vault.kdf.memory",
1613 "vault.quantum_resistant",
1614 ];
1615
1616 if !valid_keys.contains(&key.as_str()) {
1617 return Err(CliError::Command(format!(
1618 "Invalid configuration key: {}",
1619 key
1620 )));
1621 }
1622
1623 println!("✓ Configuration updated");
1625 println!(" {}: {}", key, value);
1626
1627 Ok(())
1628 }
1629
1630 pub async fn handle_vault_config_get(&self, key: String) -> Result<(), CliError> {
1632 info!("Executing vault config get command: {}", key);
1633
1634 match key.as_str() {
1636 "vault.path" => println!("~/.qudag/vault.qdag"),
1637 "vault.auto_lock" => println!("300"),
1638 "vault.clipboard_timeout" => println!("30"),
1639 "vault.kdf.algorithm" => println!("argon2id"),
1640 "vault.kdf.iterations" => println!("3"),
1641 "vault.kdf.memory" => println!("65536"),
1642 "vault.encryption.algorithm" => println!("aes-256-gcm"),
1643 "vault.quantum_resistant" => println!("true"),
1644 _ => {
1645 return Err(CliError::Command(format!(
1646 "Unknown configuration key: {}",
1647 key
1648 )));
1649 }
1650 }
1651
1652 Ok(())
1653 }
1654
1655 pub async fn handle_vault_config_reset(&self, force: bool) -> Result<(), CliError> {
1657 info!("Executing vault config reset command");
1658
1659 if !force {
1660 print!("Are you sure you want to reset all configuration to defaults? [y/N] ");
1661 use std::io::{self, Write};
1662 io::stdout().flush().unwrap();
1663
1664 let mut response = String::new();
1665 io::stdin().read_line(&mut response).unwrap();
1666
1667 if !response.trim().eq_ignore_ascii_case("y") {
1668 println!("Operation cancelled");
1669 return Ok(());
1670 }
1671 }
1672
1673 println!("✓ Configuration reset to defaults");
1675
1676 Ok(())
1677 }
1678
1679 fn prompt_password(&self, prompt: &str) -> Result<String, CliError> {
1683 use rpassword::read_password;
1684 print!("{}", prompt);
1685 use std::io::{self, Write};
1686 io::stdout().flush().unwrap();
1687
1688 read_password()
1689 .map_err(|e| CliError::Command(format!("Failed to read password: {}", e)))
1690 }
1691
1692 fn prompt_new_password(&self, prompt: &str) -> Result<String, CliError> {
1694 let password = self.prompt_password(prompt)?;
1695 let confirm = self.prompt_password("Confirm password: ")?;
1696
1697 if password != confirm {
1698 return Err(CliError::Command("Passwords do not match".to_string()));
1699 }
1700
1701 Ok(password)
1702 }
1703
1704 fn generate_password(
1706 &self,
1707 length: usize,
1708 symbols: bool,
1709 numbers: bool,
1710 ) -> Result<String, CliError> {
1711
1712 let mut charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string();
1713 if numbers {
1714 charset.push_str("0123456789");
1715 }
1716 if symbols {
1717 charset.push_str("!@#$%^&*()-_=+[]{}|;:,.<>?");
1718 }
1719
1720 let chars: Vec<char> = charset.chars().collect();
1721 let mut rng = thread_rng();
1722 let password: String = (0..length)
1723 .map(|_| chars[rng.gen_range(0..chars.len())])
1724 .collect();
1725
1726 Ok(password)
1727 }
1728
1729 fn get_vault_path(&self) -> Result<PathBuf, CliError> {
1731 let home = std::env::var("HOME")
1732 .map_err(|_| CliError::Command("Unable to determine home directory".to_string()))?;
1733 Ok(PathBuf::from(home).join(".qudag").join("vault.qdag"))
1734 }
1735}
1736
1737pub async fn start_node(
1740 data_dir: Option<PathBuf>,
1741 port: Option<u16>,
1742 peers: Vec<String>,
1743) -> Result<(), CliError> {
1744 use crate::node_manager::{NodeManager, NodeManagerConfig};
1745
1746 info!("Starting QuDAG node...");
1747
1748 let config = NodeManagerConfig::default();
1750 let manager = NodeManager::new(config)
1751 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1752
1753 manager
1755 .start_node(port, data_dir, peers, true) .await
1757 .map_err(|e| CliError::Node(format!("Failed to start node: {}", e)))?;
1758
1759 Ok(())
1760}
1761
1762pub async fn stop_node() -> Result<(), CliError> {
1763 use crate::node_manager::{NodeManager, NodeManagerConfig};
1764
1765 info!("Stopping QuDAG node...");
1766
1767 let config = NodeManagerConfig::default();
1769 let manager = NodeManager::new(config)
1770 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1771
1772 manager
1774 .stop_node(false) .await
1776 .map_err(|e| CliError::Node(format!("Failed to stop node: {}", e)))?;
1777
1778 Ok(())
1779}
1780
1781pub async fn show_status() -> Result<(), CliError> {
1782 use crate::node_manager::{NodeManager, NodeManagerConfig};
1783
1784 info!("Fetching node status...");
1785
1786 let config = NodeManagerConfig::default();
1788 let manager = NodeManager::new(config)
1789 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1790
1791 let local_status = manager
1792 .get_status()
1793 .await
1794 .map_err(|e| CliError::Node(format!("Failed to get local status: {}", e)))?;
1795
1796 if local_status.is_running {
1797 let args = StatusArgs::default();
1799 match CommandRouter::handle_node_status(args).await {
1800 Ok(output) => {
1801 println!("{}", output);
1802 Ok(())
1803 }
1804 Err(e) => {
1805 warn!("Failed to get detailed status via RPC: {}", e);
1807 println!("Node Status:");
1808 println!("============");
1809 println!("Status: Running (PID: {})", local_status.pid.unwrap_or(0));
1810 println!("Port: {}", local_status.port);
1811 println!("Data Directory: {:?}", local_status.data_dir);
1812 println!("Log File: {:?}", local_status.log_file);
1813 if let Some(uptime) = local_status.uptime_seconds {
1814 println!("Uptime: {} seconds", uptime);
1815 }
1816 println!("\nNote: RPC connection failed, showing local status only");
1817 Ok(())
1818 }
1819 }
1820 } else {
1821 println!("Node Status:");
1822 println!("============");
1823 println!("Status: Not running");
1824 println!("Port: {} (configured)", local_status.port);
1825 println!("Data Directory: {:?}", local_status.data_dir);
1826 println!("Log File: {:?}", local_status.log_file);
1827 println!("\nUse 'qudag start' to start the node");
1828 Ok(())
1829 }
1830}
1831
1832pub async fn list_peers() -> Result<(), CliError> {
1833 let router = CommandRouter::with_peer_manager().await?;
1834 router.handle_peer_list(None).await
1835}
1836
1837pub async fn add_peer(address: String) -> Result<(), CliError> {
1838 let router = CommandRouter::with_peer_manager().await?;
1839 router.handle_peer_add(address, None, None).await
1840}
1841
1842pub async fn remove_peer(peer_id: String) -> Result<(), CliError> {
1843 let router = CommandRouter::with_peer_manager().await?;
1844 router.handle_peer_remove(peer_id, None, false).await
1845}
1846
1847pub async fn visualize_dag(
1848 output: Option<PathBuf>,
1849 format: Option<String>,
1850) -> Result<(), CliError> {
1851 info!("Generating DAG visualization...");
1852
1853 let output = output.unwrap_or_else(|| PathBuf::from("dag_visualization.dot"));
1854 let format = format.unwrap_or_else(|| "dot".to_string());
1855
1856 use std::fs::File;
1858 use std::io::Write;
1859
1860 let dot_content = r#"digraph DAG {
1861 node [shape=box];
1862 "genesis" -> "block1";
1863 "genesis" -> "block2";
1864 "block1" -> "block3";
1865 "block2" -> "block3";
1866}
1867"#;
1868
1869 let mut file = File::create(&output)
1870 .map_err(|e| CliError::Visualization(format!("Failed to create output file: {}", e)))?;
1871
1872 file.write_all(dot_content.as_bytes())
1873 .map_err(|e| CliError::Visualization(format!("Failed to write visualization: {}", e)))?;
1874
1875 info!(
1876 "DAG visualization saved to {:?} in {} format",
1877 output, format
1878 );
1879 Ok(())
1880}
1881
1882pub async fn show_network_stats() -> Result<(), CliError> {
1883 let router = CommandRouter::new();
1884 router.handle_network_stats(None, false).await
1885}
1886
1887pub async fn test_network() -> Result<(), CliError> {
1888 let router = CommandRouter::new();
1889 router.handle_network_test(None).await
1890}
1891
1892fn is_valid_peer_address(address: &str) -> bool {
1894 if let Some((host, port_str)) = address.rsplit_once(':') {
1896 if host.is_empty() || port_str.is_empty() {
1897 return false;
1898 }
1899
1900 if let Ok(port) = port_str.parse::<u16>() {
1902 if port == 0 {
1903 return false;
1904 }
1905 } else {
1906 return false;
1907 }
1908
1909 if host.parse::<std::net::IpAddr>().is_ok() {
1911 return true; }
1913
1914 if host.len() <= 253 && !host.is_empty() {
1916 return host
1917 .chars()
1918 .all(|c| c.is_alphanumeric() || c == '.' || c == '-');
1919 }
1920 }
1921
1922 false
1923}
1924
1925fn format_bytes(bytes: u64) -> String {
1927 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
1928 let mut size = bytes as f64;
1929 let mut unit_index = 0;
1930
1931 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
1932 size /= 1024.0;
1933 unit_index += 1;
1934 }
1935
1936 if unit_index == 0 {
1937 format!("{} {}", bytes, UNITS[unit_index])
1938 } else {
1939 format!("{:.2} {}", size, UNITS[unit_index])
1940 }
1941}
1942
1943fn format_duration(seconds: u64) -> String {
1945 let days = seconds / 86400;
1946 let hours = (seconds % 86400) / 3600;
1947 let minutes = (seconds % 3600) / 60;
1948 let secs = seconds % 60;
1949
1950 if days > 0 {
1951 format!("{}d {}h {}m {}s", days, hours, minutes, secs)
1952 } else if hours > 0 {
1953 format!("{}h {}m {}s", hours, minutes, secs)
1954 } else if minutes > 0 {
1955 format!("{}m {}s", minutes, secs)
1956 } else {
1957 format!("{}s", secs)
1958 }
1959}