libsignal_service/websocket/
directory.rs1use libsignal_core::ServiceId;
6use libsignal_net::auth::Auth;
7use libsignal_net::cdsi::{CdsiConnection, LookupResponseEntry};
8use libsignal_net::connect_state::{
9 ConnectState, ConnectionResources, SUGGESTED_CONNECT_CONFIG,
10};
11use libsignal_net_infra::dns::DnsResolver;
12use libsignal_net_infra::utils::no_network_change_events;
13use reqwest::Method;
14use serde::Deserialize;
15use tracing::warn;
16
17use crate::content::ServiceError;
18use crate::push_service::response::SignalServiceResponse;
19use crate::utils::TryIntoE164;
20use crate::websocket::{Identified, SignalWebSocket};
21
22pub use libsignal_net::cdsi::LookupRequest;
23
24#[derive(Debug, Deserialize)]
26struct CdsiAuth {
27 pub username: String,
28 pub password: String,
29}
30
31impl SignalWebSocket<Identified> {
32 async fn get_cdsi_auth(&mut self) -> Result<CdsiAuth, ServiceError> {
41 let response = self
42 .http_request(Method::GET, "/v2/directory/auth")?
43 .send()
44 .await?
45 .service_error_for_status()
46 .await?;
47
48 response.json().await
49 }
50
51 pub async fn discover_contacts(
63 &mut self,
64 lookup_request: LookupRequest,
65 ) -> Result<Vec<(libsignal_core::E164, Option<ServiceId>)>, ServiceError>
66 {
67 let env: libsignal_net::env::Env<'_> = self.servers().into();
68
69 let cdsi_auth_response = self.get_cdsi_auth().await?;
71
72 let auth = Auth {
73 username: cdsi_auth_response.username,
74 password: cdsi_auth_response.password,
75 };
76
77 let connect_state = ConnectState::new(SUGGESTED_CONNECT_CONFIG);
79 let network_change_event = no_network_change_events();
80 let static_map = std::collections::HashMap::from([env
81 .cdsi
82 .domain_config
83 .static_fallback(libsignal_net::env::StaticIpOrder::HARDCODED)]);
84 let dns_resolver = DnsResolver::new_with_static_fallback(
85 static_map,
86 &network_change_event,
87 );
88
89 let connection_resources = ConnectionResources {
90 connect_state: &connect_state,
91 dns_resolver: &dns_resolver,
92 network_change_event: &network_change_event,
93 confirmation_header_name: None,
94 };
95
96 let cdsi_endpoint = &env.cdsi;
98 let cdsi_connection = CdsiConnection::connect_with(
99 connection_resources,
100 env.cdsi.domain_config.connect.service,
101 libsignal_net_infra::route::DirectOrProxyProvider::direct(
102 cdsi_endpoint.enclave_websocket_provider(
103 libsignal_net_infra::EnableDomainFronting::No,
104 ),
105 ),
106 cdsi_endpoint.ws_config,
107 &cdsi_endpoint.params,
108 &auth,
109 )
110 .await?;
111
112 let (_token, collector) =
113 cdsi_connection.send_request(lookup_request).await?;
114 let response = collector.collect().await?;
115
116 Ok(response.records.into_iter().map(|LookupResponseEntry { e164, aci, pni }| match (pni, aci) {
117 (None, None) => (e164, None),
118 (None, Some(aci)) => (e164, Some(aci.into())),
119 (Some(pni), None) => (e164, Some(pni.into())),
120 (Some(_), Some(aci)) => {
121 warn!("got both ACI and PNI for a phone number, this is unexpected, using ACI!");
122 (e164, Some(aci.into()))
123 },
124 }).collect())
125 }
126
127 pub async fn discover_contact_by_phone_number(
138 &mut self,
139 phone_number: impl TryIntoE164,
140 ) -> Result<Option<ServiceId>, ServiceError> {
141 let lookup_request = LookupRequest {
142 new_e164s: vec![phone_number
143 .try_into_e164()
144 .map_err(|_| ServiceError::InvalidPhoneNumber)?],
145 ..Default::default()
146 };
147
148 let results = self.discover_contacts(lookup_request).await?;
149 Ok(results
150 .into_iter()
151 .next()
152 .and_then(|(_, service_id)| service_id))
153 }
154}