diff --git a/crates/ruma-client-api/Cargo.toml b/crates/ruma-client-api/Cargo.toml index 0b835babbb..0033ccd577 100644 --- a/crates/ruma-client-api/Cargo.toml +++ b/crates/ruma-client-api/Cargo.toml @@ -45,6 +45,7 @@ unstable-msc2965 = [] unstable-msc2967 = [] unstable-msc3488 = [] unstable-msc3575 = [] +unstable-msc3814 = [] [dependencies] assign = { workspace = true } diff --git a/crates/ruma-client-api/src/dehydrated_device.rs b/crates/ruma-client-api/src/dehydrated_device.rs new file mode 100644 index 0000000000..32f5dc1c89 --- /dev/null +++ b/crates/ruma-client-api/src/dehydrated_device.rs @@ -0,0 +1,88 @@ +//! Endpoints for managing dehydrated devices. + +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")] +#[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 for DehydratedDeviceData { + type Error = serde_json::Error; + + fn try_from(value: Helper) -> Result { + 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 for Helper { + fn from(value: DehydratedDeviceData) -> Self { + let algorithm = value.algorithm(); + + match value { + DehydratedDeviceData::V1(d) => Self { algorithm, device_pickle: d.device_pickle }, + } + } +} diff --git a/crates/ruma-client-api/src/dehydrated_device/delete_dehydrated_device.rs b/crates/ruma-client-api/src/dehydrated_device/delete_dehydrated_device.rs new file mode 100644 index 0000000000..af9ee887f3 --- /dev/null +++ b/crates/ruma-client-api/src/dehydrated_device/delete_dehydrated_device.rs @@ -0,0 +1,48 @@ +//! `DELETE /_matrix/client/*/dehydrated_device/` +//! +//! Delete a dehydrated device. + +pub mod unstable { + //! `msc3814` ([MSC]) + //! + //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814 + + 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", + } + }; + + /// 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 } + } + } +} diff --git a/crates/ruma-client-api/src/dehydrated_device/get_dehydrated_device.rs b/crates/ruma-client-api/src/dehydrated_device/get_dehydrated_device.rs new file mode 100644 index 0000000000..6580acabe3 --- /dev/null +++ b/crates/ruma-client-api/src/dehydrated_device/get_dehydrated_device.rs @@ -0,0 +1,54 @@ +//! `GET /_matrix/client/*/dehydrated_device/` +//! +//! Get a dehydrated device for rehydration. + +pub mod unstable { + //! `msc3814` ([MSC]) + //! + //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814 + + 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, + } + + 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) -> Self { + Self { device_id, device_data } + } + } +} diff --git a/crates/ruma-client-api/src/dehydrated_device/get_events.rs b/crates/ruma-client-api/src/dehydrated_device/get_events.rs new file mode 100644 index 0000000000..e342ab2a67 --- /dev/null +++ b/crates/ruma-client-api/src/dehydrated_device/get_events.rs @@ -0,0 +1,65 @@ +//! `POST /_matrix/client/*/dehydrated_device/{device_id}/events` +//! +//! Get to-device events for a dehydrated device. + +pub mod unstable { + //! `msc3814` ([MSC]) + //! + //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814 + + 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. + #[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, + } + + /// 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. Will be + /// none if no further events can be found. + pub next_batch: Option, + + /// Messages sent directly between devices. + pub events: Vec>, + } + + impl Request { + /// Create a new request. + pub fn new(device_id: OwnedDeviceId) -> Self { + Self { device_id, next_batch: None } + } + } + + impl Response { + /// Create a new response with the given events. + pub fn new(events: Vec>) -> Self { + Self { next_batch: None, events } + } + } +} diff --git a/crates/ruma-client-api/src/dehydrated_device/put_dehydrated_device.rs b/crates/ruma-client-api/src/dehydrated_device/put_dehydrated_device.rs new file mode 100644 index 0000000000..d2d759b756 --- /dev/null +++ b/crates/ruma-client-api/src/dehydrated_device/put_dehydrated_device.rs @@ -0,0 +1,87 @@ +//! `PUT /_matrix/client/*/dehydrated_device/` +//! +//! Uploads a dehydrated device to the homeserver. + +pub mod unstable { + //! `msc3814` ([MSC]) + //! + //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814 + + 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: Option, + + /// The data of the dehydrated device, containing the serialized and encrypted private + /// parts of the [`DeviceKeys`]. + pub device_data: Raw, + + /// Identity keys for the dehydrated device. + pub device_keys: Raw, + + /// One-time public keys for "pre-key" messages. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub one_time_keys: BTreeMap>, + + /// Fallback public keys for "pre-key" messages. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub fallback_keys: BTreeMap>, + } + + /// 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, + device_data: Raw, + device_keys: Raw, + ) -> Self { + Self { + device_id, + device_data, + device_keys, + initial_device_display_name: None, + 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 } + } + } +} diff --git a/crates/ruma-client-api/src/lib.rs b/crates/ruma-client-api/src/lib.rs index 54dad0c323..47d9a375ff 100644 --- a/crates/ruma-client-api/src/lib.rs +++ b/crates/ruma-client-api/src/lib.rs @@ -15,6 +15,8 @@ pub mod appservice; pub mod backup; pub mod config; pub mod context; +#[cfg(feature = "unstable-msc3814")] +pub mod dehydrated_device; pub mod device; pub mod directory; pub mod discovery; diff --git a/crates/ruma/Cargo.toml b/crates/ruma/Cargo.toml index 1815657359..bd324ad92f 100644 --- a/crates/ruma/Cargo.toml +++ b/crates/ruma/Cargo.toml @@ -181,6 +181,7 @@ unstable-msc3554 = ["ruma-common/unstable-msc3554"] unstable-msc3575 = ["ruma-client-api?/unstable-msc3575"] unstable-msc3618 = ["ruma-federation-api?/unstable-msc3618"] unstable-msc3723 = ["ruma-federation-api?/unstable-msc3723"] +unstable-msc3814 = ["ruma-client-api?/unstable-msc3814"] unstable-msc3927 = ["ruma-common/unstable-msc3927"] unstable-msc3931 = ["ruma-common/unstable-msc3931"] unstable-msc3932 = ["ruma-common/unstable-msc3932"] @@ -222,6 +223,7 @@ __ci = [ "unstable-msc3575", "unstable-msc3618", "unstable-msc3723", + "unstable-msc3814", "unstable-msc3927", "unstable-msc3932", "unstable-msc3954",