openadp_ocrypt/
client.rs

1//! OpenADP Rust Client Implementation
2//!
3//! This module provides Rust client implementations for OpenADP servers,
4//! matching the Go and Python client functionality exactly:
5//!
6//! - OpenADPClient: Basic JSON-RPC client (no encryption)
7//! - EncryptedOpenADPClient: JSON-RPC client with Noise-NK encryption
8//! - MultiServerClient: High-level client managing multiple servers
9//!
10//! All clients implement standardized interfaces for cross-language compatibility.
11
12use crate::{OpenADPError, Result};
13use reqwest::Client;
14use serde::{Deserialize, Serialize};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::time::Duration;
18use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
19use tokio::time::timeout;
20use futures::future::join_all;
21use rand::rngs::OsRng;
22use rand::RngCore;
23use snow::{Builder, HandshakeState, TransportState, Keypair as SnowKeypair};
24// Removed curve25519-dalek dependency
25use chrono;
26
27// Error codes matching Go implementation
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ErrorCode {
30    NetworkFailure = 1001,
31    AuthenticationFailed = 1002,
32    InvalidRequest = 1003,
33    ServerError = 1004,
34    EncryptionFailed = 1005,
35    NoLiveServers = 1006,
36    InvalidResponse = 1007,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ServerSelectionStrategy {
41    FirstAvailable = 0,
42    RoundRobin = 1,
43    Random = 2,
44    LowestLatency = 3,
45}
46
47/// Server information from registry or configuration
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ServerInfo {
50    pub url: String,
51    #[serde(default)]
52    pub public_key: String,
53    #[serde(default)]
54    pub country: String,
55    #[serde(default)]
56    pub remaining_guesses: Option<i32>, // None means unknown, Some(n) means n remaining guesses
57}
58
59// Removed default_remaining_guesses function
60
61impl ServerInfo {
62    pub fn new(url: String) -> Self {
63        Self {
64            url,
65            public_key: String::new(),
66            country: String::new(),
67            remaining_guesses: None,
68        }
69    }
70    
71    pub fn with_public_key(mut self, public_key: String) -> Self {
72        self.public_key = public_key;
73        self
74    }
75    
76    pub fn with_country(mut self, country: String) -> Self {
77        self.country = country;
78        self
79    }
80    
81    pub fn with_remaining_guesses(mut self, remaining_guesses: Option<i32>) -> Self {
82        self.remaining_guesses = remaining_guesses;
83        self
84    }
85}
86
87/// Standardized request for RegisterSecret operation
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct RegisterSecretRequest {
90    pub auth_code: String,
91    pub uid: String,
92    pub did: String,
93    pub bid: String,
94    pub version: i32,
95    pub x: i32,
96    pub y: String, // Base64 encoded point
97    pub max_guesses: i32,
98    pub expiration: i64,
99    #[serde(default)]
100    pub encrypted: bool,
101    #[serde(default)]
102    pub auth_data: Option<HashMap<String, Value>>,
103}
104
105/// Standardized response for RegisterSecret operation
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct RegisterSecretResponse {
108    pub success: bool,
109    #[serde(default)]
110    pub message: String,
111}
112
113/// Standardized request for RecoverSecret operation
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct RecoverSecretRequest {
116    pub auth_code: String,
117    pub uid: String,
118    pub did: String,
119    pub bid: String,
120    pub b: String, // Base64 encoded point
121    pub guess_num: i32,
122    #[serde(default)]
123    pub encrypted: bool,
124    #[serde(default)]
125    pub auth_data: Option<HashMap<String, Value>>,
126}
127
128/// Standardized response for RecoverSecret operation
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct RecoverSecretResponse {
131    pub success: bool,
132    #[serde(default)]
133    pub message: String,
134    pub version: i32,
135    pub x: i32,
136    pub si_b: Option<String>, // Base64 encoded point
137    pub num_guesses: i32,
138    pub max_guesses: i32,
139    pub expiration: i64,
140}
141
142/// Standardized request for ListBackups operation
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ListBackupsRequest {
145    pub uid: String,
146    #[serde(default)]
147    pub auth_code: String,
148    #[serde(default)]
149    pub encrypted: bool,
150    #[serde(default)]
151    pub auth_data: Option<HashMap<String, Value>>,
152}
153
154/// Information about a backup
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct BackupInfo {
157    pub uid: String,
158    pub did: String,
159    pub bid: String,
160    pub version: i32,
161    pub num_guesses: i32,
162    pub max_guesses: i32,
163    pub expiration: i64,
164}
165
166/// Standardized response for ListBackups operation
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ListBackupsResponse {
169    pub backups: Vec<BackupInfo>,
170}
171
172/// Standardized response for GetServerInfo operation
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ServerInfoResponse {
175    #[serde(alias = "server_version")]
176    pub version: String,
177    #[serde(default)]
178    pub noise_nk_public_key: String,
179    #[serde(default)]
180    pub supported_methods: Vec<String>,
181    #[serde(default)]
182    pub max_request_size: i64,
183    #[serde(default)]
184    pub rate_limits: HashMap<String, Value>,
185}
186
187/// JSON-RPC 2.0 error structure
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct JsonRpcError {
190    pub code: i32,
191    pub message: String,
192    #[serde(default)]
193    pub data: Option<Value>,
194}
195
196/// JSON-RPC 2.0 request structure
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct JsonRpcRequest {
199    pub jsonrpc: String,
200    pub method: String,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub params: Option<Value>,
203    pub id: i32,
204}
205
206impl JsonRpcRequest {
207    pub fn new(method: String, params: Option<Value>) -> Self {
208        Self {
209            jsonrpc: "2.0".to_string(),
210            method,
211            params,
212            id: 1,
213        }
214    }
215    
216    pub fn to_dict(&self) -> HashMap<String, Value> {
217        let mut dict = HashMap::new();
218        dict.insert("jsonrpc".to_string(), Value::String(self.jsonrpc.clone()));
219        dict.insert("method".to_string(), Value::String(self.method.clone()));
220        dict.insert("id".to_string(), Value::Number(serde_json::Number::from(self.id)));
221        
222        if let Some(params) = &self.params {
223            dict.insert("params".to_string(), params.clone());
224        }
225        
226        dict
227    }
228}
229
230/// JSON-RPC 2.0 response structure
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct JsonRpcResponse {
233    pub jsonrpc: String,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub result: Option<Value>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub error: Option<JsonRpcError>,
238    pub id: Option<i32>,
239}
240
241/// Basic OpenADP client with JSON-RPC communication (no encryption)
242pub struct OpenADPClient {
243    url: String,
244    client: Client,
245    timeout: Duration,
246}
247
248impl OpenADPClient {
249    pub fn new(url: String, timeout_secs: u64) -> Self {
250        let client = Client::builder()
251            .timeout(Duration::from_secs(timeout_secs))
252            .build()
253            .expect("Failed to create HTTP client");
254            
255        Self {
256            url,
257            client,
258            timeout: Duration::from_secs(timeout_secs),
259        }
260    }
261    
262    /// Make a JSON-RPC request to the server
263    async fn make_request(&self, method: &str, params: Option<Value>) -> Result<Value> {
264        let request = JsonRpcRequest::new(method.to_string(), params);
265        let json_body = serde_json::to_string(&request)?;
266        
267
268        let response = timeout(self.timeout, 
269            self.client
270                .post(&self.url)
271                .header("Content-Type", "application/json")
272                .body(json_body)
273                .send()
274        ).await
275        .map_err(|_| OpenADPError::Server("Request timed out".to_string()))?
276        .map_err(OpenADPError::Network)?;
277        
278        if !response.status().is_success() {
279            return Err(OpenADPError::Server(format!("HTTP {}", response.status())));
280        }
281        
282        let response_text = response.text().await.map_err(OpenADPError::Network)?;
283        let rpc_response: JsonRpcResponse = serde_json::from_str(&response_text)?;
284        
285        if let Some(error) = rpc_response.error {
286            return Err(OpenADPError::Server(format!("RPC Error {}: {}", error.code, error.message)));
287        }
288        
289        rpc_response.result.ok_or_else(|| OpenADPError::InvalidResponse)
290    }
291    
292    /// Test server connection with echo
293    pub async fn echo(&self, message: &str) -> Result<String> {
294        let params = json!([message]);
295        let result = self.make_request("Echo", Some(params)).await?;
296        
297        result.as_str()
298            .map(|s| s.to_string())
299            .ok_or_else(|| OpenADPError::InvalidResponse)
300    }
301    
302    /// Ping server for basic connectivity test (alias for echo with 'ping' message)
303    pub async fn ping(&self) -> Result<()> {
304        self.echo("ping").await?;
305        Ok(())
306    }
307    
308    /// Get server information
309    pub async fn get_server_info(&self) -> Result<ServerInfoResponse> {
310        let result = self.make_request("GetServerInfo", None).await?;
311        serde_json::from_value(result).map_err(|e| OpenADPError::Json(e))
312    }
313    
314    /// Register a secret with the server
315    pub async fn register_secret_standardized(&self, request: RegisterSecretRequest) -> Result<RegisterSecretResponse> {
316        // Python sends parameters as array: [auth_code, uid, did, bid, version, x, y, max_guesses, expiration]
317        let params = json!([
318            request.auth_code,
319            request.uid,
320            request.did,
321            request.bid,
322            request.version,
323            request.x,
324            request.y,
325            request.max_guesses,
326            request.expiration
327        ]);
328        let result = self.make_request("RegisterSecret", Some(params)).await?;
329        
330        // Server returns a boolean for RegisterSecret
331        if let Some(success) = result.as_bool() {
332            Ok(RegisterSecretResponse {
333                success,
334                message: String::new(),
335            })
336        } else {
337            Err(OpenADPError::InvalidResponse)
338        }
339    }
340    
341    /// Recover a secret from the server
342    pub async fn recover_secret_standardized(&self, request: RecoverSecretRequest) -> Result<RecoverSecretResponse> {
343        // Python sends parameters as array: [auth_code, uid, did, bid, b, guess_num]
344        let params = json!([
345            request.auth_code,
346            request.uid,
347            request.did,
348            request.bid,
349            request.b,
350            request.guess_num
351        ]);
352
353        let result = self.make_request("RecoverSecret", Some(params)).await?;
354        
355        // Server returns a dictionary for RecoverSecret
356        serde_json::from_value(result).map_err(|e| OpenADPError::Json(e))
357    }
358    
359    /// List backups for a user
360    pub async fn list_backups_standardized(&self, request: ListBackupsRequest) -> Result<ListBackupsResponse> {
361        // Server expects just the UID as a single parameter in array format: [uid]
362        let params = Some(json!([request.uid]));
363        let result = self.make_request("ListBackups", params).await?;
364        
365        // Convert the result array to ListBackupsResponse format
366        if let Some(backups_array) = result.as_array() {
367            let mut backups = Vec::new();
368            for backup_value in backups_array {
369                if let Ok(backup_info) = serde_json::from_value::<BackupInfo>(backup_value.clone()) {
370                    backups.push(backup_info);
371                }
372            }
373            Ok(ListBackupsResponse { backups })
374        } else {
375            Err(OpenADPError::InvalidFormat("Expected array of backups".to_string()))
376        }
377    }
378    
379    /// Test connection
380    pub async fn test_connection(&self) -> Result<()> {
381        self.ping().await
382    }
383    
384    /// Get server URL
385    pub fn get_server_url(&self) -> &str {
386        &self.url
387    }
388    
389    /// Check if client supports encryption (basic client doesn't)
390    pub fn supports_encryption(&self) -> bool {
391        false
392    }
393}
394
395/// Noise-NK protocol implementation using Snow
396pub struct NoiseNK {
397    handshake_state: Option<HandshakeState>,
398    transport_state: Option<TransportState>,
399    pub handshake_complete: bool,
400    is_initiator: bool,
401    handshake_hash: Option<Vec<u8>>, // Store handshake hash before transport mode
402}
403
404impl NoiseNK {
405    pub fn new() -> Self {
406        Self {
407            handshake_state: None,
408            transport_state: None,
409            handshake_complete: false,
410            is_initiator: false,
411            handshake_hash: None,
412        }
413    }
414    
415    /// Initialize as initiator (client) with remote static key
416    pub fn initialize_as_initiator(&mut self, remote_static_key: Vec<u8>) -> Result<()> {
417        let params = "Noise_NK_25519_AESGCM_SHA256".parse()
418            .map_err(|e| OpenADPError::Crypto(format!("Invalid Noise params: {}", e)))?;
419        
420        let builder = Builder::new(params)
421            .remote_public_key(&remote_static_key);
422        
423        let handshake_state = builder.build_initiator()
424            .map_err(|e| OpenADPError::Crypto(format!("Failed to build initiator: {}", e)))?;
425        
426        self.handshake_state = Some(handshake_state);
427        self.is_initiator = true;
428        self.handshake_complete = false;
429        
430        Ok(())
431    }
432    
433    /// Initialize as responder (server) with local static key
434    pub fn initialize_as_responder(&mut self, local_static_key: Vec<u8>) -> Result<()> {
435        let params = "Noise_NK_25519_AESGCM_SHA256".parse()
436            .map_err(|e| OpenADPError::Crypto(format!("Invalid Noise params: {}", e)))?;
437        
438        let keypair = SnowKeypair {
439            private: local_static_key,
440            public: vec![0u8; 32], // Will be computed by Snow
441        };
442        
443        let builder = Builder::new(params)
444            .local_private_key(&keypair.private);
445        
446        let handshake_state = builder.build_responder()
447            .map_err(|e| OpenADPError::Crypto(format!("Failed to build responder: {}", e)))?;
448        
449        self.handshake_state = Some(handshake_state);
450        self.is_initiator = false;
451        self.handshake_complete = false;
452        
453        Ok(())
454    }
455    
456    /// Write a handshake message
457    pub fn write_message(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
458        if let Some(mut handshake_state) = self.handshake_state.take() {
459            let mut buf = vec![0u8; 1024]; // Buffer for handshake message
460            
461            let len = handshake_state.write_message(payload, &mut buf)
462                .map_err(|e| OpenADPError::Crypto(format!("Failed to write handshake message: {}", e)))?;
463            
464            buf.truncate(len);
465            
466            // Check if handshake is complete
467            if handshake_state.is_handshake_finished() {
468                // Store handshake hash before entering transport mode
469                self.handshake_hash = Some(handshake_state.get_handshake_hash().to_vec());
470                
471                let transport_state = handshake_state.into_transport_mode()
472                    .map_err(|e| OpenADPError::Crypto(format!("Failed to enter transport mode: {}", e)))?;
473                
474                self.transport_state = Some(transport_state);
475                self.handshake_complete = true;
476            } else {
477                // Put handshake state back
478                self.handshake_state = Some(handshake_state);
479            }
480            
481            Ok(buf)
482        } else {
483            Err(OpenADPError::Crypto("NoiseNK not initialized".to_string()))
484        }
485    }
486    
487    /// Read a handshake message
488    pub fn read_message(&mut self, message: &[u8]) -> Result<Vec<u8>> {
489        if let Some(mut handshake_state) = self.handshake_state.take() {
490            let mut buf = vec![0u8; 1024]; // Buffer for payload
491            
492            let len = handshake_state.read_message(message, &mut buf)
493                .map_err(|e| OpenADPError::Crypto(format!("Failed to read handshake message: {}", e)))?;
494            
495            buf.truncate(len);
496            
497            // Check if handshake is complete
498            if handshake_state.is_handshake_finished() {
499                // Store handshake hash before entering transport mode
500                self.handshake_hash = Some(handshake_state.get_handshake_hash().to_vec());
501                
502                let transport_state = handshake_state.into_transport_mode()
503                    .map_err(|e| OpenADPError::Crypto(format!("Failed to enter transport mode: {}", e)))?;
504                
505                self.transport_state = Some(transport_state);
506                self.handshake_complete = true;
507            } else {
508                // Put handshake state back
509                self.handshake_state = Some(handshake_state);
510            }
511            
512            Ok(buf)
513        } else {
514            Err(OpenADPError::Crypto("NoiseNK not initialized".to_string()))
515        }
516    }
517    
518    /// Encrypt message after handshake completion
519    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
520        if !self.handshake_complete {
521            return Err(OpenADPError::Crypto("Handshake not complete".to_string()));
522        }
523        
524        if let Some(transport_state) = &mut self.transport_state {
525            let mut buf = vec![0u8; plaintext.len() + 16]; // Extra space for AEAD tag
526            
527            let len = transport_state.write_message(plaintext, &mut buf)
528                .map_err(|e| OpenADPError::Crypto(format!("Failed to encrypt: {}", e)))?;
529            
530            buf.truncate(len);
531            Ok(buf)
532        } else {
533            Err(OpenADPError::Crypto("Transport state not available".to_string()))
534        }
535    }
536    
537    /// Decrypt message after handshake completion
538    pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>> {
539        if !self.handshake_complete {
540            return Err(OpenADPError::Crypto("Handshake not complete".to_string()));
541        }
542        
543        if let Some(transport_state) = &mut self.transport_state {
544            let mut buf = vec![0u8; ciphertext.len()]; // Buffer for plaintext
545            
546            let len = transport_state.read_message(ciphertext, &mut buf)
547                .map_err(|e| OpenADPError::Crypto(format!("Failed to decrypt: {}", e)))?;
548            
549            buf.truncate(len);
550            Ok(buf)
551        } else {
552            Err(OpenADPError::Crypto("Transport state not available".to_string()))
553        }
554    }
555    
556    /// Get handshake hash (for debugging/verification)
557    pub fn get_handshake_hash(&self) -> Result<Vec<u8>> {
558        if let Some(handshake_state) = &self.handshake_state {
559            Ok(handshake_state.get_handshake_hash().to_vec())
560        } else if let Some(hash) = &self.handshake_hash {
561            Ok(hash.clone())
562        } else {
563            Err(OpenADPError::Crypto("NoiseNK not initialized or handshake hash not available".to_string()))
564        }
565    }
566}
567
568/// Generate X25519 keypair for Noise-NK
569pub fn generate_keypair() -> Result<(Vec<u8>, Vec<u8>)> {
570    // Generate a random 32-byte private key
571    let mut private_key_bytes = [0u8; 32];
572    OsRng.fill_bytes(&mut private_key_bytes);
573    
574    // For now, return the private key and a placeholder public key
575    // The Noise library will handle the actual key generation
576    let mut public_key_bytes = [0u8; 32];
577    OsRng.fill_bytes(&mut public_key_bytes);
578    
579    Ok((private_key_bytes.to_vec(), public_key_bytes.to_vec()))
580}
581
582/// Parse server public key from base64
583pub fn parse_server_public_key(key_b64: &str) -> Result<Vec<u8>> {
584    let key_b64 = if key_b64.starts_with("ed25519:") {
585        &key_b64[8..]
586    } else {
587        key_b64
588    };
589    
590    BASE64.decode(key_b64)
591        .map_err(|e| OpenADPError::Crypto(format!("Invalid base64 public key: {}", e)))
592}
593
594/// Encrypted OpenADP client with Noise-NK encryption
595pub struct EncryptedOpenADPClient {
596    basic_client: OpenADPClient,
597    noise: Option<NoiseNK>,
598    server_public_key: Option<Vec<u8>>,
599    session_id: Option<String>,
600}
601
602impl EncryptedOpenADPClient {
603    pub fn new(url: String, server_public_key: Option<Vec<u8>>, timeout_secs: u64) -> Self {
604        let basic_client = OpenADPClient::new(url, timeout_secs);
605        
606        Self {
607            basic_client,
608            noise: None,
609            server_public_key,
610            session_id: None,
611        }
612    }
613    
614    /// Check if client has public key for encryption
615    pub fn has_public_key(&self) -> bool {
616        self.server_public_key.is_some()
617    }
618    
619    /// Initialize Noise-NK encryption
620    async fn initialize_encryption(&mut self) -> Result<()> {
621        if let Some(public_key) = &self.server_public_key {
622            let mut noise = NoiseNK::new();
623            noise.initialize_as_initiator(public_key.clone())?;
624            self.noise = Some(noise);
625        }
626        Ok(())
627    }
628    
629    /// Make encrypted request using proper Noise-NK protocol
630    async fn make_encrypted_request(&mut self, method: &str, params: Option<Value>) -> Result<Value> {
631        if self.noise.is_none() {
632            self.initialize_encryption().await?;
633        }
634        
635        if let Some(noise) = &mut self.noise {
636            // Step 1: Perform Noise-NK handshake if not already done
637            if !noise.handshake_complete {
638                // Generate session ID - use random 16-byte ID, base64 encoded (matches Go server expectation)
639                use rand::RngCore;
640                let mut rng = rand::thread_rng();
641                let mut session_bytes = [0u8; 16];  // Changed from 8 to 16 bytes
642                rng.fill_bytes(&mut session_bytes);
643                let session_id = base64::engine::general_purpose::STANDARD.encode(&session_bytes);
644                self.session_id = Some(session_id.clone());
645                
646                // Send first handshake message (-> e, es)
647                let message1 = noise.write_message(b"test")?; // Use "test" payload like Python
648                let message1_b64 = BASE64.encode(&message1);
649                
650                // Send handshake request with session field
651                let handshake_request = json!({
652                    "jsonrpc": "2.0",
653                    "method": "noise_handshake",
654                    "params": [{
655                        "session": session_id,
656                        "message": message1_b64
657                    }],
658                    "id": 1
659                });
660                
661        
662                let handshake_response = timeout(self.basic_client.timeout,
663                    self.basic_client.client
664                        .post(&self.basic_client.url)
665                        .header("Content-Type", "application/json")
666                        .json(&handshake_request)
667                        .send()
668                ).await
669                .map_err(|_| OpenADPError::Server("Handshake request timed out".to_string()))?
670                .map_err(OpenADPError::Network)?;
671                
672                if !handshake_response.status().is_success() {
673                    return Err(OpenADPError::Server(format!("Handshake HTTP {}", handshake_response.status())));
674                }
675                
676                let handshake_response_text = handshake_response.text().await.map_err(OpenADPError::Network)?;
677                let handshake_rpc_response: JsonRpcResponse = serde_json::from_str(&handshake_response_text)?;
678                
679                if let Some(error) = handshake_rpc_response.error {
680                    return Err(OpenADPError::Server(format!("Handshake RPC Error {}: {}", error.code, error.message)));
681                }
682                
683                let handshake_result = handshake_rpc_response.result.ok_or_else(|| OpenADPError::InvalidResponse)?;
684                let message2_b64 = handshake_result.get("message")
685                    .and_then(|v| v.as_str())
686                    .ok_or_else(|| OpenADPError::InvalidResponse)?;
687                
688                let message2 = BASE64.decode(message2_b64)
689                    .map_err(|e| OpenADPError::Crypto(format!("Invalid base64 in handshake response: {}", e)))?;
690                
691                // Process second handshake message (<- e, ee)
692                let _server_payload = noise.read_message(&message2)?;
693                
694                if !noise.handshake_complete {
695                    return Err(OpenADPError::Server("Handshake not complete after message exchange".to_string()));
696                }
697                
698                eprintln!("✅ Noise-NK handshake completed successfully");
699            }
700            
701            // Step 2: Send encrypted JSON-RPC request using the session
702            let session_id = self.session_id.as_ref()
703                .ok_or_else(|| OpenADPError::Crypto("No session ID available".to_string()))?;
704            
705            let request = JsonRpcRequest::new(method.to_string(), params);
706    
707            
708            let request_json = serde_json::to_vec(&request.to_dict())?;
709            
710            let encrypted_request = noise.encrypt(&request_json)?;
711            let encrypted_request_b64 = BASE64.encode(&encrypted_request);
712            
713            let encrypted_rpc_request = json!({
714                "jsonrpc": "2.0",
715                "method": "encrypted_call",
716                "params": [{
717                    "session": session_id,
718                    "data": encrypted_request_b64
719                }],
720                "id": request.id
721            });
722            
723            // Send encrypted request
724            let response = timeout(self.basic_client.timeout,
725                self.basic_client.client
726                    .post(&self.basic_client.url)
727                    .header("Content-Type", "application/json")
728                    .json(&encrypted_rpc_request)
729                    .send()
730            ).await
731            .map_err(|_| OpenADPError::Server("Encrypted request timed out".to_string()))?
732            .map_err(OpenADPError::Network)?;
733            
734            if !response.status().is_success() {
735                return Err(OpenADPError::Server(format!("Encrypted request HTTP {}", response.status())));
736            }
737            
738            let response_text = response.text().await.map_err(OpenADPError::Network)?;
739            let rpc_response: JsonRpcResponse = serde_json::from_str(&response_text)?;
740            
741            if let Some(error) = rpc_response.error {
742                return Err(OpenADPError::Server(format!("RPC Error {}: {}", error.code, error.message)));
743            }
744            
745            let encrypted_result = rpc_response.result.ok_or_else(|| OpenADPError::InvalidResponse)?;
746            let encrypted_data_b64 = encrypted_result.get("data")
747                .and_then(|v| v.as_str())
748                .ok_or_else(|| OpenADPError::InvalidResponse)?;
749            
750            let encrypted_data = BASE64.decode(encrypted_data_b64)
751                .map_err(|e| OpenADPError::Crypto(format!("Invalid base64 in encrypted response: {}", e)))?;
752            
753            // Decrypt response
754            let decrypted_response = noise.decrypt(&encrypted_data)?;
755            let response_json: Value = serde_json::from_slice(&decrypted_response)?;
756            
757            // Check if the decrypted response is itself a JSON-RPC error
758            if let Some(error_obj) = response_json.get("error") {
759                if let Some(message) = error_obj.get("message").and_then(|m| m.as_str()) {
760                    return Err(OpenADPError::Server(format!("RPC Error: {}", message)));
761                }
762            }
763            
764            // If it has a result field, return that; otherwise return the whole response
765            if let Some(result) = response_json.get("result") {
766                Ok(result.clone())
767            } else {
768                Ok(response_json)
769            }
770        } else {
771            Err(OpenADPError::Crypto("Noise not initialized".to_string()))
772        }
773    }
774    
775    /// Echo with optional encryption
776    pub async fn echo(&mut self, message: &str, encrypted: bool) -> Result<String> {
777        let params = json!([message]);
778        
779        let result = if encrypted && self.has_public_key() {
780            self.make_encrypted_request("Echo", Some(params)).await?
781        } else {
782            self.basic_client.make_request("Echo", Some(params)).await?
783        };
784        
785        result.as_str()
786            .map(|s| s.to_string())
787            .ok_or_else(|| OpenADPError::InvalidResponse)
788    }
789    
790    /// Ping server (alias for echo with 'ping' message)
791    pub async fn ping(&mut self) -> Result<()> {
792        self.echo("ping", false).await?;
793        Ok(())
794    }
795    
796    /// Get server information
797    pub async fn get_server_info(&self) -> Result<ServerInfoResponse> {
798        self.basic_client.get_server_info().await
799    }
800    
801    /// Register secret with optional encryption
802    pub async fn register_secret_standardized(&mut self, mut request: RegisterSecretRequest) -> Result<RegisterSecretResponse> {
803        request.encrypted = self.has_public_key();
804        
805        if request.encrypted && self.has_public_key() {
806            // Use encrypted request - server expects array of parameters
807            let params = Some(json!([
808                request.auth_code,
809                request.uid,
810                request.did,
811                request.bid,
812                request.version,
813                request.x,
814                request.y,
815                request.max_guesses,
816                request.expiration
817            ]));
818            
819            let result = self.make_encrypted_request("RegisterSecret", params).await?;
820            
821            if let Some(success) = result.as_bool() {
822                Ok(RegisterSecretResponse {
823                    success,
824                    message: String::new(),
825                })
826            } else {
827                Err(OpenADPError::InvalidResponse)
828            }
829        } else {
830            // Use unencrypted request
831            self.basic_client.register_secret_standardized(request).await
832        }
833    }
834    
835    /// Recover secret with optional encryption
836    pub async fn recover_secret_standardized(&mut self, mut request: RecoverSecretRequest) -> Result<RecoverSecretResponse> {
837        // Only set encrypted flag if client has public key and request doesn't explicitly set it to false
838        if self.has_public_key() {
839            request.encrypted = true;
840        }
841        
842        if request.encrypted && self.has_public_key() {
843            // Use encrypted request - server expects array of parameters
844            let params = Some(json!([
845                request.auth_code,
846                request.uid,
847                request.did,
848                request.bid,
849                request.b,
850                request.guess_num
851            ]));
852            
853            let response = self.make_encrypted_request("RecoverSecret", params).await?;
854
855
856
857            let version = response.get("version").and_then(|v| v.as_i64()).unwrap_or(1) as i32;
858            let x = response.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
859            let si_b = response.get("si_b").and_then(|v| v.as_str()).unwrap_or("").to_string();
860            let num_guesses = response.get("num_guesses").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
861            let max_guesses = response.get("max_guesses").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
862            let expiration = response.get("expiration").and_then(|v| v.as_i64()).unwrap_or(0);
863
864            Ok(RecoverSecretResponse {
865                success: true,
866                message: String::new(),
867                version,
868                x,
869                si_b: Some(si_b),
870                num_guesses,
871                max_guesses,
872                expiration,
873            })
874        } else {
875            // Use unencrypted request
876            self.basic_client.recover_secret_standardized(request).await
877        }
878    }
879    
880    /// List backups with optional encryption
881    pub async fn list_backups_standardized(&mut self, mut request: ListBackupsRequest) -> Result<ListBackupsResponse> {
882        request.encrypted = self.has_public_key();
883        
884        if request.encrypted && self.has_public_key() {
885            // Use encrypted request
886            let params = Some(json!([request.uid]));
887            let response = self.make_encrypted_request("ListBackups", params).await?;
888            
889            // Parse the response
890            let empty_vec = Vec::new();
891            let backups_array = response.get("backups").and_then(|v| v.as_array()).unwrap_or(&empty_vec);
892            let mut backups = Vec::new();
893            
894            for backup_value in backups_array {
895                if let Some(backup_obj) = backup_value.as_object() {
896                    let backup = BackupInfo {
897                        uid: backup_obj.get("uid").and_then(|v| v.as_str()).unwrap_or("").to_string(),
898                        did: backup_obj.get("did").and_then(|v| v.as_str()).unwrap_or("").to_string(),
899                        bid: backup_obj.get("bid").and_then(|v| v.as_str()).unwrap_or("").to_string(),
900                        version: backup_obj.get("version").and_then(|v| v.as_i64()).unwrap_or(1) as i32,
901                        num_guesses: backup_obj.get("num_guesses").and_then(|v| v.as_i64()).unwrap_or(0) as i32,
902                        max_guesses: backup_obj.get("max_guesses").and_then(|v| v.as_i64()).unwrap_or(0) as i32,
903                        expiration: backup_obj.get("expiration").and_then(|v| v.as_i64()).unwrap_or(0),
904                    };
905                    backups.push(backup);
906                }
907            }
908            
909            Ok(ListBackupsResponse { backups })
910        } else {
911            // Use unencrypted request
912            self.basic_client.list_backups_standardized(request).await
913        }
914    }
915    
916    /// Test connection
917    pub async fn test_connection(&self) -> Result<()> {
918        self.basic_client.test_connection().await
919    }
920    
921    /// Get server URL
922    pub fn get_server_url(&self) -> &str {
923        self.basic_client.get_server_url()
924    }
925    
926    /// Check if client supports encryption
927    pub fn supports_encryption(&self) -> bool {
928        self.has_public_key()
929    }
930}
931
932/// Server discovery functions
933
934/// JSON response from server registry
935#[derive(Debug, Clone, Serialize, Deserialize)]
936pub struct ServersResponse {
937    pub servers: Vec<ServerInfo>,
938}
939
940/// Get servers from registry
941pub async fn get_servers(registry_url: &str) -> Result<Vec<ServerInfo>> {
942    let mut url = if registry_url.is_empty() {
943        crate::DEFAULT_REGISTRY_URL.to_string()
944    } else {
945        registry_url.to_string()
946    };
947    
948    // Handle file:// URLs for local testing
949    if url.starts_with("file://") {
950        // Strip file:// prefix and read local file
951        let file_path = url.strip_prefix("file://").unwrap();
952        let content = std::fs::read_to_string(file_path)
953            .map_err(|e| OpenADPError::Io(e.to_string()))?;
954        
955        let servers_response: ServersResponse = serde_json::from_str(&content)?;
956        
957        if servers_response.servers.is_empty() {
958            return Err(OpenADPError::NoServers);
959        }
960        
961        return Ok(servers_response.servers);
962    }
963    
964    // Ensure the URL ends with the correct API endpoint
965    if !url.ends_with("/api/servers.json") && !url.ends_with("/servers.json") {
966        if url.ends_with('/') {
967            url.push_str("api/servers.json");
968        } else {
969            url.push_str("/api/servers.json");
970        }
971    }
972    
973    let client = Client::new();
974    let response = client.get(&url).send().await.map_err(OpenADPError::Network)?;
975    
976    if !response.status().is_success() {
977        return Err(OpenADPError::Server(format!("HTTP {}", response.status())));
978    }
979    
980    let servers_response: ServersResponse = response.json().await.map_err(OpenADPError::Network)?;
981    
982    if servers_response.servers.is_empty() {
983        return Err(OpenADPError::NoServers);
984    }
985    
986    Ok(servers_response.servers)
987}
988
989/// Get server URLs from registry
990pub async fn get_server_urls(registry_url: &str) -> Result<Vec<String>> {
991    let servers = get_servers(registry_url).await?;
992    Ok(servers.into_iter().map(|s| s.url).collect())
993}
994
995/// Discover servers with fallback
996pub async fn discover_servers(registry_url: &str) -> Result<Vec<ServerInfo>> {
997    match get_servers(registry_url).await {
998        Ok(servers) => Ok(servers),
999        Err(_) => {
1000            // Fallback to hardcoded servers
1001            Ok(get_fallback_server_info())
1002        }
1003    }
1004}
1005
1006/// Get fallback server URLs (matching Go implementation)
1007pub fn get_fallback_servers() -> Vec<String> {
1008    vec![
1009        "https://xyzzy.openadp.org".to_string(),
1010        "https://sky.openadp.org".to_string(),
1011        "https://minime.openadp.org".to_string(),
1012        "https://louis.evilduckie.ca".to_string(),
1013    ]
1014}
1015
1016/// Get fallback server info (matching Go implementation)
1017pub fn get_fallback_server_info() -> Vec<ServerInfo> {
1018    vec![
1019        ServerInfo {
1020            url: "https://xyzzy.openadp.org".to_string(),
1021            public_key: "FEOkIV7ZhONfuhSOkEuTNo36pVzS2KAhqDXYwC8MySA=".to_string(),
1022            country: "US".to_string(),
1023            remaining_guesses: None,
1024        },
1025        ServerInfo {
1026            url: "https://sky.openadp.org".to_string(),
1027            public_key: "uCvcLGSdROipW6AlX1vmzezkpzHNu6M0C4O/5dc8flg=".to_string(),
1028            country: "US".to_string(),
1029            remaining_guesses: None,
1030        },
1031        ServerInfo {
1032            url: "https://minime.openadp.org".to_string(),
1033            public_key: "gnV5Obw3maZGgL1HHK4YW0DkyKcp7Tp+xD9f4+gus3s=".to_string(),
1034            country: "US".to_string(),
1035            remaining_guesses: None,
1036        },
1037        ServerInfo {
1038            url: "https://louis.evilduckie.ca".to_string(),
1039            public_key: "G2G5FPQ7WMBJMPvQpMOsn9txwXavvcTZq50txF4rryw=".to_string(),
1040            country: "US".to_string(),
1041            remaining_guesses: None,
1042        },
1043    ]
1044}
1045
1046/// Multi-server client managing multiple servers
1047pub struct MultiServerClient {
1048    clients: Vec<EncryptedOpenADPClient>,
1049    strategy: ServerSelectionStrategy,
1050    #[allow(dead_code)]
1051    echo_timeout: Duration,
1052}
1053
1054impl MultiServerClient {
1055    /// Create a new multi-server client with server discovery
1056    pub async fn new(servers_url: &str, echo_timeout_secs: u64) -> Result<Self> {
1057        let server_infos = get_servers(servers_url).await?;
1058        Self::from_server_info(server_infos, echo_timeout_secs).await
1059    }
1060
1061    /// Create a new multi-server client from provided server information
1062    pub async fn from_server_info(server_infos: Vec<ServerInfo>, echo_timeout_secs: u64) -> Result<Self> {
1063        let echo_timeout = Duration::from_secs(echo_timeout_secs);
1064        
1065        // Test servers concurrently and keep only live ones
1066        let live_clients = Self::test_servers_concurrently(server_infos, echo_timeout_secs).await;
1067        
1068        if live_clients.is_empty() {
1069            return Err(OpenADPError::Server("No live servers found".to_string()));
1070        }
1071        
1072        eprintln!("Initialization complete: {} live servers available", live_clients.len());
1073        
1074        Ok(Self {
1075            clients: live_clients,
1076            strategy: ServerSelectionStrategy::FirstAvailable,
1077            echo_timeout,
1078        })
1079    }
1080
1081    /// Test servers concurrently for liveness
1082    async fn test_servers_concurrently(server_infos: Vec<ServerInfo>, timeout_secs: u64) -> Vec<EncryptedOpenADPClient> {
1083        let tasks: Vec<_> = server_infos.into_iter().map(|server_info| {
1084            async move {
1085                Self::test_single_server(server_info, timeout_secs).await
1086            }
1087        }).collect();
1088
1089        let results = join_all(tasks).await;
1090        results.into_iter().filter_map(|r| r).collect()
1091    }
1092
1093    /// Test a single server for liveness
1094    async fn test_single_server(server_info: ServerInfo, timeout_secs: u64) -> Option<EncryptedOpenADPClient> {
1095        eprintln!("Testing server: {}", server_info.url);
1096        
1097        // Parse public key if available
1098        let public_key = if !server_info.public_key.is_empty() {
1099            match parse_server_public_key(&server_info.public_key) {
1100                Ok(key) => {
1101                    eprintln!("  🔑 {}: Using Noise-NK encryption", server_info.url);
1102                    Some(key)
1103                }
1104                Err(e) => {
1105                    eprintln!("  ⚠️  {}: Invalid public key: {}", server_info.url, e);
1106                    None
1107                }
1108            }
1109        } else {
1110            None
1111        };
1112
1113        // Create client
1114        let mut client = EncryptedOpenADPClient::new(server_info.url.clone(), public_key, timeout_secs);
1115        
1116        // Test with echo
1117        let test_message = format!("liveness_test_{}", chrono::Utc::now().timestamp());
1118        
1119        match timeout(Duration::from_secs(timeout_secs), client.echo(&test_message, false)).await {
1120            Ok(Ok(response)) => {
1121                if response == test_message {
1122                    eprintln!("  ✅ {}: Live and responding", server_info.url);
1123                    Some(client)
1124                } else {
1125                    eprintln!("  ❌ {}: Echo response mismatch", server_info.url);
1126                    None
1127                }
1128            }
1129            Ok(Err(e)) => {
1130                eprintln!("  ❌ {}: {}", server_info.url, e);
1131                None
1132            }
1133            Err(_) => {
1134                eprintln!("  ❌ {}: Timeout", server_info.url);
1135                None
1136            }
1137        }
1138    }
1139
1140    pub fn get_live_server_count(&self) -> usize {
1141        self.clients.len()
1142    }
1143
1144    pub fn get_live_server_urls(&self) -> Vec<String> {
1145        self.clients.iter().map(|c| c.get_server_url().to_string()).collect()
1146    }
1147
1148    pub fn set_server_selection_strategy(&mut self, strategy: ServerSelectionStrategy) {
1149        self.strategy = strategy;
1150    }
1151
1152    /// Select a server based on the current strategy
1153    fn select_server(&self) -> Result<&EncryptedOpenADPClient> {
1154        if self.clients.is_empty() {
1155            return Err(OpenADPError::Server("No live servers available".to_string()));
1156        }
1157
1158        match self.strategy {
1159            ServerSelectionStrategy::FirstAvailable => Ok(&self.clients[0]),
1160            ServerSelectionStrategy::RoundRobin => {
1161                // Simple round-robin based on current time
1162                let index = (chrono::Utc::now().timestamp() as usize) % self.clients.len();
1163                Ok(&self.clients[index])
1164            }
1165            ServerSelectionStrategy::Random => {
1166                use rand::Rng;
1167                let mut rng = rand::thread_rng();
1168                let index = rng.gen_range(0..self.clients.len());
1169                Ok(&self.clients[index])
1170            }
1171            ServerSelectionStrategy::LowestLatency => {
1172                // For now, just use first available (latency testing would require more complex implementation)
1173                Ok(&self.clients[0])
1174            }
1175        }
1176    }
1177
1178    /// Select a mutable server reference
1179    fn select_server_mut(&mut self) -> Result<&mut EncryptedOpenADPClient> {
1180        if self.clients.is_empty() {
1181            return Err(OpenADPError::Server("No live servers available".to_string()));
1182        }
1183
1184        match self.strategy {
1185            ServerSelectionStrategy::FirstAvailable => Ok(&mut self.clients[0]),
1186            ServerSelectionStrategy::RoundRobin => {
1187                let index = (chrono::Utc::now().timestamp() as usize) % self.clients.len();
1188                Ok(&mut self.clients[index])
1189            }
1190            ServerSelectionStrategy::Random => {
1191                use rand::Rng;
1192                let mut rng = rand::thread_rng();
1193                let index = rng.gen_range(0..self.clients.len());
1194                Ok(&mut self.clients[index])
1195            }
1196            ServerSelectionStrategy::LowestLatency => {
1197                Ok(&mut self.clients[0])
1198            }
1199        }
1200    }
1201
1202    pub async fn echo(&mut self, message: &str) -> Result<String> {
1203        let client = self.select_server_mut()?;
1204        client.echo(message, false).await
1205    }
1206
1207    pub async fn ping(&mut self) -> Result<()> {
1208        let client = self.select_server_mut()?;
1209        client.ping().await
1210    }
1211
1212    /// Refresh servers by retesting all and updating live list
1213    pub async fn refresh_servers(&mut self) -> Result<()> {
1214        // For now, just test existing servers
1215        let mut live_clients = Vec::new();
1216        
1217        for client in self.clients.drain(..) {
1218            if client.test_connection().await.is_ok() {
1219                live_clients.push(client);
1220            }
1221        }
1222        
1223        self.clients = live_clients;
1224        
1225        if self.clients.is_empty() {
1226            return Err(OpenADPError::Server("No live servers remaining after refresh".to_string()));
1227        }
1228        
1229        Ok(())
1230    }
1231
1232    // Standardized interface methods
1233
1234    pub async fn register_secret_standardized(&mut self, request: RegisterSecretRequest) -> Result<RegisterSecretResponse> {
1235        let client = self.select_server_mut()?;
1236        client.register_secret_standardized(request).await
1237    }
1238
1239    pub async fn recover_secret_standardized(&mut self, request: RecoverSecretRequest) -> Result<RecoverSecretResponse> {
1240        let client = self.select_server_mut()?;
1241        client.recover_secret_standardized(request).await
1242    }
1243
1244    pub async fn list_backups_standardized(&mut self, request: ListBackupsRequest) -> Result<ListBackupsResponse> {
1245        let client = self.select_server_mut()?;
1246        client.list_backups_standardized(request).await
1247    }
1248
1249    pub async fn get_server_info_standardized(&self) -> Result<ServerInfoResponse> {
1250        let client = self.select_server()?;
1251        client.get_server_info().await
1252    }
1253
1254    pub async fn test_connection(&self) -> Result<()> {
1255        let client = self.select_server()?;
1256        client.test_connection().await
1257    }
1258
1259    pub fn get_server_url(&self) -> String {
1260        if let Ok(client) = self.select_server() {
1261            client.get_server_url().to_string()
1262        } else {
1263            "No servers available".to_string()
1264        }
1265    }
1266
1267    pub fn supports_encryption(&self) -> bool {
1268        if let Ok(client) = self.select_server() {
1269            client.supports_encryption()
1270        } else {
1271            false
1272        }
1273    }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279    
1280    #[test]
1281    fn test_server_info() {
1282        let server = ServerInfo::new("https://example.com".to_string())
1283            .with_public_key("test_key".to_string())
1284            .with_country("US".to_string());
1285        
1286        assert_eq!(server.url, "https://example.com");
1287        assert_eq!(server.public_key, "test_key");
1288        assert_eq!(server.country, "US");
1289    }
1290    
1291    #[test]
1292    fn test_json_rpc_structures() {
1293        let request = JsonRpcRequest::new("test_method".to_string(), None);
1294        assert_eq!(request.jsonrpc, "2.0");
1295        assert_eq!(request.method, "test_method");
1296        assert_eq!(request.id, 1);
1297    }
1298    
1299    #[test]
1300    fn test_noise_nk() {
1301        let mut noise = NoiseNK::new();
1302        let (_private_key, public_key) = generate_keypair().unwrap();
1303        
1304        noise.initialize_as_initiator(public_key).unwrap();
1305        
1306        // Test that we can write the first handshake message
1307        let message1 = noise.write_message(b"").unwrap();
1308        assert!(!message1.is_empty());
1309        
1310        // For a complete test, we'd need a responder, but this tests the basic functionality
1311        assert!(!noise.handshake_complete); // Handshake not complete until we get a response
1312    }
1313    
1314    #[test]
1315    fn test_fallback_servers() {
1316        let servers = get_fallback_servers();
1317        assert!(!servers.is_empty());
1318        
1319        let server_infos = get_fallback_server_info();
1320        assert_eq!(servers.len(), server_infos.len());
1321    }
1322}