Add send transaction endpoint

Also refactor PDU types and move them to ruma-events
This commit is contained in:
iinuwa 2020-06-17 04:02:19 -05:00 committed by GitHub
parent 7c934e1b8f
commit bfad8cf1f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1170 additions and 140 deletions

View File

@ -10,6 +10,7 @@ Breaking changes:
struct variants
* This change removes the types `EventMatchCondition`, `RoomMemberCountCondition` and
`SenderNotificationPermissionCondition`
* Add PDU types: `pdu::{Pdu, PduStub}`
Improvements:

View File

@ -147,6 +147,7 @@ pub mod forwarded_room_key;
pub mod fully_read;
pub mod ignored_user_list;
pub mod key;
pub mod pdu;
pub mod presence;
pub mod push_rules;
pub mod receipt;

325
ruma-events/src/pdu.rs Normal file
View File

@ -0,0 +1,325 @@
//! Types for persistent data unit schemas
//!
//! The differences between the `RoomV1Pdu` schema and the `RoomV3Pdu` schema are
//! that the `RoomV1Pdu` takes an `event_id` field (`RoomV3Pdu` does not), and
//! `auth_events` and `prev_events` take `Vec<(EventId, EventHash)> rather than
//! `Vec<EventId>` in `RoomV3Pdu`.
//!
//! The stubbed versions of each PDU type remove the `event_id` field (if any)
//! and the `room_id` field for use in PDU templates.
use std::{collections::BTreeMap, time::SystemTime};
use js_int::UInt;
use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
/// Enum for PDU schemas
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Pdu {
/// PDU for room versions 1 and 2.
RoomV1Pdu(RoomV1Pdu),
/// PDU for room versions 3 and above.
RoomV3Pdu(RoomV3Pdu),
}
/// A 'persistent data unit' (event) for room versions 1 and 2.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomV1Pdu {
/// Event ID for the PDU.
pub event_id: EventId,
/// The room this event belongs to.
pub room_id: RoomId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Encode event type as content enum variant, like event enums do
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub prev_events: Vec<(EventId, EventHash)>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub auth_events: Vec<(EventId, EventHash)>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
/// A 'persistent data unit' (event) for room versions 3 and beyond.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomV3Pdu {
/// The room this event belongs to.
pub room_id: RoomId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Encode event type as content enum variant, like event enums do
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
/// PDU type without event and room IDs.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PduStub {
/// Stub for PDUs of room version 1 and 2.
RoomV1PduStub(RoomV1PduStub),
/// Stub for PDUs of room versions 3 and above.
RoomV3PduStub(RoomV3PduStub),
}
impl PduStub {
/// Helper method to get PDU from a PDU stub.
pub fn into_pdu(self, room_id: RoomId, event_id: EventId) -> Pdu {
match self {
PduStub::RoomV1PduStub(v1_stub) => {
Pdu::RoomV1Pdu(v1_stub.into_v1_pdu(room_id, event_id))
}
PduStub::RoomV3PduStub(v3_stub) => Pdu::RoomV3Pdu(v3_stub.into_v3_pdu(room_id)),
}
}
}
/// Stub for PDUs of room version 1 and 2.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomV1PduStub {
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Encode event type as content enum variant, like event enums do
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<(EventId, EventHash)>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<(EventId, EventHash)>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
impl RoomV1PduStub {
/// Converts a V1 PDU stub into a full V1 PDU.
pub fn into_v1_pdu(self, room_id: RoomId, event_id: EventId) -> RoomV1Pdu {
RoomV1Pdu {
event_id,
room_id,
sender: self.sender,
origin: self.origin,
origin_server_ts: self.origin_server_ts,
kind: self.kind,
content: self.content,
state_key: self.state_key,
prev_events: self.prev_events,
depth: self.depth,
auth_events: self.auth_events,
redacts: self.redacts,
unsigned: self.unsigned,
hashes: self.hashes,
signatures: self.signatures,
}
}
}
/// Stub for PDUs of room versions 3 and above.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomV3PduStub {
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Encode event type as content enum variant, like event enums do
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
impl RoomV3PduStub {
/// Converts a V3 PDU stub into a full V3 PDU.
pub fn into_v3_pdu(self, room_id: RoomId) -> RoomV3Pdu {
RoomV3Pdu {
room_id,
sender: self.sender,
origin: self.origin,
origin_server_ts: self.origin_server_ts,
kind: self.kind,
content: self.content,
state_key: self.state_key,
prev_events: self.prev_events,
depth: self.depth,
auth_events: self.auth_events,
redacts: self.redacts,
unsigned: self.unsigned,
hashes: self.hashes,
signatures: self.signatures,
}
}
}
/// Content hashes of a PDU.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EventHash {
/// The SHA-256 hash.
pub sha256: String,
}

605
ruma-events/tests/pdu.rs Normal file
View File

@ -0,0 +1,605 @@
use std::{
collections::BTreeMap,
convert::TryFrom,
time::{Duration, SystemTime},
};
use matches::assert_matches;
use ruma_events::{
pdu::{EventHash, Pdu, PduStub, RoomV1Pdu, RoomV1PduStub, RoomV3Pdu, RoomV3PduStub},
EventType,
};
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
#[test]
fn serialize_stub_as_v1() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v1_stub = RoomV1PduStub {
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![(
EventId::try_from("$previousevent:matrix.org").unwrap(),
EventHash { sha256: "123567".to_string() },
)],
depth: 2_u32.into(),
auth_events: vec![(
EventId::try_from("$someauthevent:matrix.org").unwrap(),
EventHash { sha256: "21389CFEDABC".to_string() },
)],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned,
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures,
};
let pdu_stub = PduStub::RoomV1PduStub(v1_stub);
let json = json!({
"sender": "@sender:example.com",
"origin": "matrix.org",
"origin_server_ts": 1_592_050_773_658 as usize,
"type": "m.room.power_levels",
"content": {
"testing": 123
},
"state_key": "state",
"prev_events": [
[ "$previousevent:matrix.org", {"sha256": "123567"} ]
],
"depth": 2,
"auth_events": [
["$someauthevent:matrix.org", {"sha256": "21389CFEDABC"}]
],
"redacts": "$9654:matrix.org",
"unsigned": {
"somekey": { "a": 456 } },
"hashes": { "sha256": "1233543bABACDEF" },
"signatures": {
"example.com": { "ed25519:key_version":"86BytesOfSignatureOfTheRedactedEvent" }
}
});
assert_eq!(to_json_value(&pdu_stub).unwrap(), json);
}
#[test]
fn serialize_stub_as_v3() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v3_stub = RoomV3PduStub {
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![EventId::try_from("$previousevent:matrix.org").unwrap()],
depth: 2_u32.into(),
auth_events: vec![EventId::try_from("$someauthevent:matrix.org").unwrap()],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned,
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures,
};
let pdu_stub = PduStub::RoomV3PduStub(v3_stub);
let json = json!({
"sender": "@sender:example.com",
"origin": "matrix.org",
"origin_server_ts": 1_592_050_773_658 as usize,
"type": "m.room.power_levels",
"content": {
"testing": 123
},
"state_key": "state",
"prev_events": [ "$previousevent:matrix.org" ],
"depth": 2,
"auth_events": ["$someauthevent:matrix.org" ],
"redacts": "$9654:matrix.org",
"unsigned": {
"somekey": { "a": 456 } },
"hashes": { "sha256": "1233543bABACDEF" },
"signatures": {
"example.com": { "ed25519:key_version":"86BytesOfSignatureOfTheRedactedEvent" }
}
});
assert_eq!(to_json_value(&pdu_stub).unwrap(), json);
}
#[test]
fn test_deserialize_stub_as_v1() {
let json = json!({
"auth_events": [
[
"$abc123:matrix.org",
{
"sha256": "Base64EncodedSha256HashesShouldBe43BytesLong"
}
]
],
"content": {
"key": "value"
},
"depth": 12,
"event_id": "$a4ecee13e2accdadf56c1025:example.com",
"hashes": {
"sha256": "ThisHashCoversAllFieldsInCaseThisIsRedacted"
},
"origin": "matrix.org",
"origin_server_ts": 1_234_567_890,
"prev_events": [
[
"$abc123:matrix.org",
{
"sha256": "Base64EncodedSha256HashesShouldBe43BytesLong"
}
]
],
"redacts": "$def456:matrix.org",
"room_id": "!abc123:matrix.org",
"sender": "@someone:matrix.org",
"signatures": {
"example.com": {
"ed25519:key_version:": "86BytesOfSignatureOfTheRedactedEvent"
}
},
"state_key": "my_key",
"type": "m.room.message",
"unsigned": {
"key": "value"
}
});
let parsed = from_json_value::<PduStub>(json).unwrap();
match parsed {
PduStub::RoomV1PduStub(v1_stub) => {
assert_eq!(
v1_stub.auth_events.first().unwrap().0,
EventId::try_from("$abc123:matrix.org").unwrap()
);
assert_eq!(
v1_stub.auth_events.first().unwrap().1.sha256,
"Base64EncodedSha256HashesShouldBe43BytesLong"
);
}
PduStub::RoomV3PduStub(_) => panic!("Matched V3 stub"),
}
}
#[test]
fn deserialize_stub_as_v3() {
let json = json!({
"auth_events": [
"$abc123:matrix.org"
],
"content": {
"key": "value"
},
"depth": 12,
"event_id": "$a4ecee13e2accdadf56c1025:example.com",
"hashes": {
"sha256": "ThisHashCoversAllFieldsInCaseThisIsRedacted"
},
"origin": "matrix.org",
"origin_server_ts": 1_234_567_890,
"prev_events": [
"$abc123:matrix.org"
],
"redacts": "$def456:matrix.org",
"room_id": "!abc123:matrix.org",
"sender": "@someone:matrix.org",
"signatures": {
"example.com": {
"ed25519:key_version:": "86BytesOfSignatureOfTheRedactedEvent"
}
},
"state_key": "my_key",
"type": "m.room.message",
"unsigned": {
"key": "value"
}
});
let parsed = from_json_value::<PduStub>(json).unwrap();
match parsed {
PduStub::RoomV1PduStub(_) => panic!("Matched V1 stub"),
PduStub::RoomV3PduStub(v3_stub) => {
assert_eq!(
v3_stub.auth_events.first().unwrap(),
&EventId::try_from("$abc123:matrix.org").unwrap()
);
}
}
}
#[test]
fn serialize_pdu_as_v1() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v1_pdu = RoomV1Pdu {
room_id: RoomId::try_from("!n8f893n9:example.com").unwrap(),
event_id: EventId::try_from("$somejoinevent:matrix.org").unwrap(),
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![(
EventId::try_from("$previousevent:matrix.org").unwrap(),
EventHash { sha256: "123567".to_string() },
)],
depth: 2_u32.into(),
auth_events: vec![(
EventId::try_from("$someauthevent:matrix.org").unwrap(),
EventHash { sha256: "21389CFEDABC".to_string() },
)],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned,
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures,
};
let pdu = Pdu::RoomV1Pdu(v1_pdu);
let json = json!({
"room_id": "!n8f893n9:example.com",
"event_id": "$somejoinevent:matrix.org",
"sender": "@sender:example.com",
"origin": "matrix.org",
"origin_server_ts": 1_592_050_773_658 as usize,
"type": "m.room.power_levels",
"content": {
"testing": 123
},
"state_key": "state",
"prev_events": [
[ "$previousevent:matrix.org", {"sha256": "123567"} ]
],
"depth": 2,
"auth_events": [
["$someauthevent:matrix.org", {"sha256": "21389CFEDABC"}]
],
"redacts": "$9654:matrix.org",
"unsigned": {
"somekey": { "a": 456 } },
"hashes": { "sha256": "1233543bABACDEF" },
"signatures": {
"example.com": { "ed25519:key_version":"86BytesOfSignatureOfTheRedactedEvent" }
}
});
assert_eq!(to_json_value(&pdu).unwrap(), json);
}
#[test]
fn serialize_pdu_as_v3() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v3_pdu = RoomV3Pdu {
room_id: RoomId::try_from("!n8f893n9:example.com").unwrap(),
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![EventId::try_from("$previousevent:matrix.org").unwrap()],
depth: 2_u32.into(),
auth_events: vec![EventId::try_from("$someauthevent:matrix.org").unwrap()],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned,
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures,
};
let pdu_stub = Pdu::RoomV3Pdu(v3_pdu);
let json = json!({
"room_id": "!n8f893n9:example.com",
"sender": "@sender:example.com",
"origin": "matrix.org",
"origin_server_ts": 1_592_050_773_658 as usize,
"type": "m.room.power_levels",
"content": {
"testing": 123
},
"state_key": "state",
"prev_events": [ "$previousevent:matrix.org" ],
"depth": 2,
"auth_events": ["$someauthevent:matrix.org" ],
"redacts": "$9654:matrix.org",
"unsigned": {
"somekey": { "a": 456 } },
"hashes": { "sha256": "1233543bABACDEF" },
"signatures": {
"example.com": { "ed25519:key_version":"86BytesOfSignatureOfTheRedactedEvent" }
}
});
assert_eq!(to_json_value(&pdu_stub).unwrap(), json);
}
#[test]
fn test_deserialize_pdu_as_v1() {
let json = json!({
"room_id": "!n8f893n9:example.com",
"event_id": "$somejoinevent:matrix.org",
"auth_events": [
[
"$abc123:matrix.org",
{
"sha256": "Base64EncodedSha256HashesShouldBe43BytesLong"
}
]
],
"content": {
"key": "value"
},
"depth": 12,
"event_id": "$a4ecee13e2accdadf56c1025:example.com",
"hashes": {
"sha256": "ThisHashCoversAllFieldsInCaseThisIsRedacted"
},
"origin": "matrix.org",
"origin_server_ts": 1_234_567_890,
"prev_events": [
[
"$abc123:matrix.org",
{
"sha256": "Base64EncodedSha256HashesShouldBe43BytesLong"
}
]
],
"redacts": "$def456:matrix.org",
"room_id": "!abc123:matrix.org",
"sender": "@someone:matrix.org",
"signatures": {
"example.com": {
"ed25519:key_version:": "86BytesOfSignatureOfTheRedactedEvent"
}
},
"state_key": "my_key",
"type": "m.room.message",
"unsigned": {
"key": "value"
}
});
let parsed = from_json_value::<Pdu>(json).unwrap();
match parsed {
Pdu::RoomV1Pdu(v1_pdu) => {
assert_eq!(
v1_pdu.auth_events.first().unwrap().0,
EventId::try_from("$abc123:matrix.org").unwrap()
);
assert_eq!(
v1_pdu.auth_events.first().unwrap().1.sha256,
"Base64EncodedSha256HashesShouldBe43BytesLong"
);
}
Pdu::RoomV3Pdu(_) => panic!("Matched V3 PDU"),
}
}
#[test]
fn deserialize_pdu_as_v3() {
let json = json!({
"room_id": "!n8f893n9:example.com",
"auth_events": [
"$abc123:matrix.org"
],
"content": {
"key": "value"
},
"depth": 12,
"event_id": "$a4ecee13e2accdadf56c1025:example.com",
"hashes": {
"sha256": "ThisHashCoversAllFieldsInCaseThisIsRedacted"
},
"origin": "matrix.org",
"origin_server_ts": 1_234_567_890,
"prev_events": [
"$abc123:matrix.org"
],
"redacts": "$def456:matrix.org",
"room_id": "!abc123:matrix.org",
"sender": "@someone:matrix.org",
"signatures": {
"example.com": {
"ed25519:key_version:": "86BytesOfSignatureOfTheRedactedEvent"
}
},
"state_key": "my_key",
"type": "m.room.message",
"unsigned": {
"key": "value"
}
});
let parsed = from_json_value::<Pdu>(json).unwrap();
match parsed {
Pdu::RoomV1Pdu(_) => panic!("Matched V1 PDU"),
Pdu::RoomV3Pdu(v3_pdu) => {
assert_eq!(
v3_pdu.auth_events.first().unwrap(),
&EventId::try_from("$abc123:matrix.org").unwrap()
);
}
}
}
#[test]
fn convert_v1_stub_to_pdu() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v1_stub = RoomV1PduStub {
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![(
EventId::try_from("$previousevent:matrix.org").unwrap(),
EventHash { sha256: "123567".to_string() },
)],
depth: 2_u32.into(),
auth_events: vec![(
EventId::try_from("$someauthevent:matrix.org").unwrap(),
EventHash { sha256: "21389CFEDABC".to_string() },
)],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned: (&unsigned).clone(),
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures: (&signatures).clone(),
};
assert_matches!(
v1_stub.into_v1_pdu(
RoomId::try_from("!n8f893n9:example.com").unwrap(),
EventId::try_from("$somejoinevent:matrix.org").unwrap()
),
RoomV1Pdu {
room_id,
event_id,
sender,
origin,
origin_server_ts,
kind,
content,
state_key,
prev_events,
depth,
auth_events,
redacts,
unsigned,
hashes: EventHash { sha256 },
signatures,
} if room_id == RoomId::try_from("!n8f893n9:example.com").unwrap()
&& event_id == EventId::try_from("$somejoinevent:matrix.org").unwrap()
&& sender == UserId::try_from("@sender:example.com").unwrap()
&& origin == "matrix.org"
&& origin_server_ts == SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658)
&& kind == EventType::RoomPowerLevels
&& content == json!({"testing": 123})
&& state_key == Some("state".to_string())
&& prev_events[0].0 == EventId::try_from("$previousevent:matrix.org").unwrap()
&& prev_events[0].1.sha256 == "123567"
&& depth == 2_u32.into()
&& auth_events.first().unwrap().0 == EventId::try_from("$someauthevent:matrix.org").unwrap()
&& auth_events.first().unwrap().1.sha256 == "21389CFEDABC"
&& redacts == Some(EventId::try_from("$9654:matrix.org").unwrap())
&& unsigned == (&unsigned).clone()
&& sha256 == "1233543bABACDEF"
&& signatures == (&signatures).clone()
);
}
#[test]
fn convert_v3_stub_to_pdu() {
let mut signatures = BTreeMap::new();
let mut inner_signature = BTreeMap::new();
inner_signature.insert(
"ed25519:key_version".to_string(),
"86BytesOfSignatureOfTheRedactedEvent".to_string(),
);
signatures.insert("example.com".to_string(), inner_signature);
let mut unsigned = BTreeMap::new();
unsigned.insert("somekey".to_string(), json!({"a": 456}));
let v3_stub = RoomV3PduStub {
sender: UserId::try_from("@sender:example.com").unwrap(),
origin: "matrix.org".to_string(),
origin_server_ts: SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658),
kind: EventType::RoomPowerLevels,
content: json!({"testing": 123}),
state_key: Some("state".to_string()),
prev_events: vec![EventId::try_from("$previousevent:matrix.org").unwrap()],
depth: 2_u32.into(),
auth_events: vec![EventId::try_from("$someauthevent:matrix.org").unwrap()],
redacts: Some(EventId::try_from("$9654:matrix.org").unwrap()),
unsigned: (&unsigned).clone(),
hashes: EventHash { sha256: "1233543bABACDEF".to_string() },
signatures: (&signatures).clone(),
};
assert_matches!(
v3_stub.into_v3_pdu(RoomId::try_from("!n8f893n9:example.com").unwrap()),
RoomV3Pdu {
room_id,
sender,
origin,
origin_server_ts,
kind,
content,
state_key,
prev_events,
depth,
auth_events,
redacts,
unsigned,
hashes: EventHash { sha256 },
signatures,
} if room_id == RoomId::try_from("!n8f893n9:example.com").unwrap()
&& sender == UserId::try_from("@sender:example.com").unwrap()
&& origin == "matrix.org"
&& origin_server_ts == SystemTime::UNIX_EPOCH + Duration::from_millis(1_592_050_773_658)
&& kind == EventType::RoomPowerLevels
&& content == json!({"testing": 123})
&& state_key == Some("state".to_string())
&& prev_events == vec![EventId::try_from("$previousevent:matrix.org").unwrap()]
&& depth == 2_u32.into()
&& auth_events == vec![EventId::try_from("$someauthevent:matrix.org").unwrap()]
&& redacts == Some(EventId::try_from("$9654:matrix.org").unwrap())
&& unsigned == (&unsigned).clone()
&& sha256 == "1233543bABACDEF"
&& signatures == (&signatures).clone()
);
}

View File

@ -1,5 +1,9 @@
# [unreleased]
Breaking Changes:
* Replace `RoomV3Pdu` with `ruma_events::pdu::{Pdu, PduStub}`.
Improvements:
* Add endpoints:
@ -15,6 +19,7 @@ Improvements:
create_join_event_template::v1
},
query::get_room_information::v1,
transactions::send_transaction_message::v1,
version::get_server_version::v1
```

View File

@ -2,69 +2,10 @@
#![warn(missing_docs)]
use std::{collections::BTreeMap, time::SystemTime};
use ::serde::{Deserialize, Serialize};
use js_int::UInt;
use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::Value as JsonValue;
mod serde;
pub mod directory;
pub mod discovery;
pub mod membership;
pub mod query;
/// A 'persistent data unit' (event) for room versions 3 and beyond.
#[derive(Deserialize, Serialize)]
pub struct RoomV3Pdu {
/// The room this event belongs to.
pub room_id: RoomId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Replace with event content collection from ruma-events once that exists
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
/// Content hashes of a PDU.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EventHash {
/// The SHA-256 hash.
pub sha256: String,
}
pub mod transactions;

View File

@ -2,11 +2,9 @@
pub mod v1;
use ruma_events::EventJson;
use ruma_events::{pdu::Pdu, EventJson};
use serde::{Deserialize, Serialize};
use crate::RoomV3Pdu;
/// Full state of the room.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomState {
@ -14,7 +12,7 @@ pub struct RoomState {
pub origin: String,
/// The full set of authorization events that make up the state of the room,
/// and their authorization events, recursively.
pub auth_chain: Vec<EventJson<RoomV3Pdu>>,
pub auth_chain: Vec<EventJson<Pdu>>,
/// The room state.
pub state: Vec<EventJson<RoomV3Pdu>>,
pub state: Vec<EventJson<Pdu>>,
}

View File

@ -1,15 +1,10 @@
//! [PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}](https://matrix.org/docs/spec/server_server/r0.1.3#put-matrix-federation-v1-send-join-roomid-eventid)
use std::{collections::BTreeMap, time::SystemTime};
use js_int::UInt;
use ruma_api::ruma_api;
use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::Value as JsonValue;
use ruma_events::pdu::PduStub;
use ruma_identifiers::{EventId, RoomId};
use super::RoomState;
use crate::{EventHash, RoomV3Pdu};
ruma_api! {
metadata {
@ -29,44 +24,9 @@ ruma_api! {
#[ruma_api(path)]
pub event_id: EventId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Replace with event content collection from ruma-events once that exists
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
/// PDU type without event and room IDs.
#[ruma_api(body)]
pub pdu_stub: PduStub,
}
response {
@ -76,29 +36,3 @@ ruma_api! {
pub room_state: RoomState,
}
}
impl Request {
/// Helper method to get event ID and PDU (with room ID) from the request
/// parameters.
pub fn into_id_and_v3_pdu(self) -> (EventId, RoomV3Pdu) {
(
self.event_id,
RoomV3Pdu {
room_id: self.room_id,
sender: self.sender,
origin: self.origin,
origin_server_ts: self.origin_server_ts,
kind: self.kind,
content: self.content,
state_key: self.state_key,
prev_events: self.prev_events,
depth: self.depth,
auth_events: self.auth_events,
redacts: self.redacts,
unsigned: self.unsigned,
hashes: self.hashes,
signatures: self.signatures,
},
)
}
}

View File

@ -2,11 +2,9 @@
use js_int::UInt;
use ruma_api::ruma_api;
use ruma_events::EventJson;
use ruma_events::{pdu::Pdu, EventJson};
use ruma_identifiers::{RoomId, UserId};
use crate::RoomV3Pdu;
ruma_api! {
metadata {
description: "Send a request for a join event template to a resident server.",
@ -34,6 +32,6 @@ ruma_api! {
/// The version of the room where the server is trying to join.
pub room_version: Option<UInt>,
/// An unsigned template event.
pub event: EventJson<RoomV3Pdu>,
pub event: EventJson<Pdu>,
}
}

View File

@ -1,3 +1,4 @@
//! Modules for custom serde de/-serialization implementations.
pub mod pdu_process_response;
pub mod room_state;

View File

@ -0,0 +1,156 @@
use std::{collections::BTreeMap, fmt};
use ruma_identifiers::EventId;
use serde::{
de::{Deserializer, MapAccess, Visitor},
ser::{SerializeMap, Serializer},
Deserialize, Serialize,
};
pub fn serialize<S>(
response: &BTreeMap<EventId, Result<(), String>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(response.len()))?;
for (key, value) in response {
let wrapped_error = WrappedError {
error: match value {
Ok(_) => None,
Err(error) => Some(error.clone()),
},
};
map.serialize_entry(&key, &wrapped_error)?;
}
map.end()
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<BTreeMap<EventId, Result<(), String>>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(PduProcessResponseVisitor)
}
#[derive(Deserialize, Serialize)]
struct WrappedError {
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
struct PduProcessResponseVisitor;
impl<'de> Visitor<'de> for PduProcessResponseVisitor {
type Value = BTreeMap<EventId, Result<(), String>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A map of EventIds to a map of optional errors")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut map = BTreeMap::new();
while let Some((key, value)) = access.next_entry::<EventId, WrappedError>()? {
let v = match value.error {
None => Ok(()),
Some(error) => Err(error),
};
map.insert(key, v);
}
Ok(map)
}
}
#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, convert::TryFrom};
use ruma_identifiers::EventId;
use serde_json::{json, value::Serializer as JsonSerializer};
use super::{deserialize, serialize};
#[test]
fn serialize_error() {
let mut response: BTreeMap<EventId, Result<(), String>> = BTreeMap::new();
response.insert(
EventId::try_from("$someevent:matrix.org").unwrap(),
Err("Some processing error.".into()),
);
let serialized = serialize(&response, JsonSerializer).unwrap();
let json = json!({
"$someevent:matrix.org": { "error": "Some processing error." }
});
assert_eq!(serialized, json);
}
#[test]
fn serialize_ok() {
let mut response: BTreeMap<EventId, Result<(), String>> = BTreeMap::new();
response.insert(EventId::try_from("$someevent:matrix.org").unwrap(), Ok(()));
let serialized = serialize(&response, serde_json::value::Serializer).unwrap();
let json = json!({
"$someevent:matrix.org": {}
});
assert_eq!(serialized, json);
}
#[test]
fn deserialize_error() {
let json = json!({
"$someevent:matrix.org": { "error": "Some processing error." }
});
let response = deserialize(json).unwrap();
let event_id = EventId::try_from("$someevent:matrix.org").unwrap();
let event_response = response.get(&event_id).unwrap().clone().unwrap_err();
assert_eq!(event_response, "Some processing error.");
}
#[test]
fn deserialize_null_error_is_ok() {
let json = json!({
"$someevent:matrix.org": { "error": null }
});
let response = deserialize(json).unwrap();
let event_id = EventId::try_from("$someevent:matrix.org").unwrap();
assert!(response.get(&event_id).unwrap().is_ok());
}
#[test]
fn desieralize_empty_error_is_err() {
let json = json!({
"$someevent:matrix.org": { "error": "" }
});
let response = deserialize(json).unwrap();
let event_id = EventId::try_from("$someevent:matrix.org").unwrap();
let event_response = response.get(&event_id).unwrap().clone().unwrap_err();
assert_eq!(event_response, "");
}
#[test]
fn deserialize_ok() {
let json = json!({
"$someevent:matrix.org": {}
});
let response = deserialize(json).unwrap();
assert!(response
.get(&EventId::try_from("$someevent:matrix.org").unwrap())
.unwrap()
.is_ok());
}
}

View File

@ -0,0 +1,3 @@
//! Endpoints for exchanging transaction messages between homeservers.
pub mod send_transaction_message;

View File

@ -0,0 +1,3 @@
//! Endpoint to send live activity messages to another server.
pub mod v1;

View File

@ -0,0 +1,59 @@
//! [PUT /_matrix/federation/v1/send/{txnId}](https://matrix.org/docs/spec/server_server/r0.1.3#put-matrix-federation-v1-send-txnid)
use std::{collections::BTreeMap, time::SystemTime};
use ruma_api::ruma_api;
use ruma_events::pdu::Pdu;
use ruma_identifiers::EventId;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
ruma_api! {
metadata {
description: "Send transaction messages to another server",
name: "send_transaction_message",
method: PUT,
path: "/_matrix/federation/v1/send/:transaction_id",
rate_limited: false,
requires_authentication: true,
}
request {
/// A transaction ID unique between sending and receiving homeservers.
#[ruma_api(path)]
pub transaction_id: String,
/// The server_name of the homeserver sending this transaction.
pub origin: String,
/// POSIX timestamp in milliseconds on the originating homeserver when this transaction started.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
/// List of persistent updates to rooms.
///
/// Must not be more than 50 items.
pub pdus: Vec<Pdu>,
/// List of ephemeral messages.
///
/// Must not be more than 100 items.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub edus: Vec<Edu>,
}
response {
/// Map of event IDs and response for each PDU given in the request.
#[serde(with = "crate::serde::pdu_process_response")]
pub pdus: BTreeMap<EventId, Result<(), String>>,
}
}
/// Type for passing ephemeral data to homeservers.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Edu {
/// Type of the ephemeral message.
pub edu_type: String,
/// Content of ephemeral message
pub content: JsonValue,
}