feat: save pdus
PDUs are saved in a pduid -> pdus map. roomid -> pduleaves keeps track of the leaves of the event graph and eventid -> pduid maps event ids to pdus.
This commit is contained in:
parent
22cca206ba
commit
fa3226898c
8 changed files with 309 additions and 54 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -496,7 +496,9 @@ dependencies = [
|
|||
"ruma-api",
|
||||
"ruma-client-api",
|
||||
"ruma-events",
|
||||
"ruma-federation-api",
|
||||
"ruma-identifiers",
|
||||
"ruma-signatures",
|
||||
"serde_json",
|
||||
"sled",
|
||||
]
|
||||
|
@ -875,6 +877,19 @@ dependencies = [
|
|||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2a73a23c4d9243be91e101e1942f4d9cd913ef5156d756bafdfe2409ee23d72"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-events",
|
||||
"ruma-identifiers",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-identifiers"
|
||||
version = "0.14.1"
|
||||
|
@ -886,6 +901,17 @@ dependencies = [
|
|||
"url 2.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/ruma/ruma-signatures.git#a08fc01c0bce63f913e1b4b1a673169d59738b63"
|
||||
dependencies = [
|
||||
"base64 0.11.0",
|
||||
"ring",
|
||||
"serde_json",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.7.0"
|
||||
|
|
|
@ -19,4 +19,5 @@ ruma-api = "0.15.0"
|
|||
ruma-events = "0.18.0"
|
||||
js_int = "0.1.3"
|
||||
serde_json = "1.0.50"
|
||||
ruma-signatures = "0.5.0"
|
||||
ruma-signatures = { git = "https://github.com/ruma/ruma-signatures.git" }
|
||||
ruma-federation-api = "0.0.1"
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
[global]
|
||||
address = "0.0.0.0"
|
||||
port = 14004
|
||||
|
||||
#[global.tls]
|
||||
#certs = "/etc/ssl/certs/ssl-cert-snakeoil.pem"
|
||||
#key = "/etc/ssl/private/ssl-cert-snakeoil.key"
|
||||
#certs = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/fullchain.pem"
|
||||
#key = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/privkey.pem"
|
||||
|
|
154
src/data.rs
154
src/data.rs
|
@ -1,7 +1,9 @@
|
|||
use crate::{utils, Database};
|
||||
use log::debug;
|
||||
use ruma_events::collections::all::Event;
|
||||
use ruma_federation_api::RoomV3Pdu;
|
||||
use ruma_identifiers::{EventId, RoomId, UserId};
|
||||
use std::convert::TryInto;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
pub struct Data {
|
||||
hostname: String,
|
||||
|
@ -99,14 +101,152 @@ impl Data {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
/// Create a new room event.
|
||||
pub fn event_add(&self, room_id: &RoomId, event_id: &EventId, event: &Event) {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.extend_from_slice(event_id.to_string().as_bytes());
|
||||
pub fn pdu_get(&self, event_id: &EventId) -> Option<RoomV3Pdu> {
|
||||
self.db
|
||||
.roomid_eventid_event
|
||||
.insert(&key, &*serde_json::to_string(event).unwrap())
|
||||
.eventid_pduid
|
||||
.get(event_id.to_string().as_bytes())
|
||||
.unwrap()
|
||||
.map(|pdu_id| {
|
||||
serde_json::from_slice(
|
||||
&self
|
||||
.db
|
||||
.pduid_pdus
|
||||
.get(pdu_id)
|
||||
.unwrap()
|
||||
.expect("eventid_pduid in db is valid"),
|
||||
)
|
||||
.expect("pdu is valid")
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Make sure this isn't called twice in parallel
|
||||
pub fn pdu_leaves_replace(&self, room_id: &RoomId, event_id: &EventId) -> Vec<EventId> {
|
||||
let event_ids = self
|
||||
.db
|
||||
.roomid_pduleaves
|
||||
.get_iter(room_id.to_string().as_bytes())
|
||||
.values()
|
||||
.map(|pdu_id| {
|
||||
EventId::try_from(&*utils::string_from_bytes(&pdu_id.unwrap()))
|
||||
.expect("pdu leaves are valid event ids")
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.db
|
||||
.roomid_pduleaves
|
||||
.clear(room_id.to_string().as_bytes());
|
||||
|
||||
self.db.roomid_pduleaves.add(
|
||||
&room_id.to_string().as_bytes(),
|
||||
(*event_id.to_string()).into(),
|
||||
);
|
||||
|
||||
event_ids
|
||||
}
|
||||
|
||||
/// Add a persisted data unit from this homeserver
|
||||
pub fn pdu_append(&self, event_id: &EventId, room_id: &RoomId, event: Event) {
|
||||
// prev_events are the leaves of the current graph. This method removes all leaves from the
|
||||
// room and replaces them with our event
|
||||
let prev_events = self.pdu_leaves_replace(room_id, event_id);
|
||||
|
||||
// Our depth is the maximum depth of prev_events + 1
|
||||
let depth = prev_events
|
||||
.iter()
|
||||
.map(|event_id| {
|
||||
self.pdu_get(event_id)
|
||||
.expect("pdu in prev_events is valid")
|
||||
.depth
|
||||
.into()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0_u64)
|
||||
+ 1;
|
||||
|
||||
let mut pdu_value = serde_json::to_value(&event).expect("message event can be serialized");
|
||||
let pdu = pdu_value.as_object_mut().unwrap();
|
||||
|
||||
pdu.insert(
|
||||
"prev_events".to_owned(),
|
||||
prev_events
|
||||
.iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
pdu.insert("origin".to_owned(), self.hostname().into());
|
||||
pdu.insert("depth".to_owned(), depth.into());
|
||||
pdu.insert("auth_events".to_owned(), vec!["$auth_eventid"].into()); // TODO
|
||||
pdu.insert(
|
||||
"hashes".to_owned(),
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".into(),
|
||||
); // TODO
|
||||
pdu.insert("signatures".to_owned(), "signature".into()); // TODO
|
||||
|
||||
// The new value will need a new index. We store the last used index in 'n' + id
|
||||
let mut count_key: Vec<u8> = vec![b'n'];
|
||||
count_key.extend_from_slice(&room_id.to_string().as_bytes());
|
||||
|
||||
// Increment the last index and use that
|
||||
let index = utils::u64_from_bytes(
|
||||
&self
|
||||
.db
|
||||
.pduid_pdus
|
||||
.update_and_fetch(&count_key, utils::increment)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut pdu_id = vec![b'd'];
|
||||
pdu_id.extend_from_slice(room_id.to_string().as_bytes());
|
||||
|
||||
pdu_id.push(b'#'); // Add delimiter so we don't find rooms starting with the same id
|
||||
pdu_id.extend_from_slice(index.to_string().as_bytes());
|
||||
|
||||
self.db
|
||||
.pduid_pdus
|
||||
.insert(&pdu_id, dbg!(&*serde_json::to_string(&pdu).unwrap()))
|
||||
.unwrap();
|
||||
|
||||
self.db
|
||||
.eventid_pduid
|
||||
.insert(event_id.to_string(), pdu_id.clone())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Returns a vector of all PDUs.
|
||||
pub fn pdus_all(&self) -> Vec<RoomV3Pdu> {
|
||||
self.pdus_since(
|
||||
self.db
|
||||
.eventid_pduid
|
||||
.iter()
|
||||
.values()
|
||||
.next()
|
||||
.unwrap()
|
||||
.map(|key| utils::string_from_bytes(&key))
|
||||
.expect("there should be at least one pdu"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a vector of all events that happened after the event with id `since`.
|
||||
pub fn pdus_since(&self, since: String) -> Vec<RoomV3Pdu> {
|
||||
let mut pdus = Vec::new();
|
||||
|
||||
if let Some(room_id) = since.rsplitn(2, '#').nth(1) {
|
||||
let mut current = since.clone();
|
||||
|
||||
while let Some((key, value)) = self.db.pduid_pdus.get_gt(current).unwrap() {
|
||||
if key.starts_with(&room_id.to_string().as_bytes()) {
|
||||
current = utils::string_from_bytes(&key);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
pdus.push(serde_json::from_slice(&value).expect("pdu is valid"));
|
||||
}
|
||||
} else {
|
||||
debug!("event at `since` not found");
|
||||
}
|
||||
pdus
|
||||
}
|
||||
|
||||
pub fn debug(&self) {
|
||||
|
|
|
@ -15,11 +15,17 @@ impl MultiValue {
|
|||
// Data keys start with d
|
||||
let mut key = vec![b'd'];
|
||||
key.extend_from_slice(id.as_ref());
|
||||
key.push(0xff); // Add delimiter so we don't find usernames starting with the same id
|
||||
key.push(0xff); // Add delimiter so we don't find keys starting with the same id
|
||||
|
||||
self.0.scan_prefix(key)
|
||||
}
|
||||
|
||||
pub fn clear(&self, id: &[u8]) {
|
||||
for key in self.get_iter(id).keys() {
|
||||
self.0.remove(key.unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Add another value to the id.
|
||||
pub fn add(&self, id: &[u8], value: IVec) {
|
||||
// The new value will need a new index. We store the last used index in 'n' + id
|
||||
|
@ -48,7 +54,9 @@ pub struct Database {
|
|||
pub userid_deviceids: MultiValue,
|
||||
pub deviceid_token: sled::Tree,
|
||||
pub token_userid: sled::Tree,
|
||||
pub roomid_eventid_event: sled::Tree,
|
||||
pub pduid_pdus: sled::Tree,
|
||||
pub roomid_pduleaves: MultiValue,
|
||||
pub eventid_pduid: sled::Tree,
|
||||
_db: sled::Db,
|
||||
}
|
||||
|
||||
|
@ -67,7 +75,9 @@ impl Database {
|
|||
userid_deviceids: MultiValue(db.open_tree("userid_deviceids").unwrap()),
|
||||
deviceid_token: db.open_tree("deviceid_token").unwrap(),
|
||||
token_userid: db.open_tree("token_userid").unwrap(),
|
||||
roomid_eventid_event: db.open_tree("roomid_eventid_event").unwrap(),
|
||||
pduid_pdus: db.open_tree("pduid_pdus").unwrap(),
|
||||
roomid_pduleaves: MultiValue(db.open_tree("roomid_pduleaves").unwrap()),
|
||||
eventid_pduid: db.open_tree("eventid_pduid").unwrap(),
|
||||
_db: db,
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +91,7 @@ impl Database {
|
|||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("# UserId -> DeviceIds:");
|
||||
println!("\n# UserId -> DeviceIds:");
|
||||
for (k, v) in self.userid_deviceids.iter_all().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
|
@ -89,7 +99,7 @@ impl Database {
|
|||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("# DeviceId -> Token:");
|
||||
println!("\n# DeviceId -> Token:");
|
||||
for (k, v) in self.deviceid_token.iter().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
|
@ -97,7 +107,7 @@ impl Database {
|
|||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("# Token -> UserId:");
|
||||
println!("\n# Token -> UserId:");
|
||||
for (k, v) in self.token_userid.iter().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
|
@ -105,8 +115,24 @@ impl Database {
|
|||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("# RoomId + EventId -> Event:");
|
||||
for (k, v) in self.roomid_eventid_event.iter().map(|r| r.unwrap()) {
|
||||
println!("\n# RoomId -> PDU leaves:");
|
||||
for (k, v) in self.roomid_pduleaves.iter_all().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
String::from_utf8_lossy(&k),
|
||||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("\n# PDU Id -> PDUs:");
|
||||
for (k, v) in self.pduid_pdus.iter().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
String::from_utf8_lossy(&k),
|
||||
String::from_utf8_lossy(&v),
|
||||
);
|
||||
}
|
||||
println!("\n# EventId -> PDU Id:");
|
||||
for (k, v) in self.eventid_pduid.iter().map(|r| r.unwrap()) {
|
||||
println!(
|
||||
"{} -> {}",
|
||||
String::from_utf8_lossy(&k),
|
||||
|
|
89
src/main.rs
89
src/main.rs
|
@ -8,12 +8,12 @@ pub use data::Data;
|
|||
pub use database::Database;
|
||||
|
||||
use log::debug;
|
||||
use rocket::{get, post, put, routes, State};
|
||||
use rocket::{get, options, post, put, routes, State};
|
||||
use ruma_client_api::{
|
||||
error::{Error, ErrorKind},
|
||||
r0::{
|
||||
account::register, alias::get_alias, membership::join_room_by_id,
|
||||
message::create_message_event, session::login,
|
||||
message::create_message_event, session::login, sync::sync_events,
|
||||
},
|
||||
unversioned::get_supported_versions,
|
||||
};
|
||||
|
@ -24,20 +24,13 @@ use serde_json::map::Map;
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[get("/_matrix/client/versions")]
|
||||
fn get_supported_versions_route() -> MatrixResult<get_supported_versions::Response> {
|
||||
MatrixResult(Ok(get_supported_versions::Response {
|
||||
versions: vec![
|
||||
"r0.0.1".to_owned(),
|
||||
"r0.1.0".to_owned(),
|
||||
"r0.2.0".to_owned(),
|
||||
"r0.3.0".to_owned(),
|
||||
"r0.4.0".to_owned(),
|
||||
"r0.5.0".to_owned(),
|
||||
"r0.6.0".to_owned(),
|
||||
],
|
||||
versions: vec!["r0.6.0".to_owned()],
|
||||
unstable_features: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
|
@ -219,9 +212,9 @@ fn create_message_event_route(
|
|||
body: Ruma<create_message_event::Request>,
|
||||
) -> MatrixResult<create_message_event::Response> {
|
||||
// Construct event
|
||||
let event = Event::RoomMessage(MessageEvent {
|
||||
let mut event = Event::RoomMessage(MessageEvent {
|
||||
content: body.data.clone().into_result().unwrap(),
|
||||
event_id: event_id.clone(),
|
||||
event_id: EventId::try_from("$thiswillbefilledinlater").unwrap(),
|
||||
origin_server_ts: utils::millis_since_unix_epoch(),
|
||||
room_id: Some(body.room_id.clone()),
|
||||
sender: body.user_id.clone().expect("user is authenticated"),
|
||||
|
@ -229,18 +222,78 @@ fn create_message_event_route(
|
|||
});
|
||||
|
||||
// Generate event id
|
||||
dbg!(ruma_signatures::reference_hash(event));
|
||||
let event_id = EventId::try_from(&*format!(
|
||||
"${}",
|
||||
ruma_signatures::reference_hash(&serde_json::to_value(&event).unwrap())
|
||||
.expect("ruma can calculate reference hashes")
|
||||
))
|
||||
.expect("ruma's reference hashes are correct");
|
||||
|
||||
let event_id = EventId::try_from("$TODOrandomeventid:localhost").unwrap();
|
||||
data.event_add(&body.room_id, &event_id, &event);
|
||||
// Insert event id
|
||||
if let Event::RoomMessage(message) = &mut event {
|
||||
message.event_id = event_id.clone();
|
||||
}
|
||||
|
||||
// Add PDU to the graph
|
||||
data.pdu_append(&event_id, &body.room_id, event);
|
||||
|
||||
MatrixResult(Ok(create_message_event::Response { event_id }))
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/sync")]
|
||||
fn sync_route(data: State<Data>) -> MatrixResult<sync_events::Response> {
|
||||
let pdus = data.pdus_all();
|
||||
let mut joined_rooms = HashMap::new();
|
||||
joined_rooms.insert(
|
||||
"!roomid:localhost".try_into().unwrap(),
|
||||
sync_events::JoinedRoom {
|
||||
account_data: sync_events::AccountData { events: Vec::new() },
|
||||
summary: sync_events::RoomSummary {
|
||||
heroes: Vec::new(),
|
||||
joined_member_count: None,
|
||||
invited_member_count: None,
|
||||
},
|
||||
unread_notifications: sync_events::UnreadNotificationsCount {
|
||||
highlight_count: None,
|
||||
notification_count: None,
|
||||
},
|
||||
timeline: sync_events::Timeline {
|
||||
limited: None,
|
||||
prev_batch: None,
|
||||
events: todo!(),
|
||||
},
|
||||
state: sync_events::State { events: Vec::new() },
|
||||
ephemeral: sync_events::Ephemeral { events: Vec::new() },
|
||||
},
|
||||
);
|
||||
|
||||
MatrixResult(Ok(sync_events::Response {
|
||||
next_batch: String::new(),
|
||||
rooms: sync_events::Rooms {
|
||||
leave: Default::default(),
|
||||
join: joined_rooms,
|
||||
invite: Default::default(),
|
||||
},
|
||||
presence: sync_events::Presence { events: Vec::new() },
|
||||
device_lists: Default::default(),
|
||||
device_one_time_keys_count: Default::default(),
|
||||
to_device: sync_events::ToDevice { events: Vec::new() },
|
||||
}))
|
||||
}
|
||||
|
||||
#[options("/<_segments..>")]
|
||||
fn options_route(_segments: PathBuf) -> MatrixResult<create_message_event::Response> {
|
||||
MatrixResult(Err(Error {
|
||||
kind: ErrorKind::NotFound,
|
||||
message: "Room not found.".to_owned(),
|
||||
status_code: http::StatusCode::NOT_FOUND,
|
||||
}))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Log info by default
|
||||
if let Err(_) = std::env::var("RUST_LOG") {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
std::env::set_var("RUST_LOG", "matrixserver=debug,info");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
|
||||
|
@ -257,6 +310,8 @@ fn main() {
|
|||
get_alias_route,
|
||||
join_room_by_id_route,
|
||||
create_message_event_route,
|
||||
sync_route,
|
||||
options_route,
|
||||
],
|
||||
)
|
||||
.manage(data)
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
use {
|
||||
rocket::data::{FromDataSimple, Outcome},
|
||||
rocket::http::Status,
|
||||
rocket::response::Responder,
|
||||
rocket::Outcome::*,
|
||||
rocket::Request,
|
||||
rocket::State,
|
||||
ruma_api::{
|
||||
error::{FromHttpRequestError, FromHttpResponseError},
|
||||
Endpoint, Outgoing,
|
||||
},
|
||||
ruma_client_api::error::Error,
|
||||
ruma_identifiers::UserId,
|
||||
std::ops::Deref,
|
||||
std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{Cursor, Read},
|
||||
},
|
||||
use rocket::{
|
||||
data::{FromDataSimple, Outcome},
|
||||
http::Status,
|
||||
response::Responder,
|
||||
Outcome::*,
|
||||
Request, State,
|
||||
};
|
||||
use ruma_api::{
|
||||
error::{FromHttpRequestError, FromHttpResponseError},
|
||||
Endpoint, Outgoing,
|
||||
};
|
||||
use ruma_client_api::error::Error;
|
||||
use ruma_identifiers::UserId;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{Cursor, Read},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
const MESSAGE_LIMIT: u64 = 65535;
|
||||
|
||||
/// This struct converts rocket requests into ruma structs by converting them into http requests
|
||||
/// first.
|
||||
#[derive(Debug)]
|
||||
pub struct Ruma<T: Outgoing> {
|
||||
body: T::Incoming,
|
||||
pub user_id: Option<UserId>,
|
||||
|
|
|
@ -24,6 +24,11 @@ pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
|
|||
Some(number.to_be_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn u64_from_bytes(bytes: &[u8]) -> u64 {
|
||||
let array: [u8; 8] = bytes.try_into().expect("bytes are valid u64");
|
||||
u64::from_be_bytes(array)
|
||||
}
|
||||
|
||||
pub fn string_from_bytes(bytes: &[u8]) -> String {
|
||||
String::from_utf8(bytes.to_vec()).expect("bytes are valid utf8")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue