1use std::time::Duration;
2
3use ap_noise::{InitiatorHandshake, MultiDeviceTransport, Psk};
4use ap_proxy_client::IncomingMessage;
5use ap_proxy_protocol::{IdentityFingerprint, RendezvousCode};
6use base64::{Engine, engine::general_purpose::STANDARD};
7use rand::RngCore;
8
9use crate::compat::{now_millis, timeout};
10use crate::proxy::ProxyClient;
11use tokio::sync::{mpsc, oneshot};
12use tracing::{debug, warn};
13
14use super::notify;
15use crate::traits::{ConnectionInfo, ConnectionStore, ConnectionUpdate, IdentityProvider};
16use crate::{
17 error::ClientError,
18 types::{
19 CredentialData, CredentialQuery, CredentialRequestPayload, CredentialResponsePayload,
20 ProtocolMessage,
21 },
22};
23
24const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
25
26#[derive(Debug, Clone)]
32pub enum RemoteClientNotification {
33 Connecting,
35 Connected {
37 fingerprint: IdentityFingerprint,
39 },
40 ReconnectingToSession {
42 fingerprint: IdentityFingerprint,
44 },
45 RendezvousResolving {
47 code: String,
49 },
50 RendezvousResolved {
52 fingerprint: IdentityFingerprint,
54 },
55 PskMode {
57 fingerprint: IdentityFingerprint,
59 },
60 HandshakeStart,
62 HandshakeProgress {
64 message: String,
66 },
67 HandshakeComplete,
69 HandshakeFingerprint {
71 fingerprint: String,
73 },
74 FingerprintVerified,
76 FingerprintRejected {
78 reason: String,
80 },
81 Ready {
83 can_request_credentials: bool,
85 },
86 CredentialRequestSent {
88 query: CredentialQuery,
90 },
91 CredentialReceived {
93 credential: CredentialData,
95 },
96 Error {
98 message: String,
100 context: Option<String>,
102 },
103 Disconnected {
105 reason: Option<String>,
107 },
108}
109
110#[derive(Debug)]
112pub struct RemoteClientFingerprintReply {
113 pub approved: bool,
115}
116
117#[derive(Debug)]
119pub enum RemoteClientRequest {
120 VerifyFingerprint {
122 fingerprint: String,
124 reply: oneshot::Sender<RemoteClientFingerprintReply>,
126 },
127}
128
129enum RemoteClientCommand {
135 PairWithHandshake {
136 rendezvous_code: String,
137 verify_fingerprint: bool,
138 reply: oneshot::Sender<Result<IdentityFingerprint, ClientError>>,
139 },
140 PairWithPsk {
141 psk: Psk,
142 remote_fingerprint: IdentityFingerprint,
143 reply: oneshot::Sender<Result<(), ClientError>>,
144 },
145 LoadCachedConnection {
146 remote_fingerprint: IdentityFingerprint,
147 reply: oneshot::Sender<Result<(), ClientError>>,
148 },
149 RequestCredential {
150 query: CredentialQuery,
151 credential_timeout: Option<Duration>,
152 reply: oneshot::Sender<Result<CredentialData, ClientError>>,
153 },
154 ListConnections {
155 reply: oneshot::Sender<Vec<ConnectionInfo>>,
156 },
157 HasConnection {
158 fingerprint: IdentityFingerprint,
159 reply: oneshot::Sender<bool>,
160 },
161}
162
163pub struct RemoteClientHandle {
178 pub client: RemoteClient,
179 pub notifications: mpsc::Receiver<RemoteClientNotification>,
180 pub requests: mpsc::Receiver<RemoteClientRequest>,
181}
182
183#[derive(Clone)]
184pub struct RemoteClient {
185 command_tx: mpsc::Sender<RemoteClientCommand>,
186}
187
188impl RemoteClient {
189 pub async fn connect(
198 identity_provider: Box<dyn IdentityProvider>,
199 connection_store: Box<dyn ConnectionStore>,
200 mut proxy_client: Box<dyn ProxyClient>,
201 ) -> Result<RemoteClientHandle, ClientError> {
202 let identity_keypair = identity_provider.identity().await;
203 let own_fingerprint = identity_keypair.identity().fingerprint();
204
205 debug!("Connecting to proxy with identity {:?}", own_fingerprint);
206
207 let (notification_tx, notification_rx) = mpsc::channel(32);
208 let (request_tx, request_rx) = mpsc::channel(32);
209
210 notify!(notification_tx, RemoteClientNotification::Connecting);
211
212 let incoming_rx = proxy_client.connect(identity_keypair).await?;
213
214 notify!(
215 notification_tx,
216 RemoteClientNotification::Connected {
217 fingerprint: own_fingerprint,
218 }
219 );
220
221 debug!("Connected to proxy successfully");
222
223 let (command_tx, command_rx) = mpsc::channel(32);
225
226 let inner = RemoteClientInner {
228 connection_store,
229 proxy_client,
230 transport: None,
231 remote_fingerprint: None,
232 };
233
234 #[cfg(target_arch = "wasm32")]
236 wasm_bindgen_futures::spawn_local(inner.run_event_loop(
237 incoming_rx,
238 command_rx,
239 notification_tx,
240 request_tx,
241 ));
242 #[cfg(not(target_arch = "wasm32"))]
243 tokio::spawn(inner.run_event_loop(incoming_rx, command_rx, notification_tx, request_tx));
244
245 Ok(RemoteClientHandle {
246 client: Self { command_tx },
247 notifications: notification_rx,
248 requests: request_rx,
249 })
250 }
251
252 pub async fn pair_with_handshake(
259 &self,
260 rendezvous_code: String,
261 verify_fingerprint: bool,
262 ) -> Result<IdentityFingerprint, ClientError> {
263 let (tx, rx) = oneshot::channel();
264 self.command_tx
265 .send(RemoteClientCommand::PairWithHandshake {
266 rendezvous_code,
267 verify_fingerprint,
268 reply: tx,
269 })
270 .await
271 .map_err(|_| ClientError::ChannelClosed)?;
272 rx.await.map_err(|_| ClientError::ChannelClosed)?
273 }
274
275 pub async fn pair_with_psk(
280 &self,
281 psk: Psk,
282 remote_fingerprint: IdentityFingerprint,
283 ) -> Result<(), ClientError> {
284 let (tx, rx) = oneshot::channel();
285 self.command_tx
286 .send(RemoteClientCommand::PairWithPsk {
287 psk,
288 remote_fingerprint,
289 reply: tx,
290 })
291 .await
292 .map_err(|_| ClientError::ChannelClosed)?;
293 rx.await.map_err(|_| ClientError::ChannelClosed)?
294 }
295
296 pub async fn load_cached_connection(
301 &self,
302 remote_fingerprint: IdentityFingerprint,
303 ) -> Result<(), ClientError> {
304 let (tx, rx) = oneshot::channel();
305 self.command_tx
306 .send(RemoteClientCommand::LoadCachedConnection {
307 remote_fingerprint,
308 reply: tx,
309 })
310 .await
311 .map_err(|_| ClientError::ChannelClosed)?;
312 rx.await.map_err(|_| ClientError::ChannelClosed)?
313 }
314
315 pub async fn request_credential(
320 &self,
321 query: &CredentialQuery,
322 timeout: Option<Duration>,
323 ) -> Result<CredentialData, ClientError> {
324 let (tx, rx) = oneshot::channel();
325 self.command_tx
326 .send(RemoteClientCommand::RequestCredential {
327 query: query.clone(),
328 credential_timeout: timeout,
329 reply: tx,
330 })
331 .await
332 .map_err(|_| ClientError::ChannelClosed)?;
333 rx.await.map_err(|_| ClientError::ChannelClosed)?
334 }
335
336 pub async fn list_connections(&self) -> Result<Vec<ConnectionInfo>, ClientError> {
338 let (tx, rx) = oneshot::channel();
339 self.command_tx
340 .send(RemoteClientCommand::ListConnections { reply: tx })
341 .await
342 .map_err(|_| ClientError::ChannelClosed)?;
343 rx.await.map_err(|_| ClientError::ChannelClosed)
344 }
345
346 pub async fn has_connection(
348 &self,
349 fingerprint: IdentityFingerprint,
350 ) -> Result<bool, ClientError> {
351 let (tx, rx) = oneshot::channel();
352 self.command_tx
353 .send(RemoteClientCommand::HasConnection {
354 fingerprint,
355 reply: tx,
356 })
357 .await
358 .map_err(|_| ClientError::ChannelClosed)?;
359 rx.await.map_err(|_| ClientError::ChannelClosed)
360 }
361}
362
363struct RemoteClientInner {
369 connection_store: Box<dyn ConnectionStore>,
370 proxy_client: Box<dyn ProxyClient>,
371 transport: Option<MultiDeviceTransport>,
372 remote_fingerprint: Option<IdentityFingerprint>,
373}
374
375impl RemoteClientInner {
376 async fn run_event_loop(
378 mut self,
379 mut incoming_rx: mpsc::UnboundedReceiver<IncomingMessage>,
380 mut command_rx: mpsc::Receiver<RemoteClientCommand>,
381 notification_tx: mpsc::Sender<RemoteClientNotification>,
382 request_tx: mpsc::Sender<RemoteClientRequest>,
383 ) {
384 loop {
385 tokio::select! {
386 msg = incoming_rx.recv() => {
387 match msg {
388 Some(_) => {
389 debug!("Received message while idle");
391 }
392 None => {
393 notify!(notification_tx, RemoteClientNotification::Disconnected {
395 reason: Some("Proxy connection closed".to_string()),
396 });
397 return;
398 }
399 }
400 }
401 cmd = command_rx.recv() => {
402 match cmd {
403 Some(cmd) => {
404 self.handle_command(
405 cmd,
406 &mut incoming_rx,
407 ¬ification_tx,
408 &request_tx,
409 ).await;
410 }
411 None => {
412 debug!("All RemoteClient handles dropped, shutting down event loop");
414 self.proxy_client.disconnect().await.ok();
415 return;
416 }
417 }
418 }
419 }
420 }
421 }
422
423 async fn handle_command(
425 &mut self,
426 cmd: RemoteClientCommand,
427 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
428 notification_tx: &mpsc::Sender<RemoteClientNotification>,
429 request_tx: &mpsc::Sender<RemoteClientRequest>,
430 ) {
431 match cmd {
432 RemoteClientCommand::PairWithHandshake {
433 rendezvous_code,
434 verify_fingerprint,
435 reply,
436 } => {
437 let result = self
438 .do_pair_with_handshake(
439 rendezvous_code,
440 verify_fingerprint,
441 incoming_rx,
442 notification_tx,
443 request_tx,
444 )
445 .await;
446 let _ = reply.send(result);
447 }
448 RemoteClientCommand::PairWithPsk {
449 psk,
450 remote_fingerprint,
451 reply,
452 } => {
453 let result = self
454 .do_pair_with_psk(psk, remote_fingerprint, incoming_rx, notification_tx)
455 .await;
456 let _ = reply.send(result);
457 }
458 RemoteClientCommand::LoadCachedConnection {
459 remote_fingerprint,
460 reply,
461 } => {
462 let result = self
463 .do_load_cached_connection(remote_fingerprint, notification_tx)
464 .await;
465 let _ = reply.send(result);
466 }
467 RemoteClientCommand::RequestCredential {
468 query,
469 credential_timeout,
470 reply,
471 } => {
472 let result = self
473 .do_request_credential(query, credential_timeout, incoming_rx, notification_tx)
474 .await;
475 let _ = reply.send(result);
476 }
477 RemoteClientCommand::ListConnections { reply } => {
478 let connections = self.connection_store.list().await;
479 let _ = reply.send(connections);
480 }
481 RemoteClientCommand::HasConnection { fingerprint, reply } => {
482 let has = self.connection_store.get(&fingerprint).await.is_some();
483 let _ = reply.send(has);
484 }
485 }
486 }
487
488 async fn do_pair_with_handshake(
491 &mut self,
492 rendezvous_code: String,
493 verify_fingerprint: bool,
494 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
495 notification_tx: &mpsc::Sender<RemoteClientNotification>,
496 request_tx: &mpsc::Sender<RemoteClientRequest>,
497 ) -> Result<IdentityFingerprint, ClientError> {
498 notify!(
500 notification_tx,
501 RemoteClientNotification::RendezvousResolving {
502 code: rendezvous_code.clone(),
503 }
504 );
505
506 let remote_fingerprint =
507 Self::resolve_rendezvous(self.proxy_client.as_ref(), incoming_rx, &rendezvous_code)
508 .await?;
509
510 notify!(
511 notification_tx,
512 RemoteClientNotification::RendezvousResolved {
513 fingerprint: remote_fingerprint,
514 }
515 );
516
517 notify!(notification_tx, RemoteClientNotification::HandshakeStart);
519
520 let (transport, fingerprint_str) = Self::perform_handshake(
521 self.proxy_client.as_ref(),
522 incoming_rx,
523 remote_fingerprint,
524 None,
525 )
526 .await?;
527
528 notify!(notification_tx, RemoteClientNotification::HandshakeComplete);
529
530 notify!(
532 notification_tx,
533 RemoteClientNotification::HandshakeFingerprint {
534 fingerprint: fingerprint_str.clone(),
535 }
536 );
537
538 if verify_fingerprint {
539 let (fp_tx, fp_rx) = oneshot::channel();
541 if request_tx.capacity() == 0 {
542 warn!("Request channel full, waiting for consumer to drain");
543 }
544 request_tx
545 .send(RemoteClientRequest::VerifyFingerprint {
546 fingerprint: fingerprint_str,
547 reply: fp_tx,
548 })
549 .await
550 .map_err(|_| ClientError::ChannelClosed)?;
551
552 match timeout(Duration::from_secs(60), fp_rx).await {
554 Ok(Ok(RemoteClientFingerprintReply { approved: true })) => {
555 notify!(
556 notification_tx,
557 RemoteClientNotification::FingerprintVerified
558 );
559 }
560 Ok(Ok(RemoteClientFingerprintReply { approved: false })) => {
561 self.proxy_client.disconnect().await.ok();
562 notify!(
563 notification_tx,
564 RemoteClientNotification::FingerprintRejected {
565 reason: "User rejected fingerprint verification".to_string(),
566 }
567 );
568 return Err(ClientError::FingerprintRejected);
569 }
570 Ok(Err(_)) => {
571 return Err(ClientError::ChannelClosed);
572 }
573 Err(_) => {
574 self.proxy_client.disconnect().await.ok();
575 return Err(ClientError::Timeout(
576 "Fingerprint verification timeout".to_string(),
577 ));
578 }
579 }
580 }
581
582 self.finalize_pairing(transport, remote_fingerprint, notification_tx)
584 .await?;
585
586 Ok(remote_fingerprint)
587 }
588
589 async fn do_pair_with_psk(
592 &mut self,
593 psk: Psk,
594 remote_fingerprint: IdentityFingerprint,
595 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
596 notification_tx: &mpsc::Sender<RemoteClientNotification>,
597 ) -> Result<(), ClientError> {
598 notify!(
599 notification_tx,
600 RemoteClientNotification::PskMode {
601 fingerprint: remote_fingerprint,
602 }
603 );
604
605 notify!(notification_tx, RemoteClientNotification::HandshakeStart);
607
608 let (transport, _fingerprint_str) = Self::perform_handshake(
609 self.proxy_client.as_ref(),
610 incoming_rx,
611 remote_fingerprint,
612 Some(psk),
613 )
614 .await?;
615
616 notify!(notification_tx, RemoteClientNotification::HandshakeComplete);
617
618 notify!(
620 notification_tx,
621 RemoteClientNotification::FingerprintVerified
622 );
623
624 self.finalize_pairing(transport, remote_fingerprint, notification_tx)
626 .await?;
627
628 Ok(())
629 }
630
631 async fn do_load_cached_connection(
634 &mut self,
635 remote_fingerprint: IdentityFingerprint,
636 notification_tx: &mpsc::Sender<RemoteClientNotification>,
637 ) -> Result<(), ClientError> {
638 let connection = self
639 .connection_store
640 .get(&remote_fingerprint)
641 .await
642 .ok_or(ClientError::ConnectionNotFound)?;
643
644 let transport = connection
645 .transport_state
646 .ok_or(ClientError::ConnectionNotFound)?;
647
648 notify!(
649 notification_tx,
650 RemoteClientNotification::ReconnectingToSession {
651 fingerprint: remote_fingerprint,
652 }
653 );
654
655 notify!(notification_tx, RemoteClientNotification::HandshakeComplete);
656
657 notify!(
659 notification_tx,
660 RemoteClientNotification::FingerprintVerified
661 );
662
663 self.connection_store
665 .update(ConnectionUpdate {
666 fingerprint: remote_fingerprint,
667 last_connected_at: crate::compat::now_seconds(),
668 })
669 .await?;
670
671 self.transport = Some(transport);
672 self.remote_fingerprint = Some(remote_fingerprint);
673
674 notify!(
675 notification_tx,
676 RemoteClientNotification::Ready {
677 can_request_credentials: true,
678 }
679 );
680
681 debug!("Reconnected to cached connection");
682 Ok(())
683 }
684
685 async fn finalize_pairing(
688 &mut self,
689 transport: MultiDeviceTransport,
690 remote_fingerprint: IdentityFingerprint,
691 notification_tx: &mpsc::Sender<RemoteClientNotification>,
692 ) -> Result<(), ClientError> {
693 let now = crate::compat::now_seconds();
694 self.connection_store
695 .save(ConnectionInfo {
696 fingerprint: remote_fingerprint,
697 name: None,
698 cached_at: now,
699 last_connected_at: now,
700 transport_state: Some(transport.clone()),
701 })
702 .await?;
703
704 self.transport = Some(transport);
706 self.remote_fingerprint = Some(remote_fingerprint);
707
708 notify!(
710 notification_tx,
711 RemoteClientNotification::Ready {
712 can_request_credentials: true,
713 }
714 );
715
716 debug!("Connection established successfully");
717 Ok(())
718 }
719
720 async fn do_request_credential(
723 &mut self,
724 query: CredentialQuery,
725 credential_timeout: Option<Duration>,
726 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
727 notification_tx: &mpsc::Sender<RemoteClientNotification>,
728 ) -> Result<CredentialData, ClientError> {
729 let remote_fingerprint = self.remote_fingerprint.ok_or(ClientError::NotInitialized)?;
730
731 #[allow(clippy::string_slice)]
733 let request_id = format!("req-{}-{}", now_millis(), &uuid_v4()[..8]);
734
735 debug!("Requesting credential for query: {:?}", query);
736
737 let request = CredentialRequestPayload {
739 request_type: "credential_request".to_string(),
740 query: query.clone(),
741 timestamp: now_millis(),
742 request_id: request_id.clone(),
743 };
744
745 let request_json = serde_json::to_string(&request)?;
746
747 let encrypted_data = {
748 let transport = self
749 .transport
750 .as_mut()
751 .ok_or(ClientError::SecureChannelNotEstablished)?;
752 let encrypted_packet = transport
753 .encrypt(request_json.as_bytes())
754 .map_err(|e| ClientError::NoiseProtocol(e.to_string()))?;
755 STANDARD.encode(encrypted_packet.encode())
756 };
757
758 let msg = ProtocolMessage::CredentialRequest {
759 encrypted: encrypted_data,
760 };
761
762 let msg_json = serde_json::to_string(&msg)?;
764 self.proxy_client
765 .send_to(remote_fingerprint, msg_json.into_bytes())
766 .await?;
767
768 notify!(
770 notification_tx,
771 RemoteClientNotification::CredentialRequestSent {
772 query: query.clone(),
773 }
774 );
775
776 let effective_timeout = credential_timeout.unwrap_or(DEFAULT_REQUEST_TIMEOUT);
778 match timeout(
779 effective_timeout,
780 self.receive_credential_response(&request_id, incoming_rx, notification_tx),
781 )
782 .await
783 {
784 Ok(result) => result,
785 Err(_) => Err(ClientError::Timeout(format!(
786 "Timeout waiting for credential response for query: {query:?}"
787 ))),
788 }
789 }
790
791 async fn receive_credential_response(
796 &mut self,
797 request_id: &str,
798 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
799 notification_tx: &mpsc::Sender<RemoteClientNotification>,
800 ) -> Result<CredentialData, ClientError> {
801 loop {
802 match incoming_rx.recv().await {
803 Some(IncomingMessage::Send { payload, .. }) => {
804 if let Ok(text) = String::from_utf8(payload)
805 && let Ok(ProtocolMessage::CredentialResponse { encrypted }) =
806 serde_json::from_str::<ProtocolMessage>(&text)
807 {
808 match self
809 .decrypt_credential_response(&encrypted, request_id, notification_tx)
810 .await
811 {
812 Ok(credential) => return Ok(credential),
813 Err(ClientError::CredentialRequestFailed(ref msg))
814 if msg.contains("request_id mismatch") =>
815 {
816 debug!("Skipping stale credential response: {msg}");
818 continue;
819 }
820 Err(e) => return Err(e),
821 }
822 }
823 }
824 Some(_) => {
825 }
827 None => {
828 return Err(ClientError::ChannelClosed);
829 }
830 }
831 }
832 }
833
834 async fn decrypt_credential_response(
836 &mut self,
837 encrypted: &str,
838 request_id: &str,
839 notification_tx: &mpsc::Sender<RemoteClientNotification>,
840 ) -> Result<CredentialData, ClientError> {
841 let encrypted_bytes = STANDARD
842 .decode(encrypted)
843 .map_err(|e| ClientError::Serialization(format!("Invalid base64: {e}")))?;
844
845 let packet = ap_noise::TransportPacket::decode(&encrypted_bytes)
846 .map_err(|e| ClientError::NoiseProtocol(format!("Invalid packet: {e}")))?;
847
848 let transport = self
849 .transport
850 .as_mut()
851 .ok_or(ClientError::SecureChannelNotEstablished)?;
852
853 let decrypted = transport
854 .decrypt(&packet)
855 .map_err(|e| ClientError::NoiseProtocol(e.to_string()))?;
856
857 let response: CredentialResponsePayload = serde_json::from_slice(&decrypted)?;
858
859 if response.request_id.as_deref() != Some(request_id) {
861 warn!(
862 "Ignoring response with mismatched request_id: {:?}",
863 response.request_id
864 );
865 return Err(ClientError::CredentialRequestFailed(
866 "Response request_id mismatch".to_string(),
867 ));
868 }
869
870 if let Some(error) = response.error {
871 return Err(ClientError::CredentialRequestFailed(error));
872 }
873
874 if let Some(credential) = response.credential {
875 notify!(
876 notification_tx,
877 RemoteClientNotification::CredentialReceived {
878 credential: credential.clone(),
879 }
880 );
881 Ok(credential)
882 } else {
883 Err(ClientError::CredentialRequestFailed(
884 "Response contains neither credential nor error".to_string(),
885 ))
886 }
887 }
888
889 async fn resolve_rendezvous(
893 proxy_client: &dyn ProxyClient,
894 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
895 rendezvous_code: &str,
896 ) -> Result<IdentityFingerprint, ClientError> {
897 proxy_client
899 .request_identity(RendezvousCode::from_string(rendezvous_code.to_string()))
900 .await
901 .map_err(|e| ClientError::RendezvousResolutionFailed(e.to_string()))?;
902
903 let timeout_duration = Duration::from_secs(10);
905 match timeout(timeout_duration, async {
906 while let Some(msg) = incoming_rx.recv().await {
907 if let IncomingMessage::IdentityInfo { fingerprint, .. } = msg {
908 return Some(fingerprint);
909 }
910 }
911 None
912 })
913 .await
914 {
915 Ok(Some(fingerprint)) => Ok(fingerprint),
916 Ok(None) => Err(ClientError::RendezvousResolutionFailed(
917 "Connection closed while waiting for identity response".to_string(),
918 )),
919 Err(_) => Err(ClientError::RendezvousResolutionFailed(
920 "Timeout waiting for identity response. The rendezvous code may be invalid, expired, or the target client may be disconnected.".to_string(),
921 )),
922 }
923 }
924
925 async fn perform_handshake(
927 proxy_client: &dyn ProxyClient,
928 incoming_rx: &mut mpsc::UnboundedReceiver<IncomingMessage>,
929 remote_fingerprint: IdentityFingerprint,
930 psk: Option<Psk>,
931 ) -> Result<(MultiDeviceTransport, String), ClientError> {
932 let psk_id = psk.as_ref().map(|p| p.id());
934
935 let mut handshake = if let Some(psk) = psk {
937 InitiatorHandshake::with_psk(psk)
938 } else {
939 InitiatorHandshake::new()
940 };
941
942 let init_packet = handshake.send_start()?;
944
945 let msg = ProtocolMessage::HandshakeInit {
947 data: STANDARD.encode(init_packet.encode()?),
948 ciphersuite: format!("{:?}", handshake.ciphersuite()),
949 psk_id,
950 };
951
952 let msg_json = serde_json::to_string(&msg)?;
953 proxy_client
954 .send_to(remote_fingerprint, msg_json.into_bytes())
955 .await?;
956
957 debug!("Sent handshake init");
958
959 let response_timeout = Duration::from_secs(10);
961 let response: String = timeout(response_timeout, async {
962 loop {
963 if let Some(incoming) = incoming_rx.recv().await {
964 match incoming {
965 IncomingMessage::Send { payload, .. } => {
966 if let Ok(text) = String::from_utf8(payload)
968 && let Ok(ProtocolMessage::HandshakeResponse { data, .. }) =
969 serde_json::from_str::<ProtocolMessage>(&text)
970 {
971 return Ok::<String, ClientError>(data);
972 }
973 }
974 _ => continue,
975 }
976 }
977 }
978 })
979 .await
980 .map_err(|_| ClientError::Timeout("Waiting for handshake response".to_string()))??;
981
982 let response_bytes = STANDARD
984 .decode(&response)
985 .map_err(|e| ClientError::Serialization(format!("Invalid base64: {e}")))?;
986
987 let response_packet = ap_noise::HandshakePacket::decode(&response_bytes)
988 .map_err(|e| ClientError::NoiseProtocol(format!("Invalid packet: {e}")))?;
989
990 handshake.receive_finish(&response_packet)?;
992 let (transport, fingerprint) = handshake.finalize()?;
993
994 debug!("Handshake complete");
995 Ok((transport, fingerprint.to_string()))
996 }
997}
998
999fn uuid_v4() -> String {
1000 let mut bytes = [0u8; 16];
1002 let mut rng = rand::thread_rng();
1003 rng.fill_bytes(&mut bytes);
1004
1005 bytes[6] = (bytes[6] & 0x0f) | 0x40;
1007 bytes[8] = (bytes[8] & 0x3f) | 0x80;
1008
1009 format!(
1010 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
1011 bytes[0],
1012 bytes[1],
1013 bytes[2],
1014 bytes[3],
1015 bytes[4],
1016 bytes[5],
1017 bytes[6],
1018 bytes[7],
1019 bytes[8],
1020 bytes[9],
1021 bytes[10],
1022 bytes[11],
1023 bytes[12],
1024 bytes[13],
1025 bytes[14],
1026 bytes[15]
1027 )
1028}