diff --git a/Cargo.toml b/Cargo.toml index a695911c..99cfe4b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,5 +17,6 @@ js_int = "0.1.4" ruma-api = "0.16.0-rc.3" ruma-events = "0.21.0-beta.1" ruma-identifiers = "0.16.0" +ruma-serde = "0.1.0" serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.51" diff --git a/src/lib.rs b/src/lib.rs index ccdc968e..9920920a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use serde_json::Value as JsonValue; pub mod unversioned; pub mod v1; +pub mod v2; /// A 'persistent data unit' (event) for room versions 3 and beyond. #[derive(Deserialize, Serialize)] diff --git a/src/unversioned.rs b/src/unversioned.rs index bc11c34a..5e98cd63 100644 --- a/src/unversioned.rs +++ b/src/unversioned.rs @@ -1,3 +1,3 @@ //! Endpoints that cannot change with new versions of the Matrix specification. -mod discover_homeserver; +pub mod discover_homeserver; diff --git a/src/unversioned/discover_homeserver.rs b/src/unversioned/discover_homeserver.rs index ed8f9f80..2d6f386d 100644 --- a/src/unversioned/discover_homeserver.rs +++ b/src/unversioned/discover_homeserver.rs @@ -1,3 +1,5 @@ +//! [GET /.well-known/matrix/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-well-known-matrix-server) + use ruma_api::ruma_api; ruma_api! { diff --git a/src/v1.rs b/src/v1.rs index d0381b80..94961924 100644 --- a/src/v1.rs +++ b/src/v1.rs @@ -1,3 +1,4 @@ //! Endpoints for the r0.1.x versions of the federation API specification. -mod get_server_version; +pub mod get_public_rooms; +pub mod get_server_version; diff --git a/src/v1/get_public_rooms.rs b/src/v1/get_public_rooms.rs new file mode 100644 index 00000000..c989d008 --- /dev/null +++ b/src/v1/get_public_rooms.rs @@ -0,0 +1,177 @@ +//! [GET /_matrix/federation/v1/publicRooms](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-publicrooms) + +use std::fmt; + +use js_int::UInt; +use ruma_api::ruma_api; +use ruma_identifiers::{RoomAliasId, RoomId}; +use serde::{ + de::{MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Deserializer, Serialize, Serializer, +}; + +ruma_api! { + metadata { + description: "Gets all the public rooms for the homeserver.", + method: GET, + name: "get_public_rooms", + path: "/_matrix/federation/v1/publicRooms", + rate_limited: false, + requires_authentication: true, + } + + request { + /// The maximum number of rooms to return. Default is no limit. + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub limit: Option, + /// Pagination token from a previous request. + #[serde(skip_serializing_if = "Option::is_none")] + #[ruma_api(query)] + pub since: Option, + /// Network to fetch the public room lists from. + #[serde(flatten, skip_serializing_if = "ruma_serde::is_default")] + #[ruma_api(query)] + pub room_network: RoomNetwork, + } + + response { + /// A paginated chunk of public rooms. + pub chunk: Vec, + /// A pagination token for the response. + #[serde(skip_serializing_if = "Option::is_none")] + pub next_batch: Option, + /// A pagination token that allows fetching previous results. + #[serde(skip_serializing_if = "Option::is_none")] + pub prev_batch: Option, + /// An estimate on the total number of public rooms, if the server has an estimate. + pub total_room_count_estimate: Option, + } +} + +/// A chunk of a room list response, describing one room +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PublicRoomsChunk { + /// Aliases of the room. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub aliases: Vec, + /// The canonical alias of the room, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub canonical_alias: Option, + /// The name of the room, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The number of members joined to the room. + pub num_joined_members: UInt, + /// The ID of the room. + pub room_id: RoomId, + /// The topic of the room, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub topic: Option, + /// Whether the room may be viewed by guest users without joining. + pub world_readable: bool, + /// Whether guest users may join the room and participate in it. + /// + /// If they can, they will be subject to ordinary power level rules like any other user. + pub guest_can_join: bool, + /// The URL for the room's avatar, if one is set. + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar_url: Option, +} + +/// Information about which networks/protocols from application services on the +/// homeserver from which to request rooms. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RoomNetwork { + /// Return rooms from the Matrix network. + Matrix, + /// Return rooms from all the networks/protocols the homeserver knows about. + All, + /// Return rooms from a specific third party network/protocol. + ThirdParty(String), +} + +impl Default for RoomNetwork { + fn default() -> Self { + RoomNetwork::Matrix + } +} + +impl Serialize for RoomNetwork { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state; + match self { + Self::Matrix => { + state = serializer.serialize_struct("RoomNetwork", 0)?; + } + Self::All => { + state = serializer.serialize_struct("RoomNetwork", 1)?; + state.serialize_field("include_all_networks", &true)?; + } + Self::ThirdParty(network) => { + state = serializer.serialize_struct("RoomNetwork", 1)?; + state.serialize_field("third_party_instance_id", network)?; + } + } + state.end() + } +} + +impl<'de> Deserialize<'de> for RoomNetwork { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(RoomNetworkVisitor) + } +} + +struct RoomNetworkVisitor; +impl<'de> Visitor<'de> for RoomNetworkVisitor { + type Value = RoomNetwork; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Network selection") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut include_all_networks = false; + let mut third_party_instance_id = None; + while let Some((key, value)) = access.next_entry::()? { + match key.as_str() { + "include_all_networks" => { + include_all_networks = match value.as_bool() { + Some(b) => b, + _ => false, + } + } + "third_party_instance_id" => { + third_party_instance_id = value.as_str().map(|v| v.to_owned()) + } + _ => {} + }; + } + + if include_all_networks { + if third_party_instance_id.is_none() { + Ok(RoomNetwork::All) + } else { + Err(M::Error::custom( + "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive.", + )) + } + } else { + Ok(match third_party_instance_id { + Some(network) => RoomNetwork::ThirdParty(network), + None => RoomNetwork::Matrix, + }) + } + } +} diff --git a/src/v1/get_server_version.rs b/src/v1/get_server_version.rs index 1fb9501b..ea8f2704 100644 --- a/src/v1/get_server_version.rs +++ b/src/v1/get_server_version.rs @@ -1,3 +1,5 @@ +//! [GET /_matrix/federation/v1/version](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-version) + use ruma_api::ruma_api; use serde::{Deserialize, Serialize}; @@ -15,14 +17,18 @@ ruma_api! { response { /// Information about the homeserver implementation - server: Server, + #[serde(skip_serializing_if = "Option::is_none")] + pub server: Option, } } #[derive(Clone, Debug, Serialize, Deserialize)] +/// Arbitrary values that identify this implementation. pub struct Server { - /// Arbitrary name that identify this implementation. - name: Option, + /// Arbitrary name that identifies this implementation. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, /// Version of this implementation. The version format depends on the implementation. - version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } diff --git a/src/v2.rs b/src/v2.rs new file mode 100644 index 00000000..e9fa4656 --- /dev/null +++ b/src/v2.rs @@ -0,0 +1,3 @@ +//! Endpoints for the r0.1.x versions of the federation API specification. + +pub mod get_server_keys; diff --git a/src/v2/get_server_keys.rs b/src/v2/get_server_keys.rs new file mode 100644 index 00000000..73160322 --- /dev/null +++ b/src/v2/get_server_keys.rs @@ -0,0 +1,54 @@ +//! [GET /_matrix/key/v2/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-key-v2-server-keyid) + +use std::{collections::BTreeMap, time::SystemTime}; + +use ruma_api::ruma_api; +use serde::{Deserialize, Serialize}; + +ruma_api! { + metadata { + description: "Gets the homeserver's published signing keys.", + method: GET, + name: "get_server_keys", + path: "/_matrix/key/v2/server", + rate_limited: false, + requires_authentication: false, + } + + request {} + + response { + // Spec is wrong, all fields are required (see + // https://github.com/matrix-org/matrix-doc/issues/2508) + + /// DNS name of the homeserver. + pub server_name: String, + /// Public keys of the homeserver for verifying digital signatures. + pub verify_keys: BTreeMap, + /// Public keys that the homeserver used to use and when it stopped using them. + pub old_verify_keys: BTreeMap, + /// Digital signatures of this object signed using the verify_keys. + pub signatures: BTreeMap>, + /// Timestamp when the keys should be refreshed. This field MUST be ignored in room + /// versions 1, 2, 3, and 4. + #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] + pub valid_until_ts: SystemTime, + } +} + +/// Public key of the homeserver for verifying digital signatures. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct VerifyKey { + /// The Unpadded Base64 encoded key. + pub key: String, +} + +/// A key the server used to use, but stopped using. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct OldVerifyKey { + /// Timestamp when this key expired. + #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] + pub expired_ts: SystemTime, + /// The Unpadded Base64 encoded key. + pub key: String, +}