1use std::io::{Read, Write};
8use std::net::TcpStream;
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 tcp_stream = TcpStream::connect_timeout(
76 &addr.parse().map_err(|e| format!("Invalid address: {}", e))?,
77 timeout,
78 )
79 .map_err(|e| format!("Connection to {} failed: {}", addr, e))?;
80
81 tcp_stream
82 .set_read_timeout(Some(Duration::from_secs(30)))
83 .map_err(|e| format!("set_read_timeout: {}", e))?;
84
85 let psk = crypto::psk_from_hex(&config.server_psk)
86 .map_err(|e| format!("Invalid PSK: {}", e))?;
87
88 let (stream, decoder) = if tls {
89 let mut tls_stream = connect_tls(tcp_stream, config)?;
90 let mut decoder = FrameDecoder::new();
91 perform_handshake(&mut tls_stream, &mut decoder, &psk, "pwr-cli")?;
92 (Box::new(tls_stream) as Box<dyn ReadWrite>, decoder)
93 } else {
94 let mut decoder = FrameDecoder::new();
95 perform_handshake(&mut &tcp_stream, &mut decoder, &psk, "pwr-cli")?;
96 (Box::new(tcp_stream) as Box<dyn ReadWrite>, decoder)
97 };
98
99 Ok(Self { stream, decoder })
100 }
101
102 pub fn archive_project(
108 &mut self,
109 project_uuid: &uuid::Uuid,
110 project_name: &str,
111 archive_data: &[u8],
112 archive_hash: &str,
113 ) -> ClientResult<()> {
114 self.archive_project_with_progress(
115 project_uuid, project_name, archive_data, archive_hash, None,
116 )
117 }
118
119 pub fn archive_project_with_progress(
121 &mut self,
122 project_uuid: &uuid::Uuid,
123 project_name: &str,
124 archive_data: &[u8],
125 archive_hash: &str,
126 progress: Option<&dyn Fn(u64, u64)>,
127 ) -> ClientResult<()> {
128 let req = protocol::build_archive_request(
130 *project_uuid,
131 project_name,
132 archive_data.len() as u64,
133 1, true,
135 );
136 send_client_msg(&mut self.stream, &req)?;
137
138 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
140 ServerMessage::ArchiveAccept(accept) => {
141 log::debug!("Archive accepted, session {}", accept.session_id);
142 }
143 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
144 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
145 }
146
147 let chunk_size = 1024 * 1024; let mut bytes_sent = 0u64;
150 let total = archive_data.len() as u64;
151 for (i, chunk) in archive_data.chunks(chunk_size).enumerate() {
152 self.stream
153 .write_all(&(chunk.len() as u32).to_be_bytes())
154 .map_err(|e| format!("chunk write: {}", e))?;
155 self.stream
156 .write_all(chunk)
157 .map_err(|e| format!("chunk data write: {}", e))?;
158 bytes_sent += chunk.len() as u64;
159 if let Some(ref cb) = progress {
160 cb(bytes_sent, total);
161 }
162 log::debug!("Sent chunk {} ({} bytes)", i, chunk.len());
163 }
164
165 self.stream
167 .write_all(&0u32.to_be_bytes())
168 .map_err(|e| format!("eof write: {}", e))?;
169 self.stream.flush().map_err(|e| format!("flush: {}", e))?;
170
171 let complete = protocol::build_archive_complete(
173 archive_data.len() as u64,
174 archive_hash,
175 );
176 send_client_msg(&mut self.stream, &complete)?;
177
178 log::info!("Archive sent: {} bytes, hash: {}", archive_data.len(), archive_hash);
179 Ok(())
180 }
181
182 pub fn restore_project(
187 &mut self,
188 project_uuid: &uuid::Uuid,
189 ) -> ClientResult<Vec<u8>> {
190 self.restore_project_with_progress(project_uuid, None)
191 }
192
193 pub fn restore_project_with_progress(
195 &mut self,
196 project_uuid: &uuid::Uuid,
197 progress: Option<&dyn Fn(u64, u64)>,
198 ) -> ClientResult<Vec<u8>> {
199 let req = protocol::build_restore_request(*project_uuid);
201 send_client_msg(&mut self.stream, &req)?;
202
203 let (total_size, _file_count) = match recv_server_msg(&mut self.stream, &mut self.decoder)? {
205 ServerMessage::RestoreAccept(accept) => {
206 log::info!("Restoring {} bytes ({} files)", accept.total_size, accept.file_count);
207 (accept.total_size, accept.file_count)
208 }
209 ServerMessage::Error(e) => return Err(format!("Server rejected: {}", e.message)),
210 other => return Err(format!("Unexpected response: {:?}", other.message_type())),
211 };
212
213 let prefix = self.decoder.drain_bytes();
218
219 let mut data = Vec::with_capacity(total_size as usize);
221 let mut buf = Vec::with_capacity(prefix.len() + 8192);
222 buf.extend_from_slice(&prefix);
223 let mut pos: usize = 0;
224
225 loop {
226 while buf.len() - pos < 4 {
228 let start = buf.len();
229 buf.resize(start + 8192, 0);
230 let n = self.stream
231 .read(&mut buf[start..])
232 .map_err(|e| format!("chunk read: {}", e))?;
233 if n == 0 {
234 return Err("Connection closed during chunk transfer".into());
235 }
236 buf.truncate(start + n);
237 }
238
239 let chunk_len = u32::from_be_bytes([
240 buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3],
241 ]) as usize;
242 pos += 4;
243
244 if chunk_len == 0 {
245 break; }
247
248 while buf.len() - pos < chunk_len {
250 let start = buf.len();
251 buf.resize(start + (chunk_len - (buf.len() - pos)).max(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 data".into());
257 }
258 buf.truncate(start + n);
259 }
260
261 data.extend_from_slice(&buf[pos..pos + chunk_len]);
262 pos += chunk_len;
263
264 if let Some(ref cb) = progress {
265 cb(data.len() as u64, total_size);
266 }
267
268 if pos > 65536 {
270 buf.drain(..pos);
271 pos = 0;
272 }
273 }
274
275 log::info!("Restored {} bytes", data.len());
276 Ok(data)
277 }
278
279 pub fn get_status(
281 &mut self,
282 project_uuid: Option<&uuid::Uuid>,
283 ) -> ClientResult<Vec<ProjectInfo>> {
284 let req = protocol::ClientMessage::StatusRequest(
285 protocol::StatusRequest { project_uuid: project_uuid.copied() },
286 );
287 send_client_msg(&mut self.stream, &req)?;
288
289 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
290 ServerMessage::StatusResponse(response) => Ok(response.projects),
291 ServerMessage::Error(e) => Err(format!("Server error: {}", e.message)),
292 other => Err(format!("Unexpected response: {:?}", other.message_type())),
293 }
294 }
295}
296
297fn perform_handshake(
303 stream: &mut impl ReadWrite,
304 decoder: &mut FrameDecoder,
305 psk: &[u8; 32],
306 client_id: &str,
307) -> ClientResult<()> {
308 let mut nonce = [0u8; 32];
309 ring::rand::SystemRandom::new()
310 .fill(&mut nonce)
311 .map_err(|_| "CSPRNG failure".to_string())?;
312
313 let proof = crypto::compute_client_proof(psk, &nonce);
314
315 let hs = ClientMessage::Handshake(Handshake {
317 version: frame::PROTOCOL_VERSION,
318 client_id: client_id.to_string(),
319 nonce,
320 proof,
321 });
322 send_client_msg(stream, &hs)?;
323
324 match recv_server_msg(stream, decoder)? {
326 ServerMessage::HandshakeAck(ack) => {
327 if !ack.success {
328 return Err(format!(
329 "Authentication failed: {}",
330 ack.reason.unwrap_or_default()
331 ));
332 }
333
334 let expected = crypto::compute_server_proof(psk, &nonce, &ack.server_nonce);
336 if expected != ack.server_proof {
337 return Err("Server authentication failed: invalid proof".into());
338 }
339
340 log::info!("Authenticated to server v{}", ack.server_version);
341 Ok(())
342 }
343 ServerMessage::Error(e) => Err(format!("Server rejected handshake: {}", e.message)),
344 other => Err(format!("Unexpected handshake response: {:?}", other.message_type())),
345 }
346}
347
348fn send_client_msg(
350 stream: &mut impl Write,
351 msg: &ClientMessage,
352) -> ClientResult<()> {
353 let frame = frame::encode_frame(msg, msg.message_type())
354 .map_err(|e| format!("encode: {}", e))?;
355 stream.write_all(&frame).map_err(|e| format!("write: {}", e))?;
356 stream.flush().map_err(|e| format!("flush: {}", e))?;
357 Ok(())
358}
359
360fn recv_server_msg(
365 stream: &mut impl Read,
366 decoder: &mut FrameDecoder,
367) -> ClientResult<ServerMessage> {
368 let mut buf = [0u8; 8192];
369
370 loop {
371 if let Some((header, payload)) = decoder.try_decode()
372 .map_err(|e| format!("decode: {}", e))?
373 {
374 return protocol::decode_server_message(header.msg_type, &payload)
375 .map_err(|e| format!("deserialize: {}", e));
376 }
377
378 let n = stream.read(&mut buf).map_err(|e| format!("read: {}", e))?;
379 if n == 0 {
380 return Err("Connection closed by server".into());
381 }
382 decoder.push_bytes(&buf[..n]);
383 }
384}
385
386fn connect_tls(
397 tcp_stream: TcpStream,
398 config: &PwrConfig,
399) -> ClientResult<rustls::StreamOwned<rustls::ClientConnection, TcpStream>> {
400 let pinned = config.server_fingerprint.clone();
401 let verifier = Arc::new(CertVerifier::new(pinned));
402
403 let tls_config = rustls::ClientConfig::builder()
404 .dangerous()
405 .with_custom_certificate_verifier(verifier)
406 .with_no_client_auth();
407
408 let server_name: ServerName = config
409 .server_host
410 .clone()
411 .try_into()
412 .map_err(|_| format!("Invalid server hostname: {}", config.server_host))?;
413
414 let client_conn = rustls::ClientConnection::new(
415 Arc::new(tls_config),
416 server_name,
417 )
418 .map_err(|e| format!("TLS client setup: {}", e))?;
419
420 let mut tls_stream = rustls::StreamOwned::new(client_conn, tcp_stream);
421
422 tls_stream.flush().map_err(|e| format!("TLS handshake: {}", e))?;
424
425 log::info!(
426 "TLS connection established to {}:{}",
427 config.server_host,
428 config.server_port
429 );
430
431 Ok(tls_stream)
432}
433
434#[derive(Debug)]
440struct CertVerifier {
441 pinned_fingerprint: Option<String>,
442}
443
444impl CertVerifier {
445 fn new(pinned_fingerprint: Option<String>) -> Self {
446 Self { pinned_fingerprint }
447 }
448}
449
450impl ServerCertVerifier for CertVerifier {
451 fn verify_server_cert(
452 &self,
453 end_entity: &rustls::pki_types::CertificateDer<'_>,
454 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
455 _server_name: &ServerName<'_>,
456 _ocsp_response: &[u8],
457 _now: UnixTime,
458 ) -> Result<ServerCertVerified, rustls::Error> {
459 if let Some(ref pinned) = self.pinned_fingerprint {
461 let actual = crypto::sha256_hex(end_entity.as_ref());
462 if actual != *pinned {
463 return Err(rustls::Error::General(format!(
464 "Certificate fingerprint mismatch: expected {}",
465 pinned
466 )));
467 }
468 log::info!("Server certificate fingerprint verified against pinned value");
469 } else {
470 log::warn!(
471 "No certificate fingerprint pinned. Server cert SHA-256: {}",
472 crypto::sha256_hex(end_entity.as_ref())
473 );
474 }
475 Ok(ServerCertVerified::assertion())
476 }
477
478 fn verify_tls12_signature(
479 &self,
480 _message: &[u8],
481 _cert: &rustls::pki_types::CertificateDer<'_>,
482 _dss: &DigitallySignedStruct,
483 ) -> Result<HandshakeSignatureValid, rustls::Error> {
484 Ok(HandshakeSignatureValid::assertion())
486 }
487
488 fn verify_tls13_signature(
489 &self,
490 _message: &[u8],
491 _cert: &rustls::pki_types::CertificateDer<'_>,
492 _dss: &DigitallySignedStruct,
493 ) -> Result<HandshakeSignatureValid, rustls::Error> {
494 Ok(HandshakeSignatureValid::assertion())
496 }
497
498 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
499 vec![
500 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
501 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
502 rustls::SignatureScheme::RSA_PSS_SHA512,
503 rustls::SignatureScheme::RSA_PSS_SHA256,
504 rustls::SignatureScheme::RSA_PKCS1_SHA512,
505 rustls::SignatureScheme::RSA_PKCS1_SHA256,
506 ]
507 }
508}