libsignal_service/push_service/
linking.rs

1use libsignal_core::DeviceId;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::{
7    configuration::Endpoint, utils::serde_device_id,
8    websocket::registration::DeviceActivationRequest,
9};
10
11use super::{
12    response::ReqwestExt, HttpAuth, HttpAuthOverride, PushService, ServiceError,
13};
14
15#[derive(Debug, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct LinkAccountAttributes {
18    pub fetches_messages: bool,
19    pub name: String,
20    pub registration_id: u32,
21    pub pni_registration_id: u32,
22    pub capabilities: LinkCapabilities,
23}
24
25#[derive(Debug, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct LinkCapabilities {
28    pub delete_sync: bool,
29    pub versioned_expiration_timer: bool,
30    /// It is currently unclear what this field is.
31    ///
32    /// Signal Server refers to the field as `STORAGE_SERVICE_RECORD_KEY_ROTATION` [here](https://github.com/signalapp/Signal-Server/blob/5cc76f48aa4028f5001a51409a3a0e4e6ce2d7f2/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java#L15).
33    /// Signal Android refers to the field as `storageServiceEncryptionV2` [here](https://github.com/signalapp/Signal-Android/blob/ec840726fcbb5440e1337274f791d17a6fe59598/libsignal-service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt#L60).
34    /// It is therefore possibly related to backup
35    pub ssre2: bool,
36    /// Sparse Post-Quantum Ratchet (`SPARSE_POST_QUANTUM_RATCHET` on Signal Server).
37    ///
38    /// Required for all devices; the server returns 409 if a linking device omits this capability
39    /// while the account already has it on any existing device.
40    pub spqr: bool,
41}
42
43// https://github.com/signalapp/Signal-Desktop/blob/1e57db6aa4786dcddc944349e4894333ac2ffc9e/ts/textsecure/WebAPI.ts#L1287
44impl Default for LinkCapabilities {
45    fn default() -> Self {
46        Self {
47            delete_sync: true,
48            versioned_expiration_timer: true,
49            ssre2: true,
50            spqr: true,
51        }
52    }
53}
54
55#[derive(Debug, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct LinkResponse {
58    #[serde(rename = "uuid")]
59    pub aci: Uuid,
60    pub pni: Uuid,
61    #[serde(with = "serde_device_id")]
62    pub device_id: DeviceId,
63}
64
65#[derive(Debug, Serialize)]
66#[serde(rename_all = "camelCase")]
67pub struct LinkRequest {
68    pub verification_code: String,
69    pub account_attributes: LinkAccountAttributes,
70    #[serde(flatten)]
71    pub device_activation_request: DeviceActivationRequest,
72}
73
74impl PushService {
75    pub async fn link_device(
76        &mut self,
77        link_request: &LinkRequest,
78        http_auth: HttpAuth,
79    ) -> Result<LinkResponse, ServiceError> {
80        self.request(
81            Method::PUT,
82            Endpoint::service("/v1/devices/link"),
83            HttpAuthOverride::Identified(http_auth),
84        )?
85        .json(&link_request)
86        .send()
87        .await?
88        .service_error_for_status()
89        .await?
90        .json()
91        .await
92        .map_err(Into::into)
93    }
94}