# socktop_connector A WebSocket connector library for communicating with socktop agents. ## Overview `socktop_connector` provides a high-level, type-safe interface for connecting to socktop agents over WebSocket connections. It handles connection management, TLS certificate pinning, compression, and protocol buffer decoding automatically. ## Features - **WebSocket Communication**: Support for both `ws://` and `wss://` connections - **TLS Security**: Certificate pinning for secure connections with self-signed certificates - **Hostname Verification**: Configurable hostname verification for TLS connections - **Type Safety**: Strongly typed requests and responses - **Automatic Compression**: Handles gzip compression/decompression transparently - **Protocol Buffer Support**: Decodes binary process data automatically - **Error Handling**: Comprehensive error handling with detailed error messages ## Connection Types ### Non-TLS Connections (`ws://`) Use `connect_to_socktop_agent()` for unencrypted WebSocket connections. ### TLS Connections (`wss://`) Use `connect_to_socktop_agent_with_tls()` for encrypted connections with certificate pinning. You can control hostname verification with the `verify_hostname` parameter. ## Quick Start Add this to your `Cargo.toml`: ```toml [dependencies] socktop_connector = "0.1" tokio = { version = "1", features = ["full"] } ``` ### Basic Usage ```rust use socktop_connector::{connect_to_socktop_agent, AgentRequest, AgentResponse}; #[tokio::main] async fn main() -> Result<(), Box> { // Connect to a socktop agent (non-TLS connections are always unverified) let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?; // Request metrics match connector.request(AgentRequest::Metrics).await? { AgentResponse::Metrics(metrics) => { println!("CPU: {}%, Memory: {}/{}MB", metrics.cpu_total, metrics.mem_used / 1024 / 1024, metrics.mem_total / 1024 / 1024 ); } _ => unreachable!(), } // Request process list match connector.request(AgentRequest::Processes).await? { AgentResponse::Processes(processes) => { println!("Total processes: {}", processes.process_count); for process in processes.top_processes.iter().take(5) { println!(" {} (PID: {}) - CPU: {}%", process.name, process.pid, process.cpu_usage); } } _ => unreachable!(), } Ok(()) } ``` ### TLS with Certificate Pinning ```rust use socktop_connector::{connect_to_socktop_agent_with_tls, AgentRequest}; #[tokio::main] async fn main() -> Result<(), Box> { // Connect with TLS certificate pinning and hostname verification let mut connector = connect_to_socktop_agent_with_tls( "wss://remote-host:8443/ws", "/path/to/cert.pem", false // Enable hostname verification ).await?; let response = connector.request(AgentRequest::Disks).await?; println!("Got disk info: {:?}", response); Ok(()) } ``` ### Advanced Configuration ```rust use socktop_connector::{ConnectorConfig, SocktopConnector, AgentRequest}; #[tokio::main] async fn main() -> Result<(), Box> { // Create a custom configuration let config = ConnectorConfig::new("wss://remote-host:8443/ws") .with_tls_ca("/path/to/cert.pem") .with_hostname_verification(false); // Create and connect let mut connector = SocktopConnector::new(config); connector.connect().await?; // Make requests let response = connector.request(AgentRequest::Metrics).await?; // Clean disconnect connector.disconnect().await?; Ok(()) } ``` ## Continuous Updates The socktop agent provides real-time system metrics. Each request returns the current snapshot, but you can implement continuous monitoring by making requests in a loop: ```rust use socktop_connector::{connect_to_socktop_agent, AgentRequest, AgentResponse}; use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() -> Result<(), Box> { let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?; // Monitor system metrics every 2 seconds loop { match connector.request(AgentRequest::Metrics).await { Ok(AgentResponse::Metrics(metrics)) => { // Calculate total network activity across all interfaces let total_rx: u64 = metrics.networks.iter().map(|n| n.received).sum(); let total_tx: u64 = metrics.networks.iter().map(|n| n.transmitted).sum(); println!("CPU: {:.1}%, Memory: {:.1}%, Network: ↓{} ↑{}", metrics.cpu_total, (metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0, format_bytes(total_rx), format_bytes(total_tx) ); } Err(e) => { eprintln!("Error getting metrics: {}", e); break; } _ => unreachable!(), } sleep(Duration::from_secs(2)).await; } Ok(()) } fn format_bytes(bytes: u64) -> String { const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; let mut size = bytes as f64; let mut unit_index = 0; while size >= 1024.0 && unit_index < UNITS.len() - 1 { size /= 1024.0; unit_index += 1; } format!("{:.1}{}", size, UNITS[unit_index]) } ``` ### Understanding Data Freshness The socktop agent implements intelligent caching to avoid overwhelming the system: - **Metrics**: Cached for ~250ms by default (fast-changing data like CPU, memory) - **Processes**: Cached for ~1500ms by default (moderately changing data) - **Disks**: Cached for ~1000ms by default (slowly changing data) This means: 1. **Multiple rapid requests** for the same data type will return cached results 2. **Different data types** have independent cache timers 3. **Fresh data** is automatically retrieved when cache expires ```rust use socktop_connector::{connect_to_socktop_agent, AgentRequest, AgentResponse}; use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() -> Result<(), Box> { let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?; // This demonstrates cache behavior println!("Requesting metrics twice quickly..."); // First request - fresh data from system let start = std::time::Instant::now(); connector.request(AgentRequest::Metrics).await?; println!("First request took: {:?}", start.elapsed()); // Second request immediately - cached data let start = std::time::Instant::now(); connector.request(AgentRequest::Metrics).await?; println!("Second request took: {:?}", start.elapsed()); // Much faster! // Wait for cache to expire, then request again sleep(Duration::from_millis(300)).await; let start = std::time::Instant::now(); connector.request(AgentRequest::Metrics).await?; println!("Third request (after cache expiry): {:?}", start.elapsed()); Ok(()) } ``` The WebSocket connection remains open between requests, providing efficient real-time monitoring without connection overhead. ## Request Types The library supports three types of requests: - `AgentRequest::Metrics` - Get current system metrics (CPU, memory, network, etc.) - `AgentRequest::Disks` - Get disk usage information - `AgentRequest::Processes` - Get running process information ## Response Types Responses are automatically parsed into strongly-typed structures: - `AgentResponse::Metrics(Metrics)` - System metrics with CPU, memory, network data - `AgentResponse::Disks(Vec)` - List of disk usage information - `AgentResponse::Processes(ProcessesPayload)` - Process list with CPU and memory usage ## Configuration Options The library provides flexible configuration through the `ConnectorConfig` builder: - `with_tls_ca(path)` - Enable TLS with certificate pinning - `with_hostname_verification(bool)` - Control hostname verification for TLS connections - `true` (recommended): Verify the server hostname matches the certificate - `false`: Skip hostname verification (useful for localhost or IP-based connections) **Note**: Hostname verification only applies to TLS connections (`wss://`). Non-TLS connections (`ws://`) don't use certificates, so hostname verification is not applicable. ## Security Considerations - **Production TLS**: Always enable hostname verification (`verify_hostname: true`) for production - **Development/Testing**: You may disable hostname verification for localhost or IP addresses - **Certificate Pinning**: Use `with_tls_ca()` for self-signed certificates - **Non-TLS**: Use only for development or trusted networks ## Environment Variables Currently no environment variables are used. All configuration is done through the API. ## Error Handling The library uses `anyhow::Error` for error handling, providing detailed error messages for common failure scenarios: - Connection failures - TLS certificate validation errors - Protocol errors - Parsing errors ## License MIT License - see the LICENSE file for details.