whatsapp_rust/
pair.rs

1use crate::client::Client;
2use crate::types::events::{Event, PairError, PairSuccess};
3use log::{error, info, warn};
4use prost::Message;
5use rand::TryRngCore;
6use rand_core::OsRng;
7use std::sync::Arc;
8use std::sync::atomic::Ordering;
9use wacore::libsignal::protocol::KeyPair;
10use wacore_binary::jid::Jid;
11use wacore_binary::node::{Node, NodeContent};
12use waproto::whatsapp as wa;
13
14pub use wacore::pair::{DeviceState, PairCryptoError, PairUtils};
15
16pub fn make_qr_data(store: &crate::store::Device, ref_str: String) -> String {
17    let device_state = DeviceState {
18        identity_key: store.identity_key,
19        noise_key: store.noise_key,
20        adv_secret_key: store.adv_secret_key,
21    };
22    PairUtils::make_qr_data(&device_state, ref_str)
23}
24
25pub async fn handle_iq(client: &Arc<Client>, node: &Node) -> bool {
26    if node.attrs.get("from").cloned().unwrap_or_default() != "s.whatsapp.net" {
27        return false;
28    }
29
30    if let Some(children) = node.children() {
31        for child in children {
32            let handled = match child.tag.as_str() {
33                "pair-device" => {
34                    if let Some(ack_node) = PairUtils::build_ack_node(node)
35                        && let Err(e) = client.send_node(ack_node).await
36                    {
37                        warn!("Failed to send acknowledgement: {e:?}");
38                    }
39
40                    let mut codes = Vec::new();
41
42                    let device_snapshot = client.persistence_manager.get_device_snapshot().await;
43                    let device_state = DeviceState {
44                        identity_key: device_snapshot.identity_key,
45                        noise_key: device_snapshot.noise_key,
46                        adv_secret_key: device_snapshot.adv_secret_key,
47                    };
48
49                    for grandchild in child.get_children_by_tag("ref") {
50                        if let Some(NodeContent::Bytes(bytes)) = &grandchild.content
51                            && let Ok(r) = String::from_utf8(bytes.clone())
52                        {
53                            codes.push(PairUtils::make_qr_data(&device_state, r));
54                        }
55                    }
56
57                    let (stop_tx, stop_rx) = tokio::sync::watch::channel(());
58                    let codes_clone = codes.clone();
59                    let client_clone = client.clone();
60
61                    tokio::spawn(async move {
62                        // The rotation logic is now inside the library
63                        let mut is_first = true;
64                        let mut stop_rx_clone = stop_rx.clone();
65
66                        for code in codes_clone {
67                            let timeout = if is_first {
68                                is_first = false;
69                                std::time::Duration::from_secs(60)
70                            } else {
71                                std::time::Duration::from_secs(20)
72                            };
73
74                            // Dispatch the new, simple event for each code
75                            client_clone
76                                .core
77                                .event_bus
78                                .dispatch(&Event::PairingQrCode { code, timeout });
79
80                            // Wait for the timeout OR a stop signal
81                            tokio::select! {
82                                _ = tokio::time::sleep(timeout) => {}
83                                _ = stop_rx_clone.changed() => {
84                                    info!("Pairing complete. Stopping QR code rotation.");
85                                    return;
86                                }
87                            }
88                        }
89                        info!("All QR codes for this session have expired.");
90                        client_clone.disconnect().await;
91                    });
92
93                    *client.pairing_cancellation_tx.lock().await = Some(stop_tx);
94
95                    // We no longer dispatch the raw Event::Qr
96                    // client.core.event_bus.dispatch(&Event::Qr(Qr { codes }));
97                    true
98                }
99                "pair-success" => {
100                    handle_pair_success(client, node, child).await;
101                    true
102                }
103                _ => false,
104            };
105            if handled {
106                return true;
107            }
108        }
109    }
110
111    false
112}
113
114async fn handle_pair_success(client: &Arc<Client>, request_node: &Node, success_node: &Node) {
115    if let Some(tx) = client.pairing_cancellation_tx.lock().await.take() {
116        let _ = tx.send(());
117    }
118
119    let req_id = match request_node.attrs.get("id") {
120        Some(id) => id.to_string(),
121        None => {
122            error!("Received pair-success without request ID");
123            return;
124        }
125    };
126
127    let device_identity_bytes = match success_node
128        .get_optional_child_by_tag(&["device-identity"])
129        .and_then(|n| n.content.as_ref())
130    {
131        Some(NodeContent::Bytes(b)) => b.clone(),
132        _ => {
133            let error_node = PairUtils::build_pair_error_node(&req_id, 500, "internal-error");
134            if let Err(e) = client.send_node(error_node).await {
135                error!("Failed to send pair error node: {e}");
136            }
137            error!("pair-success is missing device-identity");
138            return;
139        }
140    };
141
142    let business_name = success_node
143        .get_optional_child_by_tag(&["biz"])
144        .map(|n| n.attrs().optional_string("name").unwrap_or("").to_string())
145        .unwrap_or_default();
146
147    let platform = success_node
148        .get_optional_child_by_tag(&["platform"])
149        .map(|n| n.attrs().optional_string("name").unwrap_or("").to_string())
150        .unwrap_or_default();
151
152    // For jid and lid, parse them together to handle errors correctly
153    let (jid, lid) = if let Some(device_node) = success_node.get_optional_child_by_tag(&["device"])
154    {
155        let mut parser = device_node.attrs();
156        let parsed_jid = parser.optional_jid("jid").unwrap_or_default();
157        let parsed_lid = parser.optional_jid("lid").unwrap_or_default();
158
159        if let Err(e) = parser.finish() {
160            warn!(target: "Client/Pair", "Error parsing device node attributes: {e:?}");
161            (Jid::default(), Jid::default())
162        } else {
163            (parsed_jid, parsed_lid)
164        }
165    } else {
166        (Jid::default(), Jid::default())
167    };
168
169    let device_snapshot = client.persistence_manager.get_device_snapshot().await;
170    let device_state = DeviceState {
171        identity_key: device_snapshot.identity_key,
172        noise_key: device_snapshot.noise_key,
173        adv_secret_key: device_snapshot.adv_secret_key,
174    };
175
176    let result = PairUtils::do_pair_crypto(&device_state, &device_identity_bytes);
177
178    match result {
179        Ok((self_signed_identity_bytes, key_index)) => {
180            let signed_identity_for_event = match wa::AdvSignedDeviceIdentity::decode(
181                self_signed_identity_bytes.as_slice(),
182            ) {
183                Ok(identity) => identity,
184                Err(e) => {
185                    error!(
186                        "FATAL: Failed to re-decode self-signed identity for event, pairing cannot complete: {e}"
187                    );
188                    client.core.event_bus.dispatch(&Event::PairError(PairError {
189                        id: jid.clone(),
190                        lid: lid.clone(),
191                        business_name: business_name.clone(),
192                        platform: platform.clone(),
193                        error: format!("internal error: failed to decode identity for event: {e}"),
194                    }));
195                    return;
196                }
197            };
198
199            client
200                .persistence_manager
201                .process_command(crate::store::commands::DeviceCommand::SetId(Some(
202                    jid.clone(),
203                )))
204                .await;
205            client
206                .persistence_manager
207                .process_command(crate::store::commands::DeviceCommand::SetAccount(Some(
208                    signed_identity_for_event.clone(),
209                )))
210                .await;
211            client
212                .persistence_manager
213                .process_command(crate::store::commands::DeviceCommand::SetLid(Some(
214                    lid.clone(),
215                )))
216                .await;
217
218            if !business_name.is_empty() {
219                info!("✅ Setting push_name during pairing: '{}'", &business_name);
220                client
221                    .persistence_manager
222                    .process_command(crate::store::commands::DeviceCommand::SetPushName(
223                        business_name.clone(),
224                    ))
225                    .await;
226            } else {
227                info!(
228                    "⚠️ business_name not found in pair-success, push_name remains unset for now."
229                );
230            }
231
232            let response_node = PairUtils::build_pair_success_response(
233                &req_id,
234                self_signed_identity_bytes,
235                key_index,
236            );
237
238            if let Err(e) = client.send_node(response_node).await {
239                error!("Failed to send pair-device-sign: {e}");
240                return;
241            }
242
243            // --- START: FIX ---
244            // Set the flag to trigger a full sync on the next successful connection.
245            client
246                .needs_initial_full_sync
247                .store(true, Ordering::Relaxed);
248            // --- END: FIX ---
249
250            client.expected_disconnect.store(true, Ordering::Relaxed);
251
252            info!("Successfully paired {jid}");
253
254            let success_event = PairSuccess {
255                id: jid,
256                lid,
257                business_name,
258                platform,
259            };
260            client
261                .core
262                .event_bus
263                .dispatch(&Event::PairSuccess(success_event));
264        }
265        Err(e) => {
266            error!("Pairing crypto failed: {e}");
267            let error_node = PairUtils::build_pair_error_node(&req_id, e.code, e.text);
268            if let Err(send_err) = client.send_node(error_node).await {
269                error!("Failed to send pair error node: {send_err}");
270            }
271
272            let pair_error_event = crate::types::events::PairError {
273                id: jid,
274                lid,
275                business_name,
276                platform,
277                error: e.to_string(),
278            };
279            client
280                .core
281                .event_bus
282                .dispatch(&Event::PairError(pair_error_event));
283        }
284    }
285}
286
287pub async fn pair_with_qr_code(client: &Arc<Client>, qr_code: &str) -> Result<(), anyhow::Error> {
288    info!(target: "Client/PairTest", "Master client attempting to pair with QR code.");
289
290    let (pairing_ref, dut_noise_pub, dut_identity_pub) = PairUtils::parse_qr_code(qr_code)?;
291
292    let master_ephemeral = KeyPair::generate(&mut OsRng::unwrap_err(OsRng));
293
294    let device_snapshot = client.persistence_manager.get_device_snapshot().await;
295    let device_state = DeviceState {
296        identity_key: device_snapshot.identity_key,
297        noise_key: device_snapshot.noise_key,
298        adv_secret_key: device_snapshot.adv_secret_key,
299    };
300
301    let encrypted = PairUtils::prepare_master_pairing_message(
302        &device_state,
303        &pairing_ref,
304        &dut_noise_pub,
305        &dut_identity_pub,
306        master_ephemeral,
307    )?;
308
309    let master_jid = device_snapshot.pn.clone().unwrap();
310    let req_id = client.generate_request_id();
311
312    let iq = PairUtils::build_master_pair_iq(&master_jid, encrypted, req_id);
313
314    client.send_node(iq).await?;
315
316    info!(target: "Client/PairTest", "Master client sent pairing confirmation.");
317    Ok(())
318}