Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the dehydrated devices endpoints #1605

Merged
merged 9 commits into from Aug 9, 2023
88 changes: 88 additions & 0 deletions crates/ruma-client-api/src/dehydrated_device.rs
@@ -0,0 +1,88 @@
//! Endpoints for managing devices.
poljar marked this conversation as resolved.
Show resolved Hide resolved

use ruma_common::serde::StringEnum;
use serde::{Deserialize, Serialize};

use crate::PrivOwnedStr;

pub mod delete_dehydrated_device;
pub mod get_dehydrated_device;
pub mod get_events;
pub mod put_dehydrated_device;

/// Data for a dehydrated device.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "Helper", into = "Helper")]
zecakeh marked this conversation as resolved.
Show resolved Hide resolved
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum DehydratedDeviceData {
/// The `org.matrix.msc3814.v1.olm` variant of a dehydrated device.
V1(DehydratedDeviceV1),
}

impl DehydratedDeviceData {
/// Get the algorithm this dehydrated device uses.
pub fn algorithm(&self) -> DeviceDehydrationAlgorithm {
match self {
DehydratedDeviceData::V1(_) => DeviceDehydrationAlgorithm::V1,
}
}
}

/// The `org.matrix.msc3814.v1.olm` variant of a dehydrated device.
#[derive(Clone, Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct DehydratedDeviceV1 {
/// The pickle of the `Olm` account of the device.
///
/// The pickle will contain the private parts of the long-term identity keys of the device as
/// well as a collection of one-time keys.
pub device_pickle: String,
}

impl DehydratedDeviceV1 {
/// Create a [`DehydratedDeviceV1`] struct from a device pickle.
pub fn new(device_pickle: String) -> Self {
Self { device_pickle }
}
}

/// The algorithms used for dehydrated devices.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, PartialEq, Eq, StringEnum)]
#[non_exhaustive]
pub enum DeviceDehydrationAlgorithm {
/// The `org.matrix.msc3814.v1.olm` device dehydration algorithm.
#[ruma_enum(rename = "org.matrix.msc3814.v1.olm")]
V1,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}

#[derive(Deserialize, Serialize)]
struct Helper {
algorithm: DeviceDehydrationAlgorithm,
device_pickle: String,
}

impl TryFrom<Helper> for DehydratedDeviceData {
type Error = serde_json::Error;

fn try_from(value: Helper) -> Result<Self, Self::Error> {
match value.algorithm {
DeviceDehydrationAlgorithm::V1 => Ok(DehydratedDeviceData::V1(DehydratedDeviceV1 {
device_pickle: value.device_pickle,
})),
_ => Err(serde::de::Error::custom("Unsupported device dehydration algorithm.")),
}
}
}

impl From<DehydratedDeviceData> for Helper {
fn from(value: DehydratedDeviceData) -> Self {
let algorithm = value.algorithm();

match value {
DehydratedDeviceData::V1(d) => Self { algorithm, device_pickle: d.device_pickle },
}
}
}
@@ -0,0 +1,48 @@
//! `DELETE /_matrix/client/*/dehydrated_device/`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context, unspecced as of now, but mentioned in https://github.com/matrix-org/matrix-spec-proposals/pull/3814/files#r1266444656.

//!
//! Delete a dehydrated device.

pub mod unstable {
//! `msc3814` ([MSC])
//!
//! [MSC]: https://github.com/uhoreg/matrix-doc/blob/shrivelled_sessions/proposals/3814-dehydrated-devices-with-ssss.md
zecakeh marked this conversation as resolved.
Show resolved Hide resolved

use ruma_common::{
api::{request, response, Metadata},
metadata, OwnedDeviceId,
};

const METADATA: Metadata = metadata! {
method: DELETE,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device",
zecakeh marked this conversation as resolved.
Show resolved Hide resolved
}
};

/// Request type for the `DELETE` `dehydrated_device` endpoint.
#[request(error = crate::Error)]
pub struct Request {}

/// Request type for the `DELETE` `dehydrated_device` endpoint.
#[response(error = crate::Error)]
pub struct Response {
/// The unique ID of the device that was deleted.
pub device_id: OwnedDeviceId,
}

impl Request {
/// Creates a new empty `Request`.
pub fn new() -> Self {
Self {}
}
}

impl Response {
/// Creates a new `Response` with the given device ID.
pub fn new(device_id: OwnedDeviceId) -> Self {
Self { device_id }
}
}
}
@@ -0,0 +1,54 @@
//! `GET /_matrix/client/*/dehydrated_device/`
//!
//! Get a dehydrated device for rehydration.

pub mod unstable {
//! `msc3814` ([MSC])
//!
//! [MSC]: https://github.com/uhoreg/matrix-doc/blob/shrivelled_sessions/proposals/3814-dehydrated-devices-with-ssss.md
poljar marked this conversation as resolved.
Show resolved Hide resolved

use ruma_common::{
api::{request, response, Metadata},
metadata,
serde::Raw,
OwnedDeviceId,
};

use crate::dehydrated_device::DehydratedDeviceData;

const METADATA: Metadata = metadata! {
method: GET,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device",
}
};

/// Request type for the `GET` `dehydrated_device` endpoint.
#[request(error = crate::Error)]
pub struct Request {}

/// Request type for the `GET` `dehydrated_device` endpoint.
#[response(error = crate::Error)]
pub struct Response {
/// The unique ID of the device.
pub device_id: OwnedDeviceId,
/// Information about the device.
pub device_data: Raw<DehydratedDeviceData>,
}

impl Request {
/// Creates a new empty `Request`.
pub fn new() -> Self {
Self {}
}
}

impl Response {
/// Creates a new `Response` with the given device ID and device data.
pub fn new(device_id: OwnedDeviceId, device_data: Raw<DehydratedDeviceData>) -> Self {
Self { device_id, device_data }
}
}
}
58 changes: 58 additions & 0 deletions crates/ruma-client-api/src/dehydrated_device/get_events.rs
@@ -0,0 +1,58 @@
//! `GET /_matrix/client/*/dehydrated_device/{device_id}/events`
poljar marked this conversation as resolved.
Show resolved Hide resolved
//!
//! Get to-device events for a dehydrated device.

pub mod unstable {
//! `msc3814` ([MSC])
//!
//! [MSC]: https://github.com/uhoreg/matrix-doc/blob/shrivelled_sessions/proposals/3814-dehydrated-devices-with-ssss.md
poljar marked this conversation as resolved.
Show resolved Hide resolved

use ruma_common::{
api::{request, response, Metadata},
events::AnyToDeviceEvent,
metadata,
serde::Raw,
OwnedDeviceId,
};

const METADATA: Metadata = metadata! {
method: POST,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/:device_id/events",
}
};

/// Request type for the `dehydrated_device/{device_id}/events` endpoint.
#[request(error = crate::Error)]
pub struct Request {
/// The unique ID of the device for which we would like to fetch events for.
poljar marked this conversation as resolved.
Show resolved Hide resolved
#[ruma_api(path)]
pub device_id: OwnedDeviceId,
/// A point in time to continue getting events from.
///
/// Should be a token from the `next_batch` field of a previous `/events`
/// request.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_batch: Option<String>,
}

/// Request type for the `dehydrated_device/{device_id}/events` endpoint.
#[response(error = crate::Error)]
pub struct Response {
/// The batch token to supply in the `since` param of the next `/events` request.
pub next_batch: String,
poljar marked this conversation as resolved.
Show resolved Hide resolved

/// Messages sent directly between devices.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyToDeviceEvent>>,
zecakeh marked this conversation as resolved.
Show resolved Hide resolved
}

impl Request {
/// Create a new request.
pub fn new(device_id: OwnedDeviceId) -> Self {
Self { device_id, next_batch: None }
}
}
}
poljar marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,90 @@
//! `PUT /_matrix/client/*/dehydrated_device/`
//!
//! Uploads a dehydrated device to the homeserver.

pub mod unstable {
//! `msc3814` ([MSC])
//!
//! [MSC]: https://github.com/uhoreg/matrix-doc/blob/shrivelled_sessions/proposals/3814-dehydrated-devices-with-ssss.md
poljar marked this conversation as resolved.
Show resolved Hide resolved

use std::collections::BTreeMap;

use ruma_common::{
api::{request, response, Metadata},
encryption::{DeviceKeys, OneTimeKey},
metadata,
serde::Raw,
OwnedDeviceId, OwnedDeviceKeyId,
};

use crate::dehydrated_device::DehydratedDeviceData;

const METADATA: Metadata = metadata! {
method: PUT,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device",
}
};

/// Request type for the `PUT` `dehydrated_device` endpoint.
#[request(error = crate::Error)]
pub struct Request {
/// The unique ID of the device.
pub device_id: OwnedDeviceId,

/// The display name of the device.
pub initial_device_display_name: String,
poljar marked this conversation as resolved.
Show resolved Hide resolved

/// The data of the dehydrated device, containing the serialized and encrypted private
/// parts of the [`DeviceKeys`].
pub device_data: Raw<DehydratedDeviceData>,

/// Identity keys for the device.
///
/// May be absent if no new identity keys are required.
pub device_keys: Raw<DeviceKeys>,
poljar marked this conversation as resolved.
Show resolved Hide resolved

/// One-time public keys for "pre-key" messages.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub one_time_keys: BTreeMap<OwnedDeviceKeyId, Raw<OneTimeKey>>,

/// Fallback public keys for "pre-key" messages.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub fallback_keys: BTreeMap<OwnedDeviceKeyId, Raw<OneTimeKey>>,
}

/// Response type for the `upload_keys` endpoint.
#[response(error = crate::Error)]
pub struct Response {
/// The unique ID of the device.
pub device_id: OwnedDeviceId,
}

impl Request {
/// Creates a new Request.
pub fn new(
device_id: OwnedDeviceId,
initial_device_display_name: String,
device_data: Raw<DehydratedDeviceData>,
device_keys: Raw<DeviceKeys>,
) -> Self {
Self {
device_id,
initial_device_display_name,
device_data,
device_keys,
one_time_keys: Default::default(),
fallback_keys: Default::default(),
}
}
}

impl Response {
/// Creates a new `Response` with the given one time key counts.
pub fn new(device_id: OwnedDeviceId) -> Self {
Self { device_id }
}
}
}
1 change: 1 addition & 0 deletions crates/ruma-client-api/src/lib.rs
Expand Up @@ -15,6 +15,7 @@ pub mod appservice;
pub mod backup;
pub mod config;
pub mod context;
pub mod dehydrated_device;
zecakeh marked this conversation as resolved.
Show resolved Hide resolved
pub mod device;
pub mod directory;
pub mod discovery;
Expand Down