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> {
49 let response = self
50 .http_request(Method::GET, "/v2/directory/auth")?
51 .send()
52 .await?
53 .service_error_for_status()
54 .await?;
55
56 response.json().await
57 }
58
59 pub async fn discover_contacts(
71 &mut self,
72 lookup_request: LookupRequest,
73 ) -> Result<Vec<(libsignal_core::E164, Option<ServiceId>)>, ServiceError>
74 {
75 let env: libsignal_net::env::Env<'_> = self.servers().into();
76
77 let cdsi_auth_response = self.get_cdsi_auth().await?;
79
80 let auth = Auth {
81 username: cdsi_auth_response.username,
82 password: cdsi_auth_response.password,
83 };
84
85 let connect_state = ConnectState::new(SUGGESTED_CONNECT_CONFIG);
87 let network_change_event = no_network_change_events();
88 let static_map = std::collections::HashMap::from([env
89 .cdsi
90 .domain_config
91 .static_fallback(libsignal_net::env::StaticIpOrder::HARDCODED)]);
92 let dns_resolver = DnsResolver::new_with_static_fallback(
93 static_map,
94 &network_change_event,
95 );
96
97 let connection_resources = ConnectionResources {
98 connect_state: &connect_state,
99 dns_resolver: &dns_resolver,
100 network_change_event: &network_change_event,
101 confirmation_header_name: None,
102 };
103
104 let cdsi_endpoint = &env.cdsi;
106 let cdsi_connection = CdsiConnection::connect_with(
107 connection_resources,
108 env.cdsi.domain_config.connect.service,
109 libsignal_net_infra::route::DirectOrProxyProvider::direct(
110 cdsi_endpoint.enclave_websocket_provider(
111 libsignal_net_infra::EnableDomainFronting::No,
112 ),
113 ),
114 cdsi_endpoint.ws_config,
115 &cdsi_endpoint.params,
116 &auth,
117 )
118 .await?;
119
120 let (_token, collector) =
121 cdsi_connection.send_request(lookup_request).await?;
122 let response = collector.collect().await?;
123
124 Ok(response.records.into_iter().map(|LookupResponseEntry { e164, aci, pni }| match (pni, aci) {
125 (None, None) => (e164, None),
126 (None, Some(aci)) => (e164, Some(aci.into())),
127 (Some(pni), None) => (e164, Some(pni.into())),
128 (Some(_), Some(aci)) => {
129 warn!("got both ACI and PNI for a phone number, this is unexpected, using ACI!");
130 (e164, Some(aci.into()))
131 },
132 }).collect())
133 }
134
135 pub async fn discover_contact_by_phone_number(
146 &mut self,
147 phone_number: impl TryIntoE164,
148 ) -> Result<Option<ServiceId>, ServiceError> {
149 let lookup_request = LookupRequest {
150 new_e164s: vec![phone_number
151 .try_into_e164()
152 .map_err(|_| ServiceError::InvalidPhoneNumber)?],
153 ..Default::default()
154 };
155
156 let results = self.discover_contacts(lookup_request).await?;
157 Ok(results
158 .into_iter()
159 .next()
160 .and_then(|(_, service_id)| service_id))
161 }
162}