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
80 .to_socket_addrs()
81 .map_err(|e| format!("Cannot resolve {}: {}", addr, e))?
82 .collect();
83
84 if addrs.is_empty() {
85 return Err(format!("No addresses found for {}", addr));
86 }
87
88 let mut last_err = String::new();
90 for a in &addrs {
91 match TcpStream::connect_timeout(a, timeout) {
92 Ok(stream) => {
93 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(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 &stream, &mut decoder, &psk, "pwr-cli")?;
108 (Box::new(stream) as Box<dyn ReadWrite>, decoder)
109 };
110
111 return Ok(Self { stream, decoder });
112 }
113 Err(e) => {
114 log::debug!(" {}: {}", a, e);
115 last_err = format!("{}: {}", a, e);
116 }
117 }
118 }
119
120 Err(format!("Connection to {} failed (tried {} addresses): {}",
121 addr, addrs.len(), last_err))
122 }
123
124 pub fn archive_project(
130 &mut self,
131 project_uuid: &uuid::Uuid,
132 project_name: &str,
133 archive_data: &[u8],
134 archive_hash: &str,
135 ) -> ClientResult<()> {
136 self.archive_project_with_progress(
137 project_uuid, project_name, archive_data, archive_hash, None,
138 )
139 }
140
141 pub fn archive_project_with_progress(
143 &mut self,
144 project_uuid: &uuid::Uuid,
145 project_name: &str,
146 archive_data: &[u8],
147 archive_hash: &str,
148 progress: Option<&dyn Fn(u64, u64)>,
149 ) -> ClientResult<()> {
150 let req = protocol::build_archive_request(
152 *project_uuid,
153 project_name,
154 archive_data.len() as u64,
155 1, true,
157 );
158 send_client_msg(&mut self.stream, &req)?;
159
160 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
162 ServerMessage::ArchiveAccept(accept) => {
163 log::debug!("Archive accepted, session {}", accept.session_id);
164 }
165 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
166 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
167 }
168
169 let chunk_size = 1024 * 1024; let mut bytes_sent = 0u64;
172 let total = archive_data.len() as u64;
173 for (i, chunk) in archive_data.chunks(chunk_size).enumerate() {
174 self.stream
175 .write_all(&(chunk.len() as u32).to_be_bytes())
176 .map_err(|e| format!("chunk write: {}", e))?;
177 self.stream
178 .write_all(chunk)
179 .map_err(|e| format!("chunk data write: {}", e))?;
180 bytes_sent += chunk.len() as u64;
181 if let Some(ref cb) = progress {
182 cb(bytes_sent, total);
183 }
184 log::debug!("Sent chunk {} ({} bytes)", i, chunk.len());
185 }
186
187 self.stream
189 .write_all(&0u32.to_be_bytes())
190 .map_err(|e| format!("eof write: {}", e))?;
191 self.stream.flush().map_err(|e| format!("flush: {}", e))?;
192
193 let complete = protocol::build_archive_complete(
195 archive_data.len() as u64,
196 archive_hash,
197 );
198 send_client_msg(&mut self.stream, &complete)?;
199
200 log::info!("Archive sent: {} bytes, hash: {}", archive_data.len(), archive_hash);
201 Ok(())
202 }
203
204 pub fn restore_project(
209 &mut self,
210 project_uuid: &uuid::Uuid,
211 ) -> ClientResult<Vec<u8>> {
212 self.restore_project_with_progress(project_uuid, None)
213 }
214
215 pub fn restore_project_with_progress(
217 &mut self,
218 project_uuid: &uuid::Uuid,
219 progress: Option<&dyn Fn(u64, u64)>,
220 ) -> ClientResult<Vec<u8>> {
221 let req = protocol::build_restore_request(*project_uuid);
223 send_client_msg(&mut self.stream, &req)?;
224
225 let (total_size, _file_count) = match recv_server_msg(&mut self.stream, &mut self.decoder)? {
227 ServerMessage::RestoreAccept(accept) => {
228 log::info!("Restoring {} bytes ({} files)", accept.total_size, accept.file_count);
229 (accept.total_size, accept.file_count)
230 }
231 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
232 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
233 };
234
235 let prefix = self.decoder.drain_bytes();
240
241 let mut data = Vec::with_capacity(total_size as usize);
243 let mut buf = Vec::with_capacity(prefix.len() + 8192);
244 buf.extend_from_slice(&prefix);
245 let mut pos: usize = 0;
246
247 loop {
248 while buf.len() - pos < 4 {
250 let start = buf.len();
251 buf.resize(start + 8192, 0);
252 let n = self.stream
253 .read(&mut buf[start..])
254 .map_err(|e| format!("chunk read: {}", e))?;
255 if n == 0 {
256 return Err("Connection closed during chunk transfer".into());
257 }
258 buf.truncate(start + n);
259 }
260
261 let chunk_len = u32::from_be_bytes([
262 buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3],
263 ]) as usize;
264 pos += 4;
265
266 if chunk_len == 0 {
267 break; }
269
270 while buf.len() - pos < chunk_len {
272 let start = buf.len();
273 buf.resize(start + (chunk_len - (buf.len() - pos)).max(8192), 0);
274 let n = self.stream
275 .read(&mut buf[start..])
276 .map_err(|e| format!("chunk read: {}", e))?;
277 if n == 0 {
278 return Err("Connection closed during chunk data".into());
279 }
280 buf.truncate(start + n);
281 }
282
283 data.extend_from_slice(&buf[pos..pos + chunk_len]);
284 pos += chunk_len;
285
286 if let Some(ref cb) = progress {
287 cb(data.len() as u64, total_size);
288 }
289
290 if pos > 65536 {
292 buf.drain(..pos);
293 pos = 0;
294 }
295 }
296
297 log::info!("Restored {} bytes", data.len());
298 Ok(data)
299 }
300
301 pub fn get_status(
303 &mut self,
304 project_uuid: Option<&uuid::Uuid>,
305 ) -> ClientResult<Vec<ProjectInfo>> {
306 let req = protocol::ClientMessage::StatusRequest(
307 protocol::StatusRequest { project_uuid: project_uuid.copied() },
308 );
309 send_client_msg(&mut self.stream, &req)?;
310
311 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
312 ServerMessage::StatusResponse(response) => Ok(response.projects),
313 ServerMessage::Error(e) => Err(format!("Server error: {}", e.message)),
314 other => Err(format!("Unexpected response: {:?}", other.message_type())),
315 }
316 }
317}
318
319fn perform_handshake(
325 stream: &mut impl ReadWrite,
326 decoder: &mut FrameDecoder,
327 psk: &[u8; 32],
328 client_id: &str,
329) -> ClientResult<()> {
330 let mut nonce = [0u8; 32];
331 ring::rand::SystemRandom::new()
332 .fill(&mut nonce)
333 .map_err(|_| "CSPRNG failure".to_string())?;
334
335 let proof = crypto::compute_client_proof(psk, &nonce);
336
337 let hs = ClientMessage::Handshake(Handshake {
339 version: frame::PROTOCOL_VERSION,
340 client_id: client_id.to_string(),
341 nonce,
342 proof,
343 });
344 send_client_msg(stream, &hs)?;
345
346 match recv_server_msg(stream, decoder)? {
348 ServerMessage::HandshakeAck(ack) => {
349 if !ack.success {
350 return Err(format!(
351 "Authentication failed: {}",
352 ack.reason.unwrap_or_default()
353 ));
354 }
355
356 let expected = crypto::compute_server_proof(psk, &nonce, &ack.server_nonce);
358 if expected != ack.server_proof {
359 return Err("Server authentication failed: invalid proof".into());
360 }
361
362 log::info!("Authenticated to server v{}", ack.server_version);
363 Ok(())
364 }
365 ServerMessage::Error(e) => Err(format!("Server rejected handshake: {}", e.message)),
366 other => Err(format!("Unexpected handshake response: {:?}", other.message_type())),
367 }
368}
369
370fn send_client_msg(
372 stream: &mut impl Write,
373 msg: &ClientMessage,
374) -> ClientResult<()> {
375 let frame = frame::encode_frame(msg, msg.message_type())
376 .map_err(|e| format!("encode: {}", e))?;
377 stream.write_all(&frame).map_err(|e| format!("write: {}", e))?;
378 stream.flush().map_err(|e| format!("flush: {}", e))?;
379 Ok(())
380}
381
382fn recv_server_msg(
387 stream: &mut impl Read,
388 decoder: &mut FrameDecoder,
389) -> ClientResult<ServerMessage> {
390 let mut buf = [0u8; 8192];
391
392 loop {
393 if let Some((header, payload)) = decoder.try_decode()
394 .map_err(|e| format!("decode: {}", e))?
395 {
396 return protocol::decode_server_message(header.msg_type, &payload)
397 .map_err(|e| format!("deserialize: {}", e));
398 }
399
400 let n = stream.read(&mut buf).map_err(|e| format!("read: {}", e))?;
401 if n == 0 {
402 return Err("Connection closed by server".into());
403 }
404 decoder.push_bytes(&buf[..n]);
405 }
406}
407
408fn connect_tls(
419 tcp_stream: TcpStream,
420 config: &PwrConfig,
421) -> ClientResult<rustls::StreamOwned<rustls::ClientConnection, TcpStream>> {
422 let pinned = config.server_fingerprint.clone();
423 let verifier = Arc::new(CertVerifier::new(pinned));
424
425 let tls_config = rustls::ClientConfig::builder()
426 .dangerous()
427 .with_custom_certificate_verifier(verifier)
428 .with_no_client_auth();
429
430 let server_name: ServerName = config
431 .server_host
432 .clone()
433 .try_into()
434 .map_err(|_| format!("Invalid server hostname: {}", config.server_host))?;
435
436 let client_conn = rustls::ClientConnection::new(
437 Arc::new(tls_config),
438 server_name,
439 )
440 .map_err(|e| format!("TLS client setup: {}", e))?;
441
442 let mut tls_stream = rustls::StreamOwned::new(client_conn, tcp_stream);
443
444 tls_stream.flush().map_err(|e| format!("TLS handshake: {}", e))?;
446
447 log::info!(
448 "TLS connection established to {}:{}",
449 config.server_host,
450 config.server_port
451 );
452
453 Ok(tls_stream)
454}
455
456#[derive(Debug)]
462struct CertVerifier {
463 pinned_fingerprint: Option<String>,
464}
465
466impl CertVerifier {
467 fn new(pinned_fingerprint: Option<String>) -> Self {
468 Self { pinned_fingerprint }
469 }
470}
471
472impl ServerCertVerifier for CertVerifier {
473 fn verify_server_cert(
474 &self,
475 end_entity: &rustls::pki_types::CertificateDer<'_>,
476 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
477 _server_name: &ServerName<'_>,
478 _ocsp_response: &[u8],
479 _now: UnixTime,
480 ) -> Result<ServerCertVerified, rustls::Error> {
481 if let Some(ref pinned) = self.pinned_fingerprint {
483 let actual = crypto::sha256_hex(end_entity.as_ref());
484 if actual != *pinned {
485 return Err(rustls::Error::General(format!(
486 "Certificate fingerprint mismatch: expected {}",
487 pinned
488 )));
489 }
490 log::info!("Server certificate fingerprint verified against pinned value");
491 } else {
492 log::warn!(
493 "No certificate fingerprint pinned. Server cert SHA-256: {}",
494 crypto::sha256_hex(end_entity.as_ref())
495 );
496 }
497 Ok(ServerCertVerified::assertion())
498 }
499
500 fn verify_tls12_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 verify_tls13_signature(
511 &self,
512 _message: &[u8],
513 _cert: &rustls::pki_types::CertificateDer<'_>,
514 _dss: &DigitallySignedStruct,
515 ) -> Result<HandshakeSignatureValid, rustls::Error> {
516 Ok(HandshakeSignatureValid::assertion())
518 }
519
520 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
521 vec![
522 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
523 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
524 rustls::SignatureScheme::RSA_PSS_SHA512,
525 rustls::SignatureScheme::RSA_PSS_SHA256,
526 rustls::SignatureScheme::RSA_PKCS1_SHA512,
527 rustls::SignatureScheme::RSA_PKCS1_SHA256,
528 ]
529 }
530}