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

polyphony-chat / polyproto-rs / #47

23 Feb 2025 01:12PM UTC coverage: 74.06% (+5.9%) from 68.198%
#47

push

bitfl0wer
LLVM coverage backend kind of sucks (inaccurate results), revert to ptrace

985 of 1330 relevant lines covered (74.06%)

1.88 hits per line

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

90.86
/src/api/core/mod.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

5
use std::time::UNIX_EPOCH;
6

7
use crate::types::x509_cert::SerialNumber;
8
use crate::url::Url;
9
use serde::{Deserialize, Serialize};
10
use serde_json::json;
11

12
use crate::certs::idcert::IdCert;
13
use crate::certs::idcsr::IdCsr;
14
use crate::certs::SessionId;
15
use crate::errors::{ConversionError, RequestError};
16
use crate::key::PublicKey;
17
use crate::signature::Signature;
18
use crate::types::routes::core::v1::*;
19
use crate::types::{ChallengeString, EncryptedPkm, FederationId, Service, ServiceName};
20

21
use super::{HttpClient, HttpResult};
22

23
/// Get the current UNIX timestamp according to the system clock.
24
pub fn current_unix_time() -> u64 {
1✔
25
    std::time::SystemTime::now()
2✔
26
        .duration_since(UNIX_EPOCH)
27
        .unwrap()
28
        .as_secs()
29
}
30

31
// Core Routes: No registration needed
32
impl HttpClient {
33
    /// Request a [ChallengeString] from the server.
34
    pub async fn get_challenge_string(&self) -> HttpResult<ChallengeString> {
4✔
35
        let request_url = self.url.join(GET_CHALLENGE_STRING.path)?;
1✔
36
        let request_response = self
4✔
37
            .client
38
            .request(GET_CHALLENGE_STRING.method.clone(), request_url)
1✔
39
            .send()
40
            .await;
4✔
41
        HttpClient::handle_response(request_response).await
2✔
42
    }
43

44
    /// Request the server to rotate its identity key and return the new [IdCert]. This route is
45
    /// only available to server administrators.
46
    ///
47
    /// ## Safety guarantees
48
    ///
49
    /// The resulting [IdCert] is verified and has the same safety guarantees as specified under
50
    /// [IdCert::full_verify_home_server()], as this method calls that method internally.
51
    pub async fn rotate_server_identity_key<S: Signature, P: PublicKey<S>>(
1✔
52
        &self,
53
    ) -> HttpResult<IdCert<S, P>> {
54
        let request_url = self.url.join(ROTATE_SERVER_IDENTITY_KEY.path)?;
1✔
55
        let request_response = self
4✔
56
            .client
×
57
            .request(ROTATE_SERVER_IDENTITY_KEY.method.clone(), request_url)
1✔
58
            .send()
59
            .await;
4✔
60
        let pem = HttpClient::handle_response::<String>(request_response).await?;
1✔
61
        log::debug!("Received IdCert: \n{}", pem);
3✔
62
        let id_cert = IdCert::<S, P>::from_pem_unchecked(&pem)?;
2✔
63
        match id_cert.full_verify_home_server(
1✔
64
            std::time::SystemTime::now()
3✔
65
                .duration_since(UNIX_EPOCH)
×
66
                .unwrap()
×
67
                .as_secs(),
×
68
        ) {
69
            Ok(_) => (),
×
70
            Err(e) => return Err(RequestError::ConversionError(e.into())),
×
71
        };
72
        Ok(id_cert)
1✔
73
    }
74

75
    /// Request the server's public [IdCert]. Specify a unix timestamp to get the IdCert which was
76
    /// valid at that time. If no timestamp is provided, the current IdCert is returned.
77
    ///
78
    /// ## Safety guarantees
79
    ///
80
    /// The resulting [IdCert] is verified and has the same safety guarantees as specified under
81
    /// [IdCert::full_verify_home_server()], as this method calls that method internally.
82
    pub async fn get_server_id_cert<S: Signature, P: PublicKey<S>>(
1✔
83
        &self,
84
        unix_time: Option<u64>,
85
    ) -> HttpResult<IdCert<S, P>> {
86
        let request_url = self.url.join(GET_SERVER_IDCERT.path)?;
1✔
87
        let mut request = self
2✔
88
            .client
×
89
            .request(GET_SERVER_IDCERT.method.clone(), request_url);
3✔
90
        if let Some(time) = unix_time {
1✔
91
            request = request.body(json!({ "timestamp": time }).to_string());
2✔
92
        }
93
        let response = request.send().await;
3✔
94
        let pem = HttpClient::handle_response::<String>(response).await?;
1✔
95
        let id_cert = IdCert::<S, P>::from_pem_unchecked(&pem)?;
2✔
96
        match id_cert.full_verify_home_server(unix_time.unwrap_or(current_unix_time())) {
2✔
97
            Ok(_) => (),
×
98
            Err(e) => return Err(RequestError::ConversionError(e.into())),
×
99
        };
100
        Ok(id_cert)
1✔
101
    }
102

103
    /// Request the [IdCert]s of an actor. Specify the federation ID of the actor to get the IdCerts
104
    /// of that actor. Returns a vector of IdCerts which were valid for the actor at the specified
105
    /// time. If no timestamp is provided, the current IdCerts are returned.
106
    ///
107
    /// ## Safety guarantees
108
    ///
109
    /// The resulting [IdCert]s are not verified. The caller is responsible for verifying the correctness
110
    /// of these `IdCert`s using [IdCert::full_verify_actor()] before using them.
111
    pub async fn get_actor_id_certs<S: Signature, P: PublicKey<S>>(
1✔
112
        &self,
113
        fid: &str,
114
        unix_time: Option<u64>,
115
        session_id: Option<&SessionId>,
116
    ) -> HttpResult<Vec<IdCertExt<S, P>>> {
117
        let request_url = self
2✔
118
            .url
×
119
            .join(&format!("{}{}", GET_ACTOR_IDCERTS.path, fid))?;
4✔
120
        let mut request = self
2✔
121
            .client
×
122
            .request(GET_ACTOR_IDCERTS.method.clone(), request_url);
3✔
123
        let body = match (unix_time, session_id) {
1✔
124
            // PRETTYFYME
125
            (Some(time), Some(session)) => {
1✔
126
                Some(json!({ "timestamp": time, "session_id": session.to_string() }))
4✔
127
            }
128
            (Some(time), None) => Some(json!({"timestamp": time})),
3✔
129
            (None, Some(session)) => Some(json!({"session_id": session.to_string()})),
3✔
130
            (None, None) => None,
×
131
        };
132
        if let Some(body) = body {
2✔
133
            request = request.body(body.to_string());
1✔
134
        }
135
        let response = request.send().await;
3✔
136
        let pems = HttpClient::handle_response::<Vec<IdCertExtJson>>(response).await?;
2✔
137
        let mut vec_idcert = Vec::new();
1✔
138
        for json in pems.into_iter() {
4✔
139
            vec_idcert.push(IdCertExt::try_from(json)?);
2✔
140
        }
141
        Ok(vec_idcert)
1✔
142
    }
143

144
    /// Inform a foreign server about a new [IdCert] for a session.
145
    pub async fn update_session_id_cert<S: Signature, P: PublicKey<S>>(
1✔
146
        &self,
147
        new_cert: IdCert<S, P>,
148
    ) -> HttpResult<()> {
149
        let request_url = self.url.join(UPDATE_SESSION_IDCERT.path)?;
1✔
150
        self.client
7✔
151
            .request(UPDATE_SESSION_IDCERT.method.clone(), request_url)
3✔
152
            .body(new_cert.to_pem(der::pem::LineEnding::LF)?)
1✔
153
            .send()
154
            .await?;
5✔
155
        Ok(())
1✔
156
    }
157

158
    /// Tell a server to delete a session, revoking the session token.
159
    pub async fn delete_session(&self, session_id: &SessionId) -> HttpResult<()> {
4✔
160
        let request_url = self.url.join(DELETE_SESSION.path)?;
1✔
161
        let body = json!({ "session_id": session_id.to_string() });
2✔
162
        self.client
7✔
163
            .request(DELETE_SESSION.method.clone(), request_url)
3✔
164
            .body(body.to_string())
2✔
165
            .send()
166
            .await?;
5✔
167
        Ok(())
1✔
168
    }
169

170
    // TODO: Test discover_services and discover_service
171
    /// Fetch a list of all services that the actor specified in the `actor_fid` argument has made
172
    /// discoverable.
173
    ///
174
    /// ## Parameters
175
    ///
176
    /// `limit`: How many results to return at maximum. Omitting this value will return all existing
177
    /// results.
178
    pub async fn discover_services(
1✔
179
        &self,
180
        actor_fid: &FederationId,
181
        limit: Option<u32>,
182
    ) -> HttpResult<Vec<Service>> {
183
        let request_url = self
4✔
184
            .url
185
            .join(DISCOVER_SERVICE_ALL.path)?
1✔
186
            .join(&actor_fid.to_string())?;
2✔
187
        let mut request = self
2✔
188
            .client
189
            .request(DISCOVER_SERVICE_ALL.method.clone(), request_url);
3✔
190
        if let Some(limit) = limit {
1✔
191
            request = request.body(
3✔
192
                json!({
3✔
193
                    "limit": limit
194
                })
195
                .to_string(),
196
            );
197
        }
198
        let response = request.send().await;
3✔
199
        HttpClient::handle_response::<Vec<Service>>(response).await
2✔
200
    }
201

202
    /// Fetch a list of services an actor is registered with, filtered by `service_name`.
203
    ///
204
    /// ## Parameters
205
    ///
206
    /// `limit`: Whether to limit the amount of returned results. Not specifying a limit will
207
    /// return all services. Specifying a limit value of 1 will return only the primary
208
    /// service provider.
209
    pub async fn discover_service(
1✔
210
        &self,
211
        actor_fid: &FederationId,
212
        service_name: &ServiceName,
213
        limit: Option<u32>,
214
    ) -> HttpResult<Vec<Service>> {
215
        let request_url = self
2✔
216
            .url
217
            .join(&format!("{}{}", DISCOVER_SERVICE_SINGULAR.path, actor_fid))?;
4✔
218
        let mut request = self
2✔
219
            .client
220
            .request(DISCOVER_SERVICE_SINGULAR.method.clone(), request_url);
3✔
221
        if let Some(limit) = limit {
1✔
222
            request = request.body(
3✔
223
                json!({
4✔
224
                    "limit": limit,
225
                    "name": service_name
226
                })
227
                .to_string(),
228
            );
229
        } else {
230
            request = request.body(
3✔
231
                json!({
3✔
232
                    "name": service_name
233
                })
234
                .to_string(),
235
            );
236
        }
237
        let response = request.send().await;
3✔
238
        HttpClient::handle_response::<Vec<Service>>(response).await
2✔
239
    }
240
}
241

242
// Core Routes: Registration needed
243
impl HttpClient {
244
    /// Rotate your keys for a given session. The `session_id` in the supplied [IdCsr] must
245
    /// correspond to the session token used in the authorization-Header.
246
    ///
247
    /// Returns the new [IdCert] and a token which can be used to authenticate future requests.
248
    ///
249
    /// ## Safety guarantees
250
    ///
251
    /// The resulting [IdCert] is not verified. The caller is responsible for verifying the correctness
252
    /// of this `IdCert` using either [IdCert::full_verify_actor()] or [IdCert::full_verify_home_server()].
253
    pub async fn rotate_session_id_cert<S: Signature, P: PublicKey<S>>(
1✔
254
        &self,
255
        csr: IdCsr<S, P>,
256
    ) -> HttpResult<(IdCert<S, P>, String)> {
257
        let request_url = self.url.join(ROTATE_SESSION_IDCERT.path)?;
1✔
258
        let request_response = self
6✔
259
            .client
×
260
            .request(ROTATE_SESSION_IDCERT.method.clone(), request_url)
3✔
261
            .body(csr.to_pem(der::pem::LineEnding::LF)?)
1✔
262
            .send()
263
            .await;
4✔
264
        let response_value = HttpClient::handle_response::<IdCertToken>(request_response).await?;
2✔
265
        let id_cert = IdCert::<S, P>::from_pem_unchecked(&response_value.id_cert.to_string())?;
2✔
266
        Ok((id_cert, response_value.token))
1✔
267
    }
268

269
    /// Upload encrypted private key material to the server for later retrieval. The upload size
270
    /// must not exceed the server's maximum upload size for this route. This is usually not more
271
    /// than 10kb and can be as low as 800 bytes, depending on the server configuration.
272
    ///
273
    /// The `data` parameter is a vector of [EncryptedPkm] which contains the serial number of the
274
    /// ID-Cert and the encrypted private key material. Naturally, the server cannot check the
275
    /// contents of the encrypted private key material. However, it is recommended to store the data
276
    /// in a `SubjectPublicKeyInfo` structure, where the public key is the private key material.
277
    pub async fn upload_encrypted_pkm(&self, data: Vec<EncryptedPkm>) -> HttpResult<()> {
4✔
278
        let mut body = Vec::new();
1✔
279
        for pkm in data.iter() {
3✔
280
            body.push(json!(pkm));
2✔
281
        }
282
        let request_url = self.url.join(UPLOAD_ENCRYPTED_PKM.path)?;
1✔
283
        self.client
7✔
284
            .request(UPLOAD_ENCRYPTED_PKM.method.clone(), request_url)
3✔
285
            .body(json!(body).to_string())
3✔
286
            .send()
287
            .await?;
5✔
288
        Ok(())
1✔
289
    }
290

291
    /// Retrieve encrypted private key material from the server. The serial_numbers, if provided,
292
    /// must match the serial numbers of ID-Certs that the client has uploaded key material for.
293
    /// If no serial_numbers are provided, the server will return all key material that the client
294
    /// has uploaded.
295
    pub async fn get_encrypted_pkm(
1✔
296
        &self,
297
        serials: Vec<SerialNumber>,
298
    ) -> HttpResult<Vec<EncryptedPkm>> {
299
        let request_url = self.url.join(GET_ENCRYPTED_PKM.path)?;
1✔
300
        let mut body = Vec::new();
1✔
301
        for serial in serials.iter() {
3✔
302
            body.push(json!(serial.try_as_u128()?));
3✔
303
        }
304
        let request = self
3✔
305
            .client
306
            .request(GET_ENCRYPTED_PKM.method.clone(), request_url)
3✔
307
            .body(json!(body).to_string());
3✔
308
        let response =
3✔
309
            HttpClient::handle_response::<Vec<EncryptedPkm>>(request.send().await).await?;
310
        let mut vec_pkm = Vec::new();
1✔
311
        for pkm in response.into_iter() {
4✔
312
            vec_pkm.push(pkm);
2✔
313
        }
314
        Ok(vec_pkm)
1✔
315
    }
316

317
    /// Delete encrypted private key material from the server. The serials must match the
318
    /// serial numbers of ID-Certs that the client has uploaded key material for.
319
    pub async fn delete_encrypted_pkm(&self, serials: Vec<SerialNumber>) -> HttpResult<()> {
4✔
320
        let request_url = self.url.join(DELETE_ENCRYPTED_PKM.path)?;
1✔
321
        let mut body = Vec::new();
1✔
322
        for serial in serials.iter() {
3✔
323
            body.push(json!(serial.try_as_u128()?));
3✔
324
        }
325
        self.client
7✔
326
            .request(DELETE_ENCRYPTED_PKM.method.clone(), request_url)
3✔
327
            .body(json!(body).to_string())
3✔
328
            .send()
329
            .await?;
5✔
330
        Ok(())
1✔
331
    }
332

333
    /// Retrieve the maximum upload size for encrypted private key material, in bytes.
334
    pub async fn get_pkm_upload_size_limit(&self) -> HttpResult<u64> {
4✔
335
        let request = self.client.request(
2✔
336
            GET_ENCRYPTED_PKM_UPLOAD_SIZE_LIMIT.method.clone(),
2✔
337
            self.url.join(GET_ENCRYPTED_PKM_UPLOAD_SIZE_LIMIT.path)?,
2✔
338
        );
339
        let response = request.send().await;
2✔
340
        HttpClient::handle_response::<u64>(response).await
2✔
341
    }
342

343
    /// Add a service to the list of discoverable services. The service must be a valid [Service].
344
    /// If the service provider is the first to provide this service, or if the [Service] has a
345
    /// property of `primary` set to `true`, the service will be marked as the primary service
346
    /// provider for this service.
347
    ///
348
    /// The server will return a [Vec] of all [Service]s affected
349
    /// by this operation. This [Vec] will have a length of 1, if no other service entry was
350
    /// affected, and a length of 2 if this new service entry has replaced an existing one in the
351
    /// role of primary service provider.
352
    pub async fn add_discoverable_service(&self, service: &Service) -> HttpResult<Vec<Service>> {
4✔
353
        let request = self
3✔
354
            .client
355
            .request(
356
                CREATE_DISCOVERABLE.method.clone(),
2✔
357
                self.url.join(CREATE_DISCOVERABLE.path)?,
2✔
358
            )
359
            .body(json!(service).to_string());
3✔
360
        let response = request.send().await;
2✔
361
        HttpClient::handle_response::<Vec<Service>>(response).await
2✔
362
    }
363

364
    /// Delete a discoverable service from the list of discoverable services. The service must be a
365
    /// valid [Service] that exists in the list of discoverable services. On success, the server will
366
    /// return a [ServiceDeleteResponse] containing the deleted service and, if applicable, the new
367
    /// primary service provider for the service.
368
    pub async fn delete_discoverable_service(
1✔
369
        &self,
370
        url: &Url,
371
        name: &ServiceName,
372
    ) -> HttpResult<ServiceDeleteResponse> {
373
        let request = self
3✔
374
            .client
375
            .request(
376
                DELETE_DISCOVERABLE.method.clone(),
2✔
377
                self.url.join(DELETE_DISCOVERABLE.path)?,
2✔
378
            )
379
            .body(
380
                json!({
4✔
381
                    "url": url,
382
                    "name": name,
383
                })
384
                .to_string(),
385
            );
386
        let response = request.send().await;
2✔
387
        HttpClient::handle_response::<ServiceDeleteResponse>(response).await
2✔
388
    }
389

390
    /// Set the primary service provider for a service, by specifying the URL of the new primary
391
    /// service provider and the name of the service. The server will return a [Vec] of all [Service]s
392
    /// affected by this operation. This [Vec] will have a length of 1, if no other service entry was
393
    /// affected, and a length of 2 if this new service entry has replaced an existing one in the
394
    /// role of primary service provider.
395
    pub async fn set_primary_service_provider(
1✔
396
        &self,
397
        url: &Url,
398
        name: &ServiceName,
399
    ) -> HttpResult<Vec<Service>> {
400
        let request = self
3✔
401
            .client
402
            .request(
403
                SET_PRIMARY_DISCOVERABLE.method.clone(),
2✔
404
                self.url.join(SET_PRIMARY_DISCOVERABLE.path)?,
2✔
405
            )
406
            .body(
407
                json!({
4✔
408
                    "url": url,
409
                    "name": name
410
                })
411
                .to_string(),
412
            );
413
        let response = request.send().await;
2✔
414
        HttpClient::handle_response::<Vec<Service>>(response).await
2✔
415
    }
416
}
417

418
#[derive(Debug, Clone, PartialEq, Eq)]
419
/// Represents an [IdCert] with an additional field `invalidated` which indicates whether the
420
/// certificate has been invalidated. This type is used in the API as a response to the
421
/// `GET /.p2/core/v1/idcert/actor/:fid`
422
/// route. Can be converted to and (try)from [IdCertExtJson].
423
pub struct IdCertExt<S: Signature, P: PublicKey<S>> {
424
    /// The [IdCert] itself
425
    pub id_cert: IdCert<S, P>,
426
    /// Whether the certificate has been marked as invalidated
427
    pub invalidated: bool,
428
}
429

430
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
431
/// Stringly typed version of [IdCertExt], used for serialization and deserialization.
432
pub struct IdCertExtJson {
433
    /// The [IdCert] as a PEM encoded string
434
    pub id_cert: String,
435
    /// Whether the certificate has been marked as invalidated
436
    pub invalidated: bool,
437
}
438

439
impl<S: Signature, P: PublicKey<S>> From<IdCertExt<S, P>> for IdCertExtJson {
440
    fn from(id_cert: IdCertExt<S, P>) -> Self {
×
441
        Self {
442
            id_cert: id_cert.id_cert.to_pem(der::pem::LineEnding::LF).unwrap(),
×
443
            invalidated: id_cert.invalidated,
×
444
        }
445
    }
446
}
447

448
impl<S: Signature, P: PublicKey<S>> TryFrom<IdCertExtJson> for IdCertExt<S, P> {
449
    type Error = ConversionError;
450

451
    fn try_from(id_cert: IdCertExtJson) -> Result<Self, Self::Error> {
1✔
452
        Ok(Self {
1✔
453
            id_cert: IdCert::from_pem_unchecked(id_cert.id_cert.as_str())?,
2✔
454
            invalidated: id_cert.invalidated,
1✔
455
        })
456
    }
457
}
458

459
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
460
/// Represents a pair of an [IdCert] and a token, used in the API as a response when an [IdCsr] has
461
/// been accepted by the server.
462
pub struct IdCertToken {
463
    /// The [IdCert] as a PEM encoded string
464
    pub id_cert: String,
465
    /// The token as a string
466
    pub token: String,
467
}
468

469
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
470
#[derive(Debug, Clone, PartialEq, Eq)]
471
/// Represents a response to a service discovery deletion request. Contains the deleted service
472
/// and, if applicable, the new primary service provider for the service.
473
pub struct ServiceDeleteResponse {
474
    /// The service that was deleted.
475
    pub deleted: Service,
476
    /// The new primary service provider for the service, if applicable.
477
    pub new_primary: Option<Service>,
478
}
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