• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polyphony-chat / polyproto-rs / #95

28 Jun 2025 03:36PM UTC coverage: 64.009%. Remained the same
#95

push

bitfl0wer
chore: fix compiler warnings & stability bugs, bump ver

fix: wasm build

fix: move serial_number.rs to not be affected by types feature

asfdfasdasdfasdf

5 of 24 new or added lines in 2 files covered. (20.83%)

58 existing lines in 3 files now uncovered.

1421 of 2220 relevant lines covered (64.01%)

1.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.68
/src/api/core/migration.rs
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
use super::*;
5
mod registration_required {
6
    use http::StatusCode;
7
    use reqwest::multipart::Form;
8
    use serde::Serialize;
9
    use serde_json::json;
10

11
    use crate::api::{P2RequestBuilder, SendsRequest, matches_status_code};
12
    use crate::types::keytrial::KeyTrialResponse;
13
    #[allow(deprecated)]
14
    use crate::types::p2_export::P2Export;
15

16
    use super::*;
17

18
    impl<S: Signature, T: PrivateKey<S>> Session<S, T> {
19
        /// Import a `P2Export` file. `messages` in this file must have been re-signed to the current actor.
20
        /// Only messages classified as
21
        /// ["Information not tied to a specific context"](https://docs.polyphony.chat/Protocol%20Specifications/core/#:~:text=Example%3A%20Information%20not,without%0Aany%20issues.)
22
        /// can be imported.
23
        pub async fn import_data_to_server<M: Serialize>(
24
            &self,
25
            sensitive_solution: &str,
26
            data: P2Export<M>,
27
        ) -> HttpResult<()> {
28
            let endpoint = self.instance_url.join("/.p2/core/v1/migration/data/")?;
×
29
            let request = self
×
30
                .client
×
31
                .client
×
32
                .request(IMPORT_DATA.method, endpoint)
×
33
                .header("X-P2-Sensitive-Solution", sensitive_solution)
×
34
                .multipart(Form::try_from(data).map_err(|e| RequestError::Custom {
×
35
                    reason: e.to_string(),
×
36
                })?);
37

38
            let response = request.send().await?;
×
39
            matches_status_code(
40
                &[
×
41
                    StatusCode::ACCEPTED,
×
42
                    StatusCode::CREATED,
×
43
                    StatusCode::NO_CONTENT,
×
44
                ],
45
                response.status(),
×
46
            ) // TODO Review and test when P2Export is overhauled
47
        }
48

49
        /// This route is used by actors who would like to move their identity to another home server.
50
        /// This specific route is called by the "old" actor, notifying the server about their
51
        /// intent to move to another home server. To fulfill this action,
52
        /// a key trial must be passed for all keys with which the actor has sent messages with on this server.
53
        /// The "new" actor named in this request must confirm setting up this redirect.
54
        pub async fn set_up_redirect(
1✔
55
            &self,
56
            keytrials: &[KeyTrialResponse],
57
            fid: &FederationId,
58
        ) -> HttpResult<()> {
59
            let request = self
6✔
60
                .client
×
61
                .client
×
62
                .request(
63
                    SET_UP_REDIRECT.method,
1✔
64
                    self.instance_url.join(SET_UP_REDIRECT.path)?,
2✔
65
                )
66
                .bearer_auth(&self.token)
2✔
67
                .header(
68
                    "X-P2-core-keytrial",
69
                    urlencoding::encode(&json!(keytrials).to_string()).into_owned(),
4✔
70
                )
71
                .body(fid.to_string());
2✔
72

73
            let response = request.send().await?;
2✔
74
            matches_status_code(&[StatusCode::NO_CONTENT, StatusCode::OK], response.status())?;
2✔
75

76
            Ok(())
1✔
77
        }
78

79
        /// Stop an in-progress or existing redirection process from/to actor `fid`.
80
        pub async fn remove_redirect(&self, fid: &FederationId) -> HttpResult<()> {
4✔
81
            let request = P2RequestBuilder::new(&self)
8✔
82
                .homeserver(self.instance_url.clone())
2✔
83
                .endpoint(REMOVE_REDIRECT)
1✔
84
                .query("removeActorFid", &fid.to_string())
2✔
85
                .auth_token(self.token.clone())
2✔
86
                .build()
87
                .map_err(|e| RequestError::Custom {
×
88
                    reason: e.to_string(),
×
89
                })?;
90
            let response = self.send_request(request).await?;
2✔
91
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())
2✔
92
        }
93
    }
94
}
95

96
mod registration_not_required {
97
    use http::{HeaderValue, StatusCode};
98

99
    use serde::Serialize;
100
    use serde::de::DeserializeOwned;
101
    use serde_json::{from_str, json};
102

103
    use crate::api::{P2RequestBuilder, SendsRequest, matches_status_code};
104
    use crate::types::keytrial::KeyTrialResponse;
105

106
    use super::*;
107

108
    impl<S: Signature, T: PrivateKey<S>> Session<S, T> {
109
        /// Export all of your data for safekeeping or for importing it to another server.
110
        /// Only exports data for which a key trial has been passed.
111
        ///
112
        /// ## Returns
113
        ///
114
        /// ### `Ok()`
115
        ///
116
        /// - `None`, if the server has responded with `204`, indicating the server needs time to gather the
117
        ///   data. A Retry-After header is included in the response, indicating to the actor the point in
118
        ///   time at which they should query this endpoint again. If this point in time is after the expiry
119
        ///   timestamp of the completed key trial, another key trial needs to be performed to access the data.
120
        /// - `Some(Vec<u8>)` and the requested data, if the server could gather it in time.
121
        ///
122
        /// ### `Err()`
123
        ///
124
        /// Errors, if there is some error during building or sending the request, or with parsing the
125
        /// response.
126
        pub async fn export_all_data(
127
            &self,
128
            key_trials: &[KeyTrialResponse],
129
        ) -> HttpResult<Option<Vec<u8>>> {
130
            let request = P2RequestBuilder::new(&self)
×
131
                .auth_token(self.token.clone())
×
132
                .homeserver(self.instance_url.clone())
×
133
                .endpoint(EXPORT_DATA)
×
134
                .key_trials(key_trials.to_vec())
×
135
                .build()
136
                .map_err(RequestError::from)?;
×
137
            let response = self.send_request(request).await?;
×
138
            if response.status() == StatusCode::ACCEPTED {
×
139
                Ok(None)
×
140
            } else {
141
                let response_bytes = response.bytes().await?;
×
142
                Ok(Some(response_bytes.to_vec()))
×
143
            }
144
        }
145

146
        /// Delete all data associated with you from the server. Only deletes data associated with the keys for which the `KeyTrial` has been passed.
147
        ///
148
        /// ## Parameters
149
        ///
150
        /// - `break_redirect`: If a redirect has been set up previously: Whether to break that redirect with this action.
151
        pub async fn delete_data_from_server(
152
            &self,
153
            break_redirect: bool,
154
            keytrials: &[KeyTrialResponse],
155
        ) -> HttpResult<()> {
156
            let request = P2RequestBuilder::new(&self)
×
157
                .homeserver(self.instance_url.clone())
×
158
                .endpoint(DELETE_DATA)
×
159
                .auth_token(self.token.clone())
×
160
                .query("breakRedirect", &json!(break_redirect).to_string())
×
161
                .key_trials(keytrials.to_vec())
×
162
                .build()?;
163
            let response = self.send_request(request).await?;
×
164
            matches_status_code(
165
                &[StatusCode::OK, StatusCode::ACCEPTED, StatusCode::NO_CONTENT],
×
166
                response.status(),
×
167
            )
168
        }
169

170
        /// Fetch key trials and their responses from other actors. This route exists for
171
        /// transparency reasons, and allows actors in contact with the actor mentioned in `fid` to
172
        /// verify, that it was the actor who initiated setting up a redirect or the re-signing
173
        /// of messages—not a malicious home server.
174
        pub async fn get_actor_key_trial_responses(
175
            &self,
176
            fid: &FederationId,
177
            limit: u16,
178
            key_trial_id: Option<&str>,
179
            not_before: Option<u64>,
180
            not_after: Option<u64>,
181
        ) -> HttpResult<Vec<KeyTrialResponse>> {
182
            let mut request = P2RequestBuilder::new(&self)
×
183
                .auth_token(self.token.clone())
×
184
                .endpoint(GET_COMPLETED_KEYTRIALS_AND_RESPONSES)
×
185
                .replace_endpoint_substr(r#"{fid}"#, &fid.to_string())
×
186
                .query("limit", &limit.to_string());
×
187
            if let Some(id) = key_trial_id {
×
188
                request = request.query("id", id);
×
189
            }
190
            if let Some(nbf) = not_before {
×
191
                request = request.query("notBefore", &nbf.to_string());
×
192
            }
193
            if let Some(na) = not_after {
×
194
                request = request.query("notAfter", &na.to_string());
×
195
            }
196
            let request = request.build()?;
×
197

198
            let response = self.send_request(request).await?;
×
199
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())?;
×
200
            if response.status() == StatusCode::NO_CONTENT {
×
201
                return Ok(Vec::new());
×
202
            }
203
            match response.text().await {
×
204
                Ok(text) => from_str::<Vec<KeyTrialResponse>>(&text)
×
205
                    .map_err(RequestError::DeserializationError),
×
206
                Err(e) => Err(RequestError::Custom {
×
207
                    reason: format!("Could not get the full response text: {e}"),
×
208
                }),
209
            }
210
        }
211

212
        /// Fetch messages to be re-signed. Only returns messages where the signatures correlate
213
        /// to ID-Certs for which a key trial has been passed. If this endpoint is queried again
214
        /// while the previous batch of messages has not yet been re-signed (or only partially so),
215
        /// the returned message batch will contain those uncommitted messages again.
216
        ///
217
        /// ## Returns
218
        ///
219
        /// `(M, u64)`, where
220
        ///
221
        /// - `M: DeserializeOwned`: Message batch type, specified by the caller
222
        /// - `u64`: Value of response header "X-P2-Return-Body-Size-Limit", telling the client whether
223
        ///   the route to commit re-signed messages has an upload size limit, and what the size of
224
        ///   that limit is, in bytes. A value of 0 means that there is no limit.
225
        ///
226
        /// ## Errors
227
        ///
228
        /// This function may fail on errors encountered when building the request, when sending the
229
        /// request or when parsing the response. In particular, failure on response parsing is expected
230
        /// if
231
        ///
232
        /// - You receive a `403`/`404` error
233
        /// - The "X-P2-Return-Body-Size-Limit" header in the response is formatted incorrectly
234
        ///   (i.e. not a string-like unsigned-integer-like)
235
        /// - The "X-P2-Return-Body-Size-Limit" exists but is larger than `u64::MAX`
236
        pub async fn get_messages_to_be_resigned<M: DeserializeOwned>(
237
            &self,
238
            limit: u32,
239
            serial_number: Option<SerialNumber>,
240
        ) -> HttpResult<(M, u64)> {
241
            let mut request = P2RequestBuilder::new(&self)
×
242
                .auth_token(self.token.clone())
×
243
                .homeserver(self.instance_url.clone())
×
244
                .endpoint(GET_MESSAGES_TO_BE_RESIGNED)
×
245
                .query("limit", &limit.to_string());
×
246
            if let Some(serial) = serial_number {
×
247
                request = request.query("SerialNumber", &serial.to_string());
×
248
            }
249
            let request = request.build()?;
×
250
            let response = self.send_request(request).await?;
×
251
            #[allow(clippy::unwrap_used)] // HeaderValue::from_str("0") is always valid.
252
            let return_body_size_string = response
×
253
                .headers()
254
                .get("X-P2-Return-Body-Size-Limit")
UNCOV
255
                .unwrap_or(&HeaderValue::from_str("0").unwrap())
×
256
                .to_str()
UNCOV
257
                .map_err(|e| RequestError::Custom {
×
258
                    reason: e.to_string(),
×
259
                })?
260
                .to_string();
UNCOV
261
            let return_body_size =
×
262
                return_body_size_string
×
263
                    .parse::<u64>()
UNCOV
264
                    .map_err(|e| RequestError::Custom {
×
265
                        reason: e.to_string(),
×
266
                    })?;
UNCOV
267
            let response_text = response.text().await?;
×
268
            let m = from_str::<M>(&response_text)?;
×
269
            Ok((m, return_body_size))
×
270
        }
271

272
        /// Request allowing message re-signing. To fulfill this action, a key trial must be passed.
273
        /// Unlocks message re-signing for messages signed with keys for which a key trial has been passed.
274
        pub async fn request_message_resigning(
275
            &self,
276
            keytrials: &[KeyTrialResponse],
277
        ) -> HttpResult<()> {
UNCOV
278
            let request = P2RequestBuilder::new(&self)
×
279
                .homeserver(self.instance_url.clone())
×
280
                .endpoint(REQUEST_MESSAGE_RESIGNING)
×
281
                .key_trials(keytrials.to_vec())
×
282
                .auth_token(self.token.clone())
×
283
                .build()?;
UNCOV
284
            let response = self.send_request(request).await?;
×
285
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())
×
286
        }
287

288
        /// Stop an in-progress or existing re-signing process.
UNCOV
289
        pub async fn abort_message_resigning(&self, fid: &FederationId) -> HttpResult<()> {
×
290
            let request = P2RequestBuilder::new(&self)
×
291
                .homeserver(self.instance_url.clone())
×
292
                .endpoint(ABORT_MESSAGE_RESIGNING)
×
293
                .auth_token(self.token.clone())
×
294
                .query("removeActorFid", &fid.to_string())
×
295
                .build()?;
UNCOV
296
            let response = self.send_request(request).await?;
×
297
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())
×
298
        }
299

300
        /// Commit messages that have been re-signed to the server.
301
        pub async fn commit_resigned_messages<M: Serialize + DeserializeOwned>(
302
            &self,
303
            message_batch: M,
304
        ) -> HttpResult<Option<M>> {
UNCOV
305
            let request = P2RequestBuilder::new(&self)
×
306
                .auth_token(self.token.clone())
×
307
                .homeserver(self.instance_url.clone())
×
308
                .endpoint(COMMIT_RESIGNED_MESSAGES)
×
309
                .body(json!(message_batch))
×
310
                .build()?;
UNCOV
311
            let response = self.send_request(request).await?;
×
312
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())?;
×
313
            let response_text = response.text().await?;
×
314
            if response_text.trim() == "{}" || response_text.is_empty() {
×
315
                Ok(None)
×
316
            } else {
UNCOV
317
                match from_str::<M>(&response_text) {
×
318
                    Ok(next_message_batch) => Ok(Some(next_message_batch)),
×
319
                    Err(e) => Err(e.into()),
×
320
                }
321
            }
322
        }
323

324
        /// Tell the homeserver of the "old" actor account that you intend to set up a redirect to this actor
325
        /// ("this actor" refers to the actor this [Session] belongs to).
UNCOV
326
        pub async fn set_up_redirect_extern(&self, source: &FederationId) -> HttpResult<()> {
×
327
            let request = P2RequestBuilder::new(&self)
×
328
                .homeserver(self.instance_url.clone())
×
329
                .endpoint(SET_UP_REDIRECT_EXTERN)
×
330
                .auth_token(self.token.clone())
×
331
                .query("redirectSourceFid", &source.to_string())
×
332
                .build()?;
UNCOV
333
            let response = self.send_request(request).await?;
×
334
            matches_status_code(
UNCOV
335
                &[StatusCode::OK, StatusCode::NO_CONTENT, StatusCode::ACCEPTED],
×
336
                response.status(),
×
337
            )
338
        }
339

340
        /// Tell the homeserver of the "old" actor account that you no longer intend to set up a
341
        /// redirect to this actor ("this actor" refers to the actor this [Session] belongs to).
UNCOV
342
        pub async fn remove_redirect_extern(&self, source: &FederationId) -> HttpResult<()> {
×
343
            let request = P2RequestBuilder::new(&self)
×
344
                .homeserver(self.instance_url.clone())
×
345
                .endpoint(REMOVE_REDIRECT_EXTERN)
×
346
                .auth_token(self.token.clone())
×
347
                .query("redirectSourceFid", &source.to_string())
×
348
                .build()?;
UNCOV
349
            let response = self.send_request(request).await?;
×
350
            matches_status_code(&[StatusCode::OK, StatusCode::NO_CONTENT], response.status())
×
351
        }
352
    }
353
}
354

355
#[cfg(test)]
356
mod test {
357
    use http::HeaderValue;
358

359
    #[test]
360
    #[allow(clippy::unwrap_used)]
361
    fn header_value_zero() {
362
        HeaderValue::from_str("0").unwrap();
363
    }
364
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc