├── .gitignore ├── src ├── v2.rs ├── unversioned.rs ├── v1.rs ├── unversioned │ └── discover_homeserver.rs ├── v1 │ ├── get_server_version.rs │ └── get_public_rooms.rs ├── v2 │ └── get_server_keys.rs └── lib.rs ├── README.md ├── .builds ├── msrv.yml ├── beta.yml ├── stable.yml └── nightly.yml ├── Cargo.toml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /src/v2.rs: -------------------------------------------------------------------------------- 1 | //! Endpoints for the r0.1.x versions of the federation API specification. 2 | 3 | pub mod get_server_keys; 4 | -------------------------------------------------------------------------------- /src/unversioned.rs: -------------------------------------------------------------------------------- 1 | //! Endpoints that cannot change with new versions of the Matrix specification. 2 | 3 | pub mod discover_homeserver; 4 | -------------------------------------------------------------------------------- /src/v1.rs: -------------------------------------------------------------------------------- 1 | //! Endpoints for the r0.1.x versions of the federation API specification. 2 | 3 | pub mod get_public_rooms; 4 | pub mod get_server_version; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruma-federation-api 2 | 3 | **ruma-federation-api** contains serializable types for the requests and responses for each endpoint in the [Matrix](https://matrix.org/) federation API specification. 4 | These types can be shared by client and server code. 5 | 6 | ## Minimum Rust version 7 | 8 | ruma-federation-api requires Rust 1.40.0 or later. 9 | 10 | ## License 11 | 12 | [MIT](http://opensource.org/licenses/MIT) 13 | -------------------------------------------------------------------------------- /.builds/msrv.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-federation-api 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install 1.40.0 --profile minimal 10 | rustup default 1.40.0 11 | - test: | 12 | cd ruma-federation-api 13 | 14 | # Only make sure the code builds with the MSRV. Tests can require later 15 | # Rust versions, don't compile or run them. 16 | cargo build --verbose 17 | -------------------------------------------------------------------------------- /src/unversioned/discover_homeserver.rs: -------------------------------------------------------------------------------- 1 | //! [GET /.well-known/matrix/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-well-known-matrix-server) 2 | 3 | use ruma_api::ruma_api; 4 | 5 | ruma_api! { 6 | metadata { 7 | description: "Get discovery information about the domain.", 8 | method: GET, 9 | name: "discover_homeserver", 10 | path: "/.well-known/matrix/server", 11 | rate_limited: false, 12 | requires_authentication: false, 13 | } 14 | 15 | request {} 16 | 17 | response { 18 | /// The server name to delegate server-server communciations to, with optional port. 19 | #[serde(rename = "m.homeserver")] 20 | pub homeserver: String, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jonas Platte "] 3 | categories = ["api-bindings", "web-programming"] 4 | description = "Types for the endpoints in the Matrix server-server API." 5 | documentation = "https://docs.rs/ruma-federation-api" 6 | edition = "2018" 7 | homepage = "https://github.com/ruma/ruma-federation-api" 8 | keywords = ["matrix", "chat", "messaging", "ruma"] 9 | license = "MIT" 10 | name = "ruma-federation-api" 11 | readme = "README.md" 12 | repository = "https://github.com/ruma/ruma-federation-api" 13 | version = "0.0.1" 14 | 15 | [dependencies] 16 | js_int = "0.1.4" 17 | ruma-api = "0.16.0-rc.3" 18 | ruma-events = "0.21.0-beta.1" 19 | ruma-identifiers = "0.16.0" 20 | ruma-serde = "0.1.0" 21 | serde = { version = "1.0.106", features = ["derive"] } 22 | serde_json = "1.0.51" 23 | -------------------------------------------------------------------------------- /.builds/beta.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-federation-api 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install beta --profile minimal -c rustfmt -c clippy 10 | rustup default beta 11 | - test: | 12 | cd ruma-federation-api 13 | 14 | # We don't want the build to stop on individual failure of independent 15 | # tools, so capture tool exit codes and set the task exit code manually 16 | set +e 17 | 18 | cargo fmt -- --check 19 | fmt_exit=$? 20 | 21 | cargo clippy --all-targets --all-features -- -D warnings 22 | clippy_exit=$? 23 | 24 | cargo test --verbose 25 | test_exit=$? 26 | 27 | exit $(( $fmt_exit || $clippy_exit || $test_exit )) 28 | -------------------------------------------------------------------------------- /.builds/stable.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-federation-api 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install stable --profile minimal -c rustfmt -c clippy 10 | rustup default stable 11 | - test: | 12 | cd ruma-federation-api 13 | 14 | # We don't want the build to stop on individual failure of independent 15 | # tools, so capture tool exit codes and set the task exit code manually 16 | set +e 17 | 18 | cargo fmt -- --check 19 | fmt_exit=$? 20 | 21 | cargo clippy --all-targets --all-features -- -D warnings 22 | clippy_exit=$? 23 | 24 | cargo test --verbose 25 | test_exit=$? 26 | 27 | exit $(( $fmt_exit || $clippy_exit || $test_exit )) 28 | # TODO: Add audit task once cargo-audit binary releases are available. 29 | # See https://github.com/RustSec/cargo-audit/issues/66 30 | -------------------------------------------------------------------------------- /.builds/nightly.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-federation-api 6 | tasks: 7 | - rustup: | 8 | rustup toolchain install nightly --profile minimal 9 | rustup default nightly 10 | 11 | # Try installing rustfmt & clippy for nightly, but don't fail the build 12 | # if they are not available 13 | rustup component add rustfmt || true 14 | rustup component add clippy || true 15 | - test: | 16 | cd ruma-federation-api 17 | 18 | # We don't want the build to stop on individual failure of independent 19 | # tools, so capture tool exit codes and set the task exit code manually 20 | set +e 21 | 22 | if ( rustup component list | grep -q rustfmt ); then 23 | cargo fmt -- --check 24 | fi 25 | fmt_exit=$? 26 | 27 | if ( rustup component list | grep -q clippy ); then 28 | cargo clippy --all-targets --all-features -- -D warnings 29 | fi 30 | clippy_exit=$? 31 | 32 | exit $(( $fmt_exit || $clippy_exit )) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jimmy Cuadra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/v1/get_server_version.rs: -------------------------------------------------------------------------------- 1 | //! [GET /_matrix/federation/v1/version](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-version) 2 | 3 | use ruma_api::ruma_api; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | ruma_api! { 7 | metadata { 8 | description: "Get the implementation name and version of this homeserver.", 9 | method: GET, 10 | name: "discover_homeserver", 11 | path: "/.well-known/matrix/server", 12 | rate_limited: false, 13 | requires_authentication: false, 14 | } 15 | 16 | request {} 17 | 18 | response { 19 | /// Information about the homeserver implementation 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub server: Option, 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug, Serialize, Deserialize)] 26 | /// Arbitrary values that identify this implementation. 27 | pub struct Server { 28 | /// Arbitrary name that identifies this implementation. 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub name: Option, 31 | /// Version of this implementation. The version format depends on the implementation. 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub version: Option, 34 | } 35 | -------------------------------------------------------------------------------- /src/v2/get_server_keys.rs: -------------------------------------------------------------------------------- 1 | //! [GET /_matrix/key/v2/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-key-v2-server-keyid) 2 | 3 | use std::{collections::BTreeMap, time::SystemTime}; 4 | 5 | use ruma_api::ruma_api; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | ruma_api! { 9 | metadata { 10 | description: "Gets the homeserver's published signing keys.", 11 | method: GET, 12 | name: "get_server_keys", 13 | path: "/_matrix/key/v2/server", 14 | rate_limited: false, 15 | requires_authentication: false, 16 | } 17 | 18 | request {} 19 | 20 | response { 21 | // Spec is wrong, all fields are required (see 22 | // https://github.com/matrix-org/matrix-doc/issues/2508) 23 | 24 | /// DNS name of the homeserver. 25 | pub server_name: String, 26 | /// Public keys of the homeserver for verifying digital signatures. 27 | pub verify_keys: BTreeMap, 28 | /// Public keys that the homeserver used to use and when it stopped using them. 29 | pub old_verify_keys: BTreeMap, 30 | /// Digital signatures of this object signed using the verify_keys. 31 | pub signatures: BTreeMap>, 32 | /// Timestamp when the keys should be refreshed. This field MUST be ignored in room 33 | /// versions 1, 2, 3, and 4. 34 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 35 | pub valid_until_ts: SystemTime, 36 | } 37 | } 38 | 39 | /// Public key of the homeserver for verifying digital signatures. 40 | #[derive(Clone, Debug, Deserialize, Serialize)] 41 | pub struct VerifyKey { 42 | /// The Unpadded Base64 encoded key. 43 | pub key: String, 44 | } 45 | 46 | /// A key the server used to use, but stopped using. 47 | #[derive(Clone, Debug, Deserialize, Serialize)] 48 | pub struct OldVerifyKey { 49 | /// Timestamp when this key expired. 50 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 51 | pub expired_ts: SystemTime, 52 | /// The Unpadded Base64 encoded key. 53 | pub key: String, 54 | } 55 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! (De)serializable types for the matrix server-server protocol. 2 | 3 | #![warn(missing_docs)] 4 | 5 | use std::collections::BTreeMap; 6 | 7 | use js_int::UInt; 8 | use ruma_events::EventType; 9 | use ruma_identifiers::{EventId, RoomId, UserId}; 10 | use serde::{Deserialize, Serialize}; 11 | use serde_json::Value as JsonValue; 12 | 13 | pub mod unversioned; 14 | pub mod v1; 15 | pub mod v2; 16 | 17 | /// A 'persistent data unit' (event) for room versions 3 and beyond. 18 | #[derive(Deserialize, Serialize)] 19 | pub struct RoomV3Pdu { 20 | /// The room this event belongs to. 21 | pub room_id: RoomId, 22 | /// The user id of the user who sent this event. 23 | pub sender: UserId, 24 | /// The `server_name` of the homeserver that created this event. 25 | pub origin: String, 26 | /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver 27 | /// of when this event was created. 28 | pub origin_server_ts: UInt, 29 | 30 | // TODO: Replace with event content collection from ruma-events once that exists 31 | /// The event's type. 32 | #[serde(rename = "type")] 33 | pub kind: EventType, 34 | /// The event's content. 35 | pub content: JsonValue, 36 | 37 | /// A key that determines which piece of room state the event represents. 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub state_key: Option, 40 | /// Event IDs for the most recent events in the room that the homeserver was 41 | /// aware of when it created this event. 42 | pub prev_events: Vec, 43 | /// The maximum depth of the `prev_events`, plus one. 44 | pub depth: UInt, 45 | /// Event IDs for the authorization events that would allow this event to be 46 | /// in the room. 47 | pub auth_events: Vec, 48 | /// For redaction events, the ID of the event being redacted. 49 | #[serde(skip_serializing_if = "Option::is_none")] 50 | pub redacts: Option, 51 | /// Additional data added by the origin server but not covered by the 52 | /// signatures. 53 | #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")] 54 | pub unsigned: serde_json::Map, 55 | /// Content hashes of the PDU. 56 | pub hashes: EventHash, 57 | /// Signatures for the PDU. 58 | pub signatures: BTreeMap>, 59 | } 60 | 61 | /// Content hashes of a PDU. 62 | #[derive(Deserialize, Serialize)] 63 | pub struct EventHash { 64 | /// The SHA-256 hash. 65 | pub sha256: String, 66 | } 67 | -------------------------------------------------------------------------------- /src/v1/get_public_rooms.rs: -------------------------------------------------------------------------------- 1 | //! [GET /_matrix/federation/v1/publicRooms](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-publicrooms) 2 | 3 | use std::fmt; 4 | 5 | use js_int::UInt; 6 | use ruma_api::ruma_api; 7 | use ruma_identifiers::{RoomAliasId, RoomId}; 8 | use serde::{ 9 | de::{MapAccess, Visitor}, 10 | ser::SerializeStruct, 11 | Deserialize, Deserializer, Serialize, Serializer, 12 | }; 13 | 14 | ruma_api! { 15 | metadata { 16 | description: "Gets all the public rooms for the homeserver.", 17 | method: GET, 18 | name: "get_public_rooms", 19 | path: "/_matrix/federation/v1/publicRooms", 20 | rate_limited: false, 21 | requires_authentication: true, 22 | } 23 | 24 | request { 25 | /// The maximum number of rooms to return. Default is no limit. 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | #[ruma_api(query)] 28 | pub limit: Option, 29 | /// Pagination token from a previous request. 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | #[ruma_api(query)] 32 | pub since: Option, 33 | /// Network to fetch the public room lists from. 34 | #[serde(flatten, skip_serializing_if = "ruma_serde::is_default")] 35 | #[ruma_api(query)] 36 | pub room_network: RoomNetwork, 37 | } 38 | 39 | response { 40 | /// A paginated chunk of public rooms. 41 | pub chunk: Vec, 42 | /// A pagination token for the response. 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub next_batch: Option, 45 | /// A pagination token that allows fetching previous results. 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub prev_batch: Option, 48 | /// An estimate on the total number of public rooms, if the server has an estimate. 49 | pub total_room_count_estimate: Option, 50 | } 51 | } 52 | 53 | /// A chunk of a room list response, describing one room 54 | #[derive(Clone, Debug, Deserialize, Serialize)] 55 | pub struct PublicRoomsChunk { 56 | /// Aliases of the room. 57 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 58 | pub aliases: Vec, 59 | /// The canonical alias of the room, if any. 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub canonical_alias: Option, 62 | /// The name of the room, if any. 63 | #[serde(skip_serializing_if = "Option::is_none")] 64 | pub name: Option, 65 | /// The number of members joined to the room. 66 | pub num_joined_members: UInt, 67 | /// The ID of the room. 68 | pub room_id: RoomId, 69 | /// The topic of the room, if any. 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | pub topic: Option, 72 | /// Whether the room may be viewed by guest users without joining. 73 | pub world_readable: bool, 74 | /// Whether guest users may join the room and participate in it. 75 | /// 76 | /// If they can, they will be subject to ordinary power level rules like any other user. 77 | pub guest_can_join: bool, 78 | /// The URL for the room's avatar, if one is set. 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | pub avatar_url: Option, 81 | } 82 | 83 | /// Information about which networks/protocols from application services on the 84 | /// homeserver from which to request rooms. 85 | #[derive(Clone, Debug, PartialEq, Eq)] 86 | pub enum RoomNetwork { 87 | /// Return rooms from the Matrix network. 88 | Matrix, 89 | /// Return rooms from all the networks/protocols the homeserver knows about. 90 | All, 91 | /// Return rooms from a specific third party network/protocol. 92 | ThirdParty(String), 93 | } 94 | 95 | impl Default for RoomNetwork { 96 | fn default() -> Self { 97 | RoomNetwork::Matrix 98 | } 99 | } 100 | 101 | impl Serialize for RoomNetwork { 102 | fn serialize(&self, serializer: S) -> Result 103 | where 104 | S: Serializer, 105 | { 106 | let mut state; 107 | match self { 108 | Self::Matrix => { 109 | state = serializer.serialize_struct("RoomNetwork", 0)?; 110 | } 111 | Self::All => { 112 | state = serializer.serialize_struct("RoomNetwork", 1)?; 113 | state.serialize_field("include_all_networks", &true)?; 114 | } 115 | Self::ThirdParty(network) => { 116 | state = serializer.serialize_struct("RoomNetwork", 1)?; 117 | state.serialize_field("third_party_instance_id", network)?; 118 | } 119 | } 120 | state.end() 121 | } 122 | } 123 | 124 | impl<'de> Deserialize<'de> for RoomNetwork { 125 | fn deserialize(deserializer: D) -> Result 126 | where 127 | D: Deserializer<'de>, 128 | { 129 | deserializer.deserialize_map(RoomNetworkVisitor) 130 | } 131 | } 132 | 133 | struct RoomNetworkVisitor; 134 | impl<'de> Visitor<'de> for RoomNetworkVisitor { 135 | type Value = RoomNetwork; 136 | 137 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 138 | formatter.write_str("Network selection") 139 | } 140 | 141 | fn visit_map(self, mut access: M) -> Result 142 | where 143 | M: MapAccess<'de>, 144 | { 145 | let mut include_all_networks = false; 146 | let mut third_party_instance_id = None; 147 | while let Some((key, value)) = access.next_entry::()? { 148 | match key.as_str() { 149 | "include_all_networks" => { 150 | include_all_networks = match value.as_bool() { 151 | Some(b) => b, 152 | _ => false, 153 | } 154 | } 155 | "third_party_instance_id" => { 156 | third_party_instance_id = value.as_str().map(|v| v.to_owned()) 157 | } 158 | _ => {} 159 | }; 160 | } 161 | 162 | if include_all_networks { 163 | if third_party_instance_id.is_none() { 164 | Ok(RoomNetwork::All) 165 | } else { 166 | Err(M::Error::custom( 167 | "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive.", 168 | )) 169 | } 170 | } else { 171 | Ok(match third_party_instance_id { 172 | Some(network) => RoomNetwork::ThirdParty(network), 173 | None => RoomNetwork::Matrix, 174 | }) 175 | } 176 | } 177 | } 178 | --------------------------------------------------------------------------------