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 mut data = Vec::with_capacity(total_size as usize);
215 let mut header_buf = [0u8; 4];
216
217 loop {
218 self.stream
219 .read_exact(&mut header_buf)
220 .map_err(|e| format!("chunk header read: {}", e))?;
221
222 let chunk_len = u32::from_be_bytes(header_buf) as usize;
223 if chunk_len == 0 {
224 break; }
226
227 let start = data.len();
228 data.resize(start + chunk_len, 0);
229 self.stream
230 .read_exact(&mut data[start..])
231 .map_err(|e| format!("chunk data read: {}", e))?;
232
233 if let Some(ref cb) = progress {
234 cb(data.len() as u64, total_size);
235 }
236 }
237
238 log::info!("Restored {} bytes", data.len());
239 Ok(data)
240 }
241
242 pub fn get_status(
244 &mut self,
245 project_uuid: Option<&uuid::Uuid>,
246 ) -> ClientResult<Vec<ProjectInfo>> {
247 let req = protocol::ClientMessage::StatusRequest(
248 protocol::StatusRequest { project_uuid: project_uuid.copied() },
249 );
250 send_client_msg(&mut self.stream, &req)?;
251
252 match recv_server_msg(&mut self.stream, &mut self.decoder)? {
253 ServerMessage::StatusResponse(response) => Ok(response.projects),
254 ServerMessage::Error(e) => Err(format!("Server error: {}", e.message)),
255 other => Err(format!("Unexpected response: {:?}", other.message_type())),
256 }
257 }
258}
259
260fn perform_handshake(
266 stream: &mut impl ReadWrite,
267 decoder: &mut FrameDecoder,
268 psk: &[u8; 32],
269 client_id: &str,
270) -> ClientResult<()> {
271 let mut nonce = [0u8; 32];
272 ring::rand::SystemRandom::new()
273 .fill(&mut nonce)
274 .map_err(|_| "CSPRNG failure".to_string())?;
275
276 let proof = crypto::compute_client_proof(psk, &nonce);
277
278 let hs = ClientMessage::Handshake(Handshake {
280 version: frame::PROTOCOL_VERSION,
281 client_id: client_id.to_string(),
282 nonce,
283 proof,
284 });
285 send_client_msg(stream, &hs)?;
286
287 match recv_server_msg(stream, decoder)? {
289 ServerMessage::HandshakeAck(ack) => {
290 if !ack.success {
291 return Err(format!(
292 "Authentication failed: {}",
293 ack.reason.unwrap_or_default()
294 ));
295 }
296
297 let expected = crypto::compute_server_proof(psk, &nonce, &ack.server_nonce);
299 if expected != ack.server_proof {
300 return Err("Server authentication failed: invalid proof".into());
301 }
302
303 log::info!("Authenticated to server v{}", ack.server_version);
304 Ok(())
305 }
306 ServerMessage::Error(e) => Err(format!("Server rejected handshake: {}", e.message)),
307 other => Err(format!("Unexpected handshake response: {:?}", other.message_type())),
308 }
309}
310
311fn send_client_msg(
313 stream: &mut impl Write,
314 msg: &ClientMessage,
315) -> ClientResult<()> {
316 let frame = frame::encode_frame(msg, msg.message_type())
317 .map_err(|e| format!("encode: {}", e))?;
318 stream.write_all(&frame).map_err(|e| format!("write: {}", e))?;
319 stream.flush().map_err(|e| format!("flush: {}", e))?;
320 Ok(())
321}
322
323fn recv_server_msg(
328 stream: &mut impl Read,
329 decoder: &mut FrameDecoder,
330) -> ClientResult<ServerMessage> {
331 let mut buf = [0u8; 8192];
332
333 loop {
334 if let Some((header, payload)) = decoder.try_decode()
335 .map_err(|e| format!("decode: {}", e))?
336 {
337 return protocol::decode_server_message(header.msg_type, &payload)
338 .map_err(|e| format!("deserialize: {}", e));
339 }
340
341 let n = stream.read(&mut buf).map_err(|e| format!("read: {}", e))?;
342 if n == 0 {
343 return Err("Connection closed by server".into());
344 }
345 decoder.push_bytes(&buf[..n]);
346 }
347}
348
349fn connect_tls(
360 tcp_stream: TcpStream,
361 config: &PwrConfig,
362) -> ClientResult<rustls::StreamOwned<rustls::ClientConnection, TcpStream>> {
363 let pinned = config.server_fingerprint.clone();
364 let verifier = Arc::new(CertVerifier::new(pinned));
365
366 let tls_config = rustls::ClientConfig::builder()
367 .dangerous()
368 .with_custom_certificate_verifier(verifier)
369 .with_no_client_auth();
370
371 let server_name: ServerName = config
372 .server_host
373 .clone()
374 .try_into()
375 .map_err(|_| format!("Invalid server hostname: {}", config.server_host))?;
376
377 let client_conn = rustls::ClientConnection::new(
378 Arc::new(tls_config),
379 server_name,
380 )
381 .map_err(|e| format!("TLS client setup: {}", e))?;
382
383 let mut tls_stream = rustls::StreamOwned::new(client_conn, tcp_stream);
384
385 tls_stream.flush().map_err(|e| format!("TLS handshake: {}", e))?;
387
388 log::info!(
389 "TLS connection established to {}:{}",
390 config.server_host,
391 config.server_port
392 );
393
394 Ok(tls_stream)
395}
396
397#[derive(Debug)]
403struct CertVerifier {
404 pinned_fingerprint: Option<String>,
405}
406
407impl CertVerifier {
408 fn new(pinned_fingerprint: Option<String>) -> Self {
409 Self { pinned_fingerprint }
410 }
411}
412
413impl ServerCertVerifier for CertVerifier {
414 fn verify_server_cert(
415 &self,
416 end_entity: &rustls::pki_types::CertificateDer<'_>,
417 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
418 _server_name: &ServerName<'_>,
419 _ocsp_response: &[u8],
420 _now: UnixTime,
421 ) -> Result<ServerCertVerified, rustls::Error> {
422 if let Some(ref pinned) = self.pinned_fingerprint {
424 let actual = crypto::sha256_hex(end_entity.as_ref());
425 if actual != *pinned {
426 return Err(rustls::Error::General(format!(
427 "Certificate fingerprint mismatch: expected {}",
428 pinned
429 )));
430 }
431 log::info!("Server certificate fingerprint verified against pinned value");
432 } else {
433 log::warn!(
434 "No certificate fingerprint pinned. Server cert SHA-256: {}",
435 crypto::sha256_hex(end_entity.as_ref())
436 );
437 }
438 Ok(ServerCertVerified::assertion())
439 }
440
441 fn verify_tls12_signature(
442 &self,
443 _message: &[u8],
444 _cert: &rustls::pki_types::CertificateDer<'_>,
445 _dss: &DigitallySignedStruct,
446 ) -> Result<HandshakeSignatureValid, rustls::Error> {
447 Ok(HandshakeSignatureValid::assertion())
449 }
450
451 fn verify_tls13_signature(
452 &self,
453 _message: &[u8],
454 _cert: &rustls::pki_types::CertificateDer<'_>,
455 _dss: &DigitallySignedStruct,
456 ) -> Result<HandshakeSignatureValid, rustls::Error> {
457 Ok(HandshakeSignatureValid::assertion())
459 }
460
461 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
462 vec![
463 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
464 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
465 rustls::SignatureScheme::RSA_PSS_SHA512,
466 rustls::SignatureScheme::RSA_PSS_SHA256,
467 rustls::SignatureScheme::RSA_PKCS1_SHA512,
468 rustls::SignatureScheme::RSA_PKCS1_SHA256,
469 ]
470 }
471}