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