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 libsignal_net_infra::route::DirectOrProxyProvider::direct(
109 cdsi_endpoint.enclave_websocket_provider(
110 libsignal_net_infra::EnableDomainFronting::No,
111 ),
112 ),
113 cdsi_endpoint.ws_config,
114 &cdsi_endpoint.params,
115 &auth,
116 )
117 .await?;
118
119 let (_token, collector) =
120 cdsi_connection.send_request(lookup_request).await?;
121 let response = collector.collect().await?;
122
123 Ok(response.records.into_iter().map(|LookupResponseEntry { e164, aci, pni }| match (pni, aci) {
124 (None, None) => (e164, None),
125 (None, Some(aci)) => (e164, Some(aci.into())),
126 (Some(pni), None) => (e164, Some(pni.into())),
127 (Some(_), Some(aci)) => {
128 warn!("got both ACI and PNI for a phone number, this is unexpected, using ACI!");
129 (e164, Some(aci.into()))
130 },
131 }).collect())
132 }
133
134 pub async fn discover_contact_by_phone_number(
145 &mut self,
146 phone_number: impl TryIntoE164,
147 ) -> Result<Option<ServiceId>, ServiceError> {
148 let lookup_request = LookupRequest {
149 new_e164s: vec![phone_number
150 .try_into_e164()
151 .map_err(|_| ServiceError::InvalidPhoneNumber)?],
152 ..Default::default()
153 };
154
155 let results = self.discover_contacts(lookup_request).await?;
156 Ok(results
157 .into_iter()
158 .next()
159 .and_then(|(_, service_id)| service_id))
160 }
161}