1use std::io::{Read, Write};
8use std::net::{TcpStream, ToSocketAddrs};
9use std::sync::Arc;
10use std::time::Duration;
11
12use pwr_core::config::PwrConfig;
13use pwr_core::crypto;
14use pwr_core::frame::{self, FrameDecoder};
15use pwr_core::protocol::{self, ClientMessage, Handshake, ProjectInfo, ServerMessage};
16use ring::rand::SecureRandom;
17use rustls::pki_types::{ServerName, UnixTime};
18use rustls::client::danger::{ServerCertVerified, ServerCertVerifier, HandshakeSignatureValid};
19use rustls::DigitallySignedStruct;
20
21pub type ClientResult<T> = Result<T, String>;
23
24pub struct PwrClient {
26 stream: Box<dyn ReadWrite>,
27 decoder: FrameDecoder,
28}
29
30trait ReadWrite: Read + Write {}
32impl<T: Read + Write> ReadWrite for T {}
33
34impl PwrClient {
35 pub fn connect(config: &PwrConfig, tls: bool) -> ClientResult<Self> {
42 let max_retries = 3;
43 let mut last_err = String::new();
44
45 for attempt in 0..=max_retries {
46 match Self::connect_once(config, tls) {
47 Ok(client) => {
48 if attempt > 0 {
49 log::info!("Connected after {} retries", attempt);
50 }
51 return Ok(client);
52 }
53 Err(e) => {
54 last_err = e;
55 if attempt < max_retries {
56 let delay = Duration::from_secs(1 << attempt); log::warn!(
58 "Connection attempt {} failed: {}. Retrying in {:?}...",
59 attempt + 1, last_err, delay
60 );
61 std::thread::sleep(delay);
62 }
63 }
64 }
65 }
66
67 Err(format!("Connection failed after {} attempts: {}", max_retries + 1, last_err))
68 }
69
70 fn connect_once(config: &PwrConfig, tls: bool) -> ClientResult<Self> {
72 let addr = config.server_addr();
73 let timeout = Duration::from_secs(config.connect_timeout_secs);
74
75 let addrs: Vec<_> = addr
79 .to_socket_addrs()
80 .map_err(|e| format!("Cannot resolve {}: {}", addr, e))?
81 .collect();
82
83 if addrs.is_empty() {
84 return Err(format!("No addresses found for {}", addr));
85 }
86
87 let tcp_stream = TcpStream::connect_timeout(
88 &addrs[0],
89 timeout,
90 )
91 .map_err(|e| format!("Connection to {} failed: {}", addr, e))?;
92
93 tcp_stream
94 .set_read_timeout(Some(Duration::from_secs(30)))
95 .map_err(|e| format!("set_read_timeout: {}", e))?;
96
97 let psk = crypto::psk_from_hex(&config.server_psk)
98 .map_err(|e| format!("Invalid PSK: {}", e))?;
99
100 let (stream, decoder) = if tls {
101 let mut tls_stream = connect_tls(tcp_stream, config)?;
102 let mut decoder = FrameDecoder::new();
103 perform_handshake(&mut tls_stream, &mut decoder, &psk, "pwr-cli")?;
104 (Box::new(tls_stream) as Box<dyn ReadWrite>, decoder)
105 } else {
106 let mut decoder = FrameDecoder::new();
107 perform_handshake(&mut &tcp_stream, &mut decoder, &psk, "pwr-cli")?;
108 (Box::new(tcp_stream) as Box<dyn ReadWrite>, decoder)
109 };
110
111 Ok(Self { stream, decoder })
112 }
113
114 pub fn archive_project(
120 &mut self,
121 project_uuid: &uuid::Uuid,
122 project_name: &str,
123 archive_data: &[u8],
124 archive_hash: &str,
125 ) -> ClientResult<()> {
126 self.archive_project_with_progress(
127 project_uuid, project_name, archive_data, archive_hash, None,
128 )
129 }
130
131 pub fn archive_project_with_progress(
133 &mut self,
134 project_uuid: &uuid::Uuid,
135 project_name: &str,
136 archive_data: &[u8],
137 archive_hash: &str,
138 progress: Option<&dyn Fn(u64, u64)>,
139 ) -> ClientResult<()> {
140 let req = protocol::build_archive_request(
142 *project_uuid,
143 project_name,
144 archive_data.len() as u64,
145 1, true,
147 );
148 send_client_msg(&mut self.stream, &req)?;
149
150 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
152 ServerMessage::ArchiveAccept(accept) => {
153 log::debug!("Archive accepted, session {}", accept.session_id);
154 }
155 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
156 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
157 }
158
159 let chunk_size = 1024 * 1024; let mut bytes_sent = 0u64;
162 let total = archive_data.len() as u64;
163 for (i, chunk) in archive_data.chunks(chunk_size).enumerate() {
164 self.stream
165 .write_all(&(chunk.len() as u32).to_be_bytes())
166 .map_err(|e| format!("chunk write: {}", e))?;
167 self.stream
168 .write_all(chunk)
169 .map_err(|e| format!("chunk data write: {}", e))?;
170 bytes_sent += chunk.len() as u64;
171 if let Some(ref cb) = progress {
172 cb(bytes_sent, total);
173 }
174 log::debug!("Sent chunk {} ({} bytes)", i, chunk.len());
175 }
176
177 self.stream
179 .write_all(&0u32.to_be_bytes())
180 .map_err(|e| format!("eof write: {}", e))?;
181 self.stream.flush().map_err(|e| format!("flush: {}", e))?;
182
183 let complete = protocol::build_archive_complete(
185 archive_data.len() as u64,
186 archive_hash,
187 );
188 send_client_msg(&mut self.stream, &complete)?;
189
190 log::info!("Archive sent: {} bytes, hash: {}", archive_data.len(), archive_hash);
191 Ok(())
192 }
193
194 pub fn restore_project(
199 &mut self,
200 project_uuid: &uuid::Uuid,
201 ) -> ClientResult<Vec<u8>> {
202 self.restore_project_with_progress(project_uuid, None)
203 }
204
205 pub fn restore_project_with_progress(
207 &mut self,
208 project_uuid: &uuid::Uuid,
209 progress: Option<&dyn Fn(u64, u64)>,
210 ) -> ClientResult<Vec<u8>> {
211 let req = protocol::build_restore_request(*project_uuid);
213 send_client_msg(&mut self.stream, &req)?;
214
215 let (total_size, _file_count) = match recv_server_msg(&mut self.stream, &mut self.decoder)? {
217 ServerMessage::RestoreAccept(accept) => {
218 log::info!("Restoring {} bytes ({} files)", accept.total_size, accept.file_count);
219 (accept.total_size, accept.file_count)
220 }
221 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
222 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
223 };
224
225 let prefix = self.decoder.drain_bytes();
230
231 let mut data = Vec::with_capacity(total_size as usize);
233 let mut buf = Vec::with_capacity(prefix.len() + 8192);
234 buf.extend_from_slice(&prefix);
235 let mut pos: usize = 0;
236
237 loop {
238 while buf.len() - pos < 4 {
240 let start = buf.len();
241 buf.resize(start + 8192, 0);
242 let n = self.stream
243 .read(&mut buf[start..])
244 .map_err(|e| format!("chunk read: {}", e))?;
245 if n == 0 {
246 return Err("Connection closed during chunk transfer".into());
247 }
248 buf.truncate(start + n);
249 }
250
251 let chunk_len = u32::from_be_bytes([
252 buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3],
253 ]) as usize;
254 pos += 4;
255
256 if chunk_len == 0 {
257 break; }
259
260 while buf.len() - pos < chunk_len {
262 let start = buf.len();
263 buf.resize(start + (chunk_len - (buf.len() - pos)).max(8192), 0);
264 let n = self.stream
265 .read(&mut buf[start..])
266 .map_err(|e| format!("chunk read: {}", e))?;
267 if n == 0 {
268 return Err("Connection closed during chunk data".into());
269 }
270 buf.truncate(start + n);
271 }
272
273 data.extend_from_slice(&buf[pos..pos + chunk_len]);
274 pos += chunk_len;
275
276 if let Some(ref cb) = progress {
277 cb(data.len() as u64, total_size);
278 }
279
280 if pos > 65536 {
282 buf.drain(..pos);
283 pos = 0;
284 }
285 }
286
287 log::info!("Restored {} bytes", data.len());
288 Ok(data)
289 }
290
291 pub fn get_status(
293 &mut self,
294 project_uuid: Option<&uuid::Uuid>,
295 ) -> ClientResult<Vec<ProjectInfo>> {
296 let req = protocol::ClientMessage::StatusRequest(
297 protocol::StatusRequest { project_uuid: project_uuid.copied() },
298 );
299 send_client_msg(&mut self.stream, &req)?;
300
301 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
302 ServerMessage::StatusResponse(response) => Ok(response.projects),
303 ServerMessage::Error(e) => Err(format!("Server error: {}", e.message)),
304 other => Err(format!("Unexpected response: {:?}", other.message_type())),
305 }
306 }
307}
308
309fn perform_handshake(
315 stream: &mut impl ReadWrite,
316 decoder: &mut FrameDecoder,
317 psk: &[u8; 32],
318 client_id: &str,
319) -> ClientResult<()> {
320 let mut nonce = [0u8; 32];
321 ring::rand::SystemRandom::new()
322 .fill(&mut nonce)
323 .map_err(|_| "CSPRNG failure".to_string())?;
324
325 let proof = crypto::compute_client_proof(psk, &nonce);
326
327 let hs = ClientMessage::Handshake(Handshake {
329 version: frame::PROTOCOL_VERSION,
330 client_id: client_id.to_string(),
331 nonce,
332 proof,
333 });
334 send_client_msg(stream, &hs)?;
335
336 match recv_server_msg(stream, decoder)? {
338 ServerMessage::HandshakeAck(ack) => {
339 if !ack.success {
340 return Err(format!(
341 "Authentication failed: {}",
342 ack.reason.unwrap_or_default()
343 ));
344 }
345
346 let expected = crypto::compute_server_proof(psk, &nonce, &ack.server_nonce);
348 if expected != ack.server_proof {
349 return Err("Server authentication failed: invalid proof".into());
350 }
351
352 log::info!("Authenticated to server v{}", ack.server_version);
353 Ok(())
354 }
355 ServerMessage::Error(e) => Err(format!("Server rejected handshake: {}", e.message)),
356 other => Err(format!("Unexpected handshake response: {:?}", other.message_type())),
357 }
358}
359
360fn send_client_msg(
362 stream: &mut impl Write,
363 msg: &ClientMessage,
364) -> ClientResult<()> {
365 let frame = frame::encode_frame(msg, msg.message_type())
366 .map_err(|e| format!("encode: {}", e))?;
367 stream.write_all(&frame).map_err(|e| format!("write: {}", e))?;
368 stream.flush().map_err(|e| format!("flush: {}", e))?;
369 Ok(())
370}
371
372fn recv_server_msg(
377 stream: &mut impl Read,
378 decoder: &mut FrameDecoder,
379) -> ClientResult<ServerMessage> {
380 let mut buf = [0u8; 8192];
381
382 loop {
383 if let Some((header, payload)) = decoder.try_decode()
384 .map_err(|e| format!("decode: {}", e))?
385 {
386 return protocol::decode_server_message(header.msg_type, &payload)
387 .map_err(|e| format!("deserialize: {}", e));
388 }
389
390 let n = stream.read(&mut buf).map_err(|e| format!("read: {}", e))?;
391 if n == 0 {
392 return Err("Connection closed by server".into());
393 }
394 decoder.push_bytes(&buf[..n]);
395 }
396}
397
398fn connect_tls(
409 tcp_stream: TcpStream,
410 config: &PwrConfig,
411) -> ClientResult<rustls::StreamOwned<rustls::ClientConnection, TcpStream>> {
412 let pinned = config.server_fingerprint.clone();
413 let verifier = Arc::new(CertVerifier::new(pinned));
414
415 let tls_config = rustls::ClientConfig::builder()
416 .dangerous()
417 .with_custom_certificate_verifier(verifier)
418 .with_no_client_auth();
419
420 let server_name: ServerName = config
421 .server_host
422 .clone()
423 .try_into()
424 .map_err(|_| format!("Invalid server hostname: {}", config.server_host))?;
425
426 let client_conn = rustls::ClientConnection::new(
427 Arc::new(tls_config),
428 server_name,
429 )
430 .map_err(|e| format!("TLS client setup: {}", e))?;
431
432 let mut tls_stream = rustls::StreamOwned::new(client_conn, tcp_stream);
433
434 tls_stream.flush().map_err(|e| format!("TLS handshake: {}", e))?;
436
437 log::info!(
438 "TLS connection established to {}:{}",
439 config.server_host,
440 config.server_port
441 );
442
443 Ok(tls_stream)
444}
445
446#[derive(Debug)]
452struct CertVerifier {
453 pinned_fingerprint: Option<String>,
454}
455
456impl CertVerifier {
457 fn new(pinned_fingerprint: Option<String>) -> Self {
458 Self { pinned_fingerprint }
459 }
460}
461
462impl ServerCertVerifier for CertVerifier {
463 fn verify_server_cert(
464 &self,
465 end_entity: &rustls::pki_types::CertificateDer<'_>,
466 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
467 _server_name: &ServerName<'_>,
468 _ocsp_response: &[u8],
469 _now: UnixTime,
470 ) -> Result<ServerCertVerified, rustls::Error> {
471 if let Some(ref pinned) = self.pinned_fingerprint {
473 let actual = crypto::sha256_hex(end_entity.as_ref());
474 if actual != *pinned {
475 return Err(rustls::Error::General(format!(
476 "Certificate fingerprint mismatch: expected {}",
477 pinned
478 )));
479 }
480 log::info!("Server certificate fingerprint verified against pinned value");
481 } else {
482 log::warn!(
483 "No certificate fingerprint pinned. Server cert SHA-256: {}",
484 crypto::sha256_hex(end_entity.as_ref())
485 );
486 }
487 Ok(ServerCertVerified::assertion())
488 }
489
490 fn verify_tls12_signature(
491 &self,
492 _message: &[u8],
493 _cert: &rustls::pki_types::CertificateDer<'_>,
494 _dss: &DigitallySignedStruct,
495 ) -> Result<HandshakeSignatureValid, rustls::Error> {
496 Ok(HandshakeSignatureValid::assertion())
498 }
499
500 fn verify_tls13_signature(
501 &self,
502 _message: &[u8],
503 _cert: &rustls::pki_types::CertificateDer<'_>,
504 _dss: &DigitallySignedStruct,
505 ) -> Result<HandshakeSignatureValid, rustls::Error> {
506 Ok(HandshakeSignatureValid::assertion())
508 }
509
510 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
511 vec![
512 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
513 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
514 rustls::SignatureScheme::RSA_PSS_SHA512,
515 rustls::SignatureScheme::RSA_PSS_SHA256,
516 rustls::SignatureScheme::RSA_PKCS1_SHA512,
517 rustls::SignatureScheme::RSA_PKCS1_SHA256,
518 ]
519 }
520}