// client/src/handshake.rs #![forbid(unsafe_code)] use lesavka_common::lesavka::{self as pb, handshake_client::HandshakeClient}; use std::time::Duration; use tokio::time::timeout; use tonic::{Code, transport::Endpoint}; use tracing::{info, warn}; #[derive(Default, Clone, Copy, Debug)] pub struct PeerCaps { pub camera: bool, pub microphone: bool, } pub async fn negotiate(uri: &str) -> PeerCaps { info!(%uri, "🤝 dial handshake"); let ep = Endpoint::from_shared(uri.to_owned()) .expect("handshake endpoint") .tcp_nodelay(true) .http2_keep_alive_interval(Duration::from_secs(15)) .connect_timeout(Duration::from_secs(5)); let channel = timeout(Duration::from_secs(8), ep.connect()) .await .expect("handshake connect timeout") .expect("handshake connect failed"); info!("🤝 handshake channel connected"); let mut cli = HandshakeClient::new(channel); info!("🤝 fetching capabilities…"); match timeout(Duration::from_secs(5), cli.get_capabilities(pb::Empty {})).await { Ok(Ok(rsp)) => { let caps = PeerCaps { camera: rsp.get_ref().camera, microphone: rsp.get_ref().microphone, }; info!(?caps, "🤝 handshake ok"); caps } Ok(Err(e)) if e.code() == Code::Unimplemented => { warn!("🤝 handshake not implemented on server – assuming defaults"); PeerCaps::default() } Ok(Err(e)) => { warn!("🤝 handshake failed: {e} – assuming defaults"); PeerCaps::default() } Err(_) => { warn!("🤝 handshake timed out – assuming defaults"); PeerCaps::default() } } }