mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-18 08:11:24 +01:00
change rustfmt configuration
This change is fully automated, except the `rustfmt.toml` changes and a few clippy directives to allow specific functions with too many lines because they are longer now.
This commit is contained in:
parent
40d6ce230d
commit
0afc1d2f50
123 changed files with 7881 additions and 4687 deletions
|
|
@ -1,14 +1,16 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::Result;
|
||||
use ruma::{
|
||||
events::{AnyEphemeralRoomEvent, RoomAccountDataEventType},
|
||||
serde::Raw,
|
||||
RoomId, UserId,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Places one event in the account data of the user and removes the previous entry.
|
||||
/// Places one event in the account data of the user and removes the
|
||||
/// previous entry.
|
||||
fn update(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ use ruma::{
|
|||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
history_visibility::{
|
||||
HistoryVisibility, RoomHistoryVisibilityEventContent,
|
||||
},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
message::RoomMessageEventContent,
|
||||
|
|
@ -26,19 +28,19 @@ use ruma::{
|
|||
},
|
||||
TimelineEventType,
|
||||
},
|
||||
EventId, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
EventId, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, RoomVersionId,
|
||||
ServerName, UserId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use tokio::sync::{mpsc, Mutex, RwLock};
|
||||
use tracing::warn;
|
||||
|
||||
use super::pdu::PduBuilder;
|
||||
use crate::{
|
||||
api::client_server::{leave_all_rooms, AUTO_GEN_PASSWORD_LENGTH},
|
||||
services, utils, Error, PduEvent, Result,
|
||||
};
|
||||
|
||||
use super::pdu::PduBuilder;
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Parser)]
|
||||
#[command(name = "@grapevine:server.name:", version = env!("CARGO_PKG_VERSION"))]
|
||||
|
|
@ -46,11 +48,12 @@ enum AdminCommand {
|
|||
#[command(verbatim_doc_comment)]
|
||||
/// Register an appservice using its registration YAML
|
||||
///
|
||||
/// This command needs a YAML generated by an appservice (such as a bridge),
|
||||
/// which must be provided in a Markdown code-block below the command.
|
||||
/// This command needs a YAML generated by an appservice (such as a
|
||||
/// bridge), which must be provided in a Markdown code-block below the
|
||||
/// command.
|
||||
///
|
||||
/// Registering a new bridge using the ID of an existing bridge will replace
|
||||
/// the old one.
|
||||
/// Registering a new bridge using the ID of an existing bridge will
|
||||
/// replace the old one.
|
||||
///
|
||||
/// [commandbody]()
|
||||
/// # ```
|
||||
|
|
@ -95,8 +98,9 @@ enum AdminCommand {
|
|||
///
|
||||
/// Users will not be removed from joined rooms by default.
|
||||
/// Can be overridden with --leave-rooms flag.
|
||||
/// Removing a mass amount of users from a room may cause a significant amount of leave events.
|
||||
/// The time to leave rooms may depend significantly on joined rooms and servers.
|
||||
/// Removing a mass amount of users from a room may cause a significant
|
||||
/// amount of leave events. The time to leave rooms may depend
|
||||
/// significantly on joined rooms and servers.
|
||||
///
|
||||
/// [commandbody]()
|
||||
/// # ```
|
||||
|
|
@ -138,11 +142,17 @@ enum AdminCommand {
|
|||
/// Print database memory usage statistics
|
||||
MemoryUsage,
|
||||
|
||||
/// Clears all of Grapevine's database caches with index smaller than the amount
|
||||
ClearDatabaseCaches { amount: u32 },
|
||||
/// Clears all of Grapevine's database caches with index smaller than the
|
||||
/// amount
|
||||
ClearDatabaseCaches {
|
||||
amount: u32,
|
||||
},
|
||||
|
||||
/// Clears all of Grapevine's service caches with index smaller than the amount
|
||||
ClearServiceCaches { amount: u32 },
|
||||
/// Clears all of Grapevine's service caches with index smaller than the
|
||||
/// amount
|
||||
ClearServiceCaches {
|
||||
amount: u32,
|
||||
},
|
||||
|
||||
/// Show configuration values
|
||||
ShowConfig,
|
||||
|
|
@ -162,9 +172,13 @@ enum AdminCommand {
|
|||
},
|
||||
|
||||
/// Disables incoming federation handling for a room.
|
||||
DisableRoom { room_id: Box<RoomId> },
|
||||
DisableRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
/// Enables incoming federation handling for a room again.
|
||||
EnableRoom { room_id: Box<RoomId> },
|
||||
EnableRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// Verify json signatures
|
||||
/// [commandbody]()
|
||||
|
|
@ -267,31 +281,38 @@ impl Service {
|
|||
}
|
||||
|
||||
pub(crate) fn process_message(&self, room_message: String) {
|
||||
self.sender
|
||||
.send(AdminRoomEvent::ProcessMessage(room_message))
|
||||
.unwrap();
|
||||
self.sender.send(AdminRoomEvent::ProcessMessage(room_message)).unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn send_message(&self, message_content: RoomMessageEventContent) {
|
||||
self.sender
|
||||
.send(AdminRoomEvent::SendMessage(message_content))
|
||||
.unwrap();
|
||||
pub(crate) fn send_message(
|
||||
&self,
|
||||
message_content: RoomMessageEventContent,
|
||||
) {
|
||||
self.sender.send(AdminRoomEvent::SendMessage(message_content)).unwrap();
|
||||
}
|
||||
|
||||
// Parse and process a message from the admin room
|
||||
async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent {
|
||||
async fn process_admin_message(
|
||||
&self,
|
||||
room_message: String,
|
||||
) -> RoomMessageEventContent {
|
||||
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
|
||||
let command_line = lines.next().expect("each string has at least one line");
|
||||
let command_line =
|
||||
lines.next().expect("each string has at least one line");
|
||||
let body: Vec<_> = lines.collect();
|
||||
|
||||
let admin_command = match Self::parse_admin_command(command_line) {
|
||||
Ok(command) => command,
|
||||
Err(error) => {
|
||||
let server_name = services().globals.server_name();
|
||||
let message = error.replace("server.name", server_name.as_str());
|
||||
let message =
|
||||
error.replace("server.name", server_name.as_str());
|
||||
let html_message = Self::usage_to_html(&message, server_name);
|
||||
|
||||
return RoomMessageEventContent::text_html(message, html_message);
|
||||
return RoomMessageEventContent::text_html(
|
||||
message,
|
||||
html_message,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -299,22 +320,28 @@ impl Service {
|
|||
Ok(reply_message) => reply_message,
|
||||
Err(error) => {
|
||||
let markdown_message = format!(
|
||||
"Encountered an error while handling the command:\n\
|
||||
```\n{error}\n```",
|
||||
"Encountered an error while handling the \
|
||||
command:\n```\n{error}\n```",
|
||||
);
|
||||
let html_message = format!(
|
||||
"Encountered an error while handling the command:\n\
|
||||
<pre>\n{error}\n</pre>",
|
||||
"Encountered an error while handling the \
|
||||
command:\n<pre>\n{error}\n</pre>",
|
||||
);
|
||||
|
||||
RoomMessageEventContent::text_html(markdown_message, html_message)
|
||||
RoomMessageEventContent::text_html(
|
||||
markdown_message,
|
||||
html_message,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse chat messages from the admin room into an AdminCommand object
|
||||
fn parse_admin_command(command_line: &str) -> std::result::Result<AdminCommand, String> {
|
||||
// Note: argv[0] is `@grapevine:servername:`, which is treated as the main command
|
||||
fn parse_admin_command(
|
||||
command_line: &str,
|
||||
) -> std::result::Result<AdminCommand, String> {
|
||||
// Note: argv[0] is `@grapevine:servername:`, which is treated as the
|
||||
// main command
|
||||
let mut argv: Vec<_> = command_line.split_whitespace().collect();
|
||||
|
||||
// Replace `help command` with `command --help`
|
||||
|
|
@ -342,18 +369,26 @@ impl Service {
|
|||
) -> Result<RoomMessageEventContent> {
|
||||
let reply_message_content = match command {
|
||||
AdminCommand::RegisterAppservice => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
if body.len() > 2
|
||||
&& body[0].trim() == "```"
|
||||
&& body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let appservice_config = body[1..body.len() - 1].join("\n");
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(
|
||||
&appservice_config,
|
||||
);
|
||||
match parsed_config {
|
||||
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
|
||||
Ok(id) => RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {id}."
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
)),
|
||||
Ok(yaml) => match services()
|
||||
.appservice
|
||||
.register_appservice(yaml)
|
||||
.await
|
||||
{
|
||||
Ok(id) => RoomMessageEventContent::text_plain(
|
||||
format!("Appservice registered with ID: {id}."),
|
||||
),
|
||||
Err(e) => RoomMessageEventContent::text_plain(
|
||||
format!("Failed to register appservice: {e}"),
|
||||
),
|
||||
},
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config: {e}"
|
||||
|
|
@ -361,7 +396,8 @@ impl Service {
|
|||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
"Expected code block in command body. Add --help for \
|
||||
details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -372,7 +408,9 @@ impl Service {
|
|||
.unregister_appservice(&appservice_identifier)
|
||||
.await
|
||||
{
|
||||
Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."),
|
||||
Ok(()) => RoomMessageEventContent::text_plain(
|
||||
"Appservice unregistered.",
|
||||
),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to unregister appservice: {e}"
|
||||
)),
|
||||
|
|
@ -407,17 +445,25 @@ impl Service {
|
|||
);
|
||||
RoomMessageEventContent::text_plain(output)
|
||||
}
|
||||
AdminCommand::ListLocalUsers => match services().users.list_local_users() {
|
||||
AdminCommand::ListLocalUsers => match services()
|
||||
.users
|
||||
.list_local_users()
|
||||
{
|
||||
Ok(users) => {
|
||||
let mut msg: String = format!("Found {} local user account(s):\n", users.len());
|
||||
let mut msg: String = format!(
|
||||
"Found {} local user account(s):\n",
|
||||
users.len()
|
||||
);
|
||||
msg += &users.join("\n");
|
||||
RoomMessageEventContent::text_plain(&msg)
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(e.to_string()),
|
||||
},
|
||||
AdminCommand::IncomingFederation => {
|
||||
let map = services().globals.roomid_federationhandletime.read().await;
|
||||
let mut msg: String = format!("Handling {} incoming pdus:\n", map.len());
|
||||
let map =
|
||||
services().globals.roomid_federationhandletime.read().await;
|
||||
let mut msg: String =
|
||||
format!("Handling {} incoming pdus:\n", map.len());
|
||||
|
||||
for (r, (e, i)) in map.iter() {
|
||||
let elapsed = i.elapsed();
|
||||
|
|
@ -431,17 +477,26 @@ impl Service {
|
|||
}
|
||||
RoomMessageEventContent::text_plain(&msg)
|
||||
}
|
||||
AdminCommand::GetAuthChain { event_id } => {
|
||||
AdminCommand::GetAuthChain {
|
||||
event_id,
|
||||
} => {
|
||||
let event_id = Arc::<EventId>::from(event_id);
|
||||
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
||||
if let Some(event) =
|
||||
services().rooms.timeline.get_pdu_json(&event_id)?
|
||||
{
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
.and_then(|val| val.as_str())
|
||||
.ok_or_else(|| Error::bad_database("Invalid event in database"))?;
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("Invalid event in database")
|
||||
})?;
|
||||
|
||||
let room_id = <&RoomId>::try_from(room_id_str).map_err(|_| {
|
||||
Error::bad_database("Invalid room id field in event in database")
|
||||
})?;
|
||||
let room_id =
|
||||
<&RoomId>::try_from(room_id_str).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room id field in event in database",
|
||||
)
|
||||
})?;
|
||||
let start = Instant::now();
|
||||
let count = services()
|
||||
.rooms
|
||||
|
|
@ -458,29 +513,47 @@ impl Service {
|
|||
}
|
||||
}
|
||||
AdminCommand::ParsePdu => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
if body.len() > 2
|
||||
&& body[0].trim() == "```"
|
||||
&& body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => {
|
||||
match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
match ruma::signatures::reference_hash(
|
||||
&value,
|
||||
&RoomVersionId::V6,
|
||||
) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
let event_id =
|
||||
EventId::parse(format!("${hash}"));
|
||||
|
||||
match serde_json::from_value::<PduEvent>(
|
||||
serde_json::to_value(value).expect("value is json"),
|
||||
serde_json::to_value(value)
|
||||
.expect("value is json"),
|
||||
) {
|
||||
Ok(pdu) => RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\n{pdu:#?}"
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
)),
|
||||
Ok(pdu) => {
|
||||
RoomMessageEventContent::text_plain(
|
||||
format!(
|
||||
"EventId: {event_id:?}\\
|
||||
n{pdu:#?}"
|
||||
),
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
RoomMessageEventContent::text_plain(
|
||||
format!(
|
||||
"EventId: {event_id:?}\\
|
||||
nCould not parse event: \
|
||||
{e}"
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse PDU JSON: {e:?}"
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(
|
||||
format!("Could not parse PDU JSON: {e:?}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
|
|
@ -488,10 +561,14 @@ impl Service {
|
|||
)),
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain("Expected code block in command body.")
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body.",
|
||||
)
|
||||
}
|
||||
}
|
||||
AdminCommand::GetPdu { event_id } => {
|
||||
AdminCommand::GetPdu {
|
||||
event_id,
|
||||
} => {
|
||||
let mut outlier = false;
|
||||
let mut pdu_json = services()
|
||||
.rooms
|
||||
|
|
@ -499,7 +576,8 @@ impl Service {
|
|||
.get_non_outlier_pdu_json(&event_id)?;
|
||||
if pdu_json.is_none() {
|
||||
outlier = true;
|
||||
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?;
|
||||
pdu_json =
|
||||
services().rooms.timeline.get_pdu_json(&event_id)?;
|
||||
}
|
||||
match pdu_json {
|
||||
Some(json) => {
|
||||
|
|
@ -516,7 +594,8 @@ impl Service {
|
|||
json_text
|
||||
),
|
||||
format!(
|
||||
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
|
||||
"<p>{}</p>\n<pre><code \
|
||||
class=\"language-json\">{}\n</code></pre>\n",
|
||||
if outlier {
|
||||
"PDU is outlier"
|
||||
} else {
|
||||
|
|
@ -526,7 +605,9 @@ impl Service {
|
|||
),
|
||||
)
|
||||
}
|
||||
None => RoomMessageEventContent::text_plain("PDU not found."),
|
||||
None => {
|
||||
RoomMessageEventContent::text_plain("PDU not found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
AdminCommand::MemoryUsage => {
|
||||
|
|
@ -537,30 +618,42 @@ impl Service {
|
|||
"Services:\n{response1}\n\nDatabase:\n{response2}"
|
||||
))
|
||||
}
|
||||
AdminCommand::ClearDatabaseCaches { amount } => {
|
||||
AdminCommand::ClearDatabaseCaches {
|
||||
amount,
|
||||
} => {
|
||||
services().globals.db.clear_caches(amount);
|
||||
|
||||
RoomMessageEventContent::text_plain("Done.")
|
||||
}
|
||||
AdminCommand::ClearServiceCaches { amount } => {
|
||||
AdminCommand::ClearServiceCaches {
|
||||
amount,
|
||||
} => {
|
||||
services().clear_caches(amount).await;
|
||||
|
||||
RoomMessageEventContent::text_plain("Done.")
|
||||
}
|
||||
AdminCommand::ShowConfig => {
|
||||
// Construct and send the response
|
||||
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"{}",
|
||||
services().globals.config
|
||||
))
|
||||
}
|
||||
AdminCommand::ResetPassword { username } => {
|
||||
AdminCommand::ResetPassword {
|
||||
username,
|
||||
} => {
|
||||
let user_id = match UserId::parse_with_server_name(
|
||||
username.as_str().to_lowercase(),
|
||||
services().globals.server_name(),
|
||||
) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"The supplied username is not a valid username: {e}"
|
||||
)))
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
format!(
|
||||
"The supplied username is not a valid \
|
||||
username: {e}"
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -589,23 +682,29 @@ impl Service {
|
|||
));
|
||||
}
|
||||
|
||||
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||
let new_password =
|
||||
utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||
|
||||
match services()
|
||||
.users
|
||||
.set_password(&user_id, Some(new_password.as_str()))
|
||||
{
|
||||
Ok(()) => RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully reset the password for user {user_id}: {new_password}"
|
||||
"Successfully reset the password for user {user_id}: \
|
||||
{new_password}"
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Couldn't reset the password for user {user_id}: {e}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
AdminCommand::CreateUser { username, password } => {
|
||||
let password =
|
||||
password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||
AdminCommand::CreateUser {
|
||||
username,
|
||||
password,
|
||||
} => {
|
||||
let password = password.unwrap_or_else(|| {
|
||||
utils::random_string(AUTO_GEN_PASSWORD_LENGTH)
|
||||
});
|
||||
// Validate user id
|
||||
let user_id = match UserId::parse_with_server_name(
|
||||
username.as_str().to_lowercase(),
|
||||
|
|
@ -613,9 +712,12 @@ impl Service {
|
|||
) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"The supplied username is not a valid username: {e}"
|
||||
)))
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
format!(
|
||||
"The supplied username is not a valid \
|
||||
username: {e}"
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
if user_id.is_historical() {
|
||||
|
|
@ -647,24 +749,32 @@ impl Service {
|
|||
.into(),
|
||||
&serde_json::to_value(PushRulesEvent {
|
||||
content: PushRulesEventContent {
|
||||
global: ruma::push::Ruleset::server_default(&user_id),
|
||||
global: ruma::push::Ruleset::server_default(
|
||||
&user_id,
|
||||
),
|
||||
},
|
||||
})
|
||||
.expect("to json value always works"),
|
||||
)?;
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
// we dont add a device since we're not the user, just the
|
||||
// creator
|
||||
|
||||
// Inhibit login does not work for guests
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Created user with user_id: {user_id} and password: {password}"
|
||||
"Created user with user_id: {user_id} and password: \
|
||||
{password}"
|
||||
))
|
||||
}
|
||||
AdminCommand::DisableRoom { room_id } => {
|
||||
AdminCommand::DisableRoom {
|
||||
room_id,
|
||||
} => {
|
||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||
RoomMessageEventContent::text_plain("Room disabled.")
|
||||
}
|
||||
AdminCommand::EnableRoom { room_id } => {
|
||||
AdminCommand::EnableRoom {
|
||||
room_id,
|
||||
} => {
|
||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
||||
RoomMessageEventContent::text_plain("Room enabled.")
|
||||
}
|
||||
|
|
@ -677,13 +787,16 @@ impl Service {
|
|||
RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} doesn't exist on this server"
|
||||
))
|
||||
} else if user_id.server_name() != services().globals.server_name() {
|
||||
} else if user_id.server_name()
|
||||
!= services().globals.server_name()
|
||||
{
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} is not from this server"
|
||||
))
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Making {user_id} leave all rooms before deactivation..."
|
||||
"Making {user_id} leave all rooms before \
|
||||
deactivation..."
|
||||
));
|
||||
|
||||
services().users.deactivate_account(&user_id)?;
|
||||
|
|
@ -697,10 +810,18 @@ impl Service {
|
|||
))
|
||||
}
|
||||
}
|
||||
AdminCommand::DeactivateAll { leave_rooms, force } => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
AdminCommand::DeactivateAll {
|
||||
leave_rooms,
|
||||
force,
|
||||
} => {
|
||||
if body.len() > 2
|
||||
&& body[0].trim() == "```"
|
||||
&& body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let users = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||
let users = body
|
||||
.clone()
|
||||
.drain(1..body.len() - 1)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut user_ids = Vec::new();
|
||||
let mut remote_ids = Vec::new();
|
||||
|
|
@ -710,7 +831,9 @@ impl Service {
|
|||
for &user in &users {
|
||||
match <&UserId>::try_from(user) {
|
||||
Ok(user_id) => {
|
||||
if user_id.server_name() != services().globals.server_name() {
|
||||
if user_id.server_name()
|
||||
!= services().globals.server_name()
|
||||
{
|
||||
remote_ids.push(user_id);
|
||||
} else if !services().users.exists(user_id)? {
|
||||
non_existant_ids.push(user_id);
|
||||
|
|
@ -727,39 +850,59 @@ impl Service {
|
|||
let mut markdown_message = String::new();
|
||||
let mut html_message = String::new();
|
||||
if !invalid_users.is_empty() {
|
||||
markdown_message.push_str("The following user ids are not valid:\n```\n");
|
||||
html_message.push_str("The following user ids are not valid:\n<pre>\n");
|
||||
markdown_message.push_str(
|
||||
"The following user ids are not valid:\n```\n",
|
||||
);
|
||||
html_message.push_str(
|
||||
"The following user ids are not valid:\n<pre>\n",
|
||||
);
|
||||
for invalid_user in invalid_users {
|
||||
writeln!(markdown_message, "{invalid_user}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
writeln!(html_message, "{invalid_user}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
.expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
writeln!(html_message, "{invalid_user}").expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
}
|
||||
markdown_message.push_str("```\n\n");
|
||||
html_message.push_str("</pre>\n\n");
|
||||
}
|
||||
if !remote_ids.is_empty() {
|
||||
markdown_message
|
||||
.push_str("The following users are not from this server:\n```\n");
|
||||
html_message
|
||||
.push_str("The following users are not from this server:\n<pre>\n");
|
||||
markdown_message.push_str(
|
||||
"The following users are not from this \
|
||||
server:\n```\n",
|
||||
);
|
||||
html_message.push_str(
|
||||
"The following users are not from this \
|
||||
server:\n<pre>\n",
|
||||
);
|
||||
for remote_id in remote_ids {
|
||||
writeln!(markdown_message, "{remote_id}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
writeln!(html_message, "{remote_id}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
writeln!(markdown_message, "{remote_id}").expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
writeln!(html_message, "{remote_id}").expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
}
|
||||
markdown_message.push_str("```\n\n");
|
||||
html_message.push_str("</pre>\n\n");
|
||||
}
|
||||
if !non_existant_ids.is_empty() {
|
||||
markdown_message.push_str("The following users do not exist:\n```\n");
|
||||
html_message.push_str("The following users do not exist:\n<pre>\n");
|
||||
markdown_message.push_str(
|
||||
"The following users do not exist:\n```\n",
|
||||
);
|
||||
html_message.push_str(
|
||||
"The following users do not exist:\n<pre>\n",
|
||||
);
|
||||
for non_existant_id in non_existant_ids {
|
||||
writeln!(markdown_message, "{non_existant_id}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
writeln!(html_message, "{non_existant_id}")
|
||||
.expect("write to in-memory buffer should succeed");
|
||||
.expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
writeln!(html_message, "{non_existant_id}").expect(
|
||||
"write to in-memory buffer should succeed",
|
||||
);
|
||||
}
|
||||
markdown_message.push_str("```\n\n");
|
||||
html_message.push_str("</pre>\n\n");
|
||||
|
|
@ -775,21 +918,24 @@ impl Service {
|
|||
let mut admins = Vec::new();
|
||||
|
||||
if !force {
|
||||
user_ids.retain(|&user_id| match services().users.is_admin(user_id) {
|
||||
Ok(is_admin) => {
|
||||
if is_admin {
|
||||
admins.push(user_id.localpart());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
user_ids.retain(|&user_id| {
|
||||
match services().users.is_admin(user_id) {
|
||||
Ok(is_admin) => {
|
||||
if is_admin {
|
||||
admins.push(user_id.localpart());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
Err(_) => false,
|
||||
});
|
||||
}
|
||||
|
||||
for &user_id in &user_ids {
|
||||
if services().users.deactivate_account(user_id).is_ok() {
|
||||
if services().users.deactivate_account(user_id).is_ok()
|
||||
{
|
||||
deactivation_count += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -807,16 +953,25 @@ impl Service {
|
|||
"Deactivated {deactivation_count} accounts."
|
||||
))
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(format!("Deactivated {} accounts.\nSkipped admin accounts: {:?}. Use --force to deactivate admin accounts", deactivation_count, admins.join(", ")))
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Deactivated {} accounts.\nSkipped admin \
|
||||
accounts: {:?}. Use --force to deactivate admin \
|
||||
accounts",
|
||||
deactivation_count,
|
||||
admins.join(", ")
|
||||
))
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
"Expected code block in command body. Add --help for \
|
||||
details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
AdminCommand::SignJson => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
if body.len() > 2
|
||||
&& body[0].trim() == "```"
|
||||
&& body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
|
|
@ -827,20 +982,26 @@ impl Service {
|
|||
&mut value,
|
||||
)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value)
|
||||
.expect("canonical json is valid json");
|
||||
let json_text =
|
||||
serde_json::to_string_pretty(&value)
|
||||
.expect("canonical json is valid json");
|
||||
RoomMessageEventContent::text_plain(json_text)
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json: {e}"
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
"Expected code block in command body. Add --help for \
|
||||
details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
AdminCommand::VerifyJson => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
if body.len() > 2
|
||||
&& body[0].trim() == "```"
|
||||
&& body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
|
|
@ -850,22 +1011,35 @@ impl Service {
|
|||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_required_signing_keys(&value, &pub_key_map)
|
||||
.fetch_required_signing_keys(
|
||||
&value,
|
||||
&pub_key_map,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let pub_key_map = pub_key_map.read().await;
|
||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||
Ok(()) => RoomMessageEventContent::text_plain("Signature correct"),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
)),
|
||||
match ruma::signatures::verify_json(
|
||||
&pub_key_map,
|
||||
&value,
|
||||
) {
|
||||
Ok(()) => RoomMessageEventContent::text_plain(
|
||||
"Signature correct",
|
||||
),
|
||||
Err(e) => RoomMessageEventContent::text_plain(
|
||||
format!(
|
||||
"Signature verification failed: {e}"
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json: {e}"
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
"Expected code block in command body. Add --help for \
|
||||
details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -876,7 +1050,8 @@ impl Service {
|
|||
|
||||
// Utility to turn clap's `--help` text to HTML.
|
||||
fn usage_to_html(text: &str, server_name: &ServerName) -> String {
|
||||
// Replace `@grapevine:servername:-subcmdname` with `@grapevine:servername: subcmdname`
|
||||
// Replace `@grapevine:servername:-subcmdname` with
|
||||
// `@grapevine:servername: subcmdname`
|
||||
let localpart = if services().globals.config.conduit_compat {
|
||||
"conduit"
|
||||
} else {
|
||||
|
|
@ -892,11 +1067,13 @@ impl Service {
|
|||
let text = text.replace("SUBCOMMAND", "COMMAND");
|
||||
let text = text.replace("subcommand", "command");
|
||||
|
||||
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
|
||||
// Escape option names (e.g. `<element-id>`) since they look like HTML
|
||||
// tags
|
||||
let text = text.replace('<', "<").replace('>', ">");
|
||||
|
||||
// Italicize the first line (command name and version text)
|
||||
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
|
||||
let re =
|
||||
Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
|
||||
let text = re.replace_all(&text, "<em>$1</em>\n");
|
||||
|
||||
// Unmerge wrapped lines
|
||||
|
|
@ -911,8 +1088,8 @@ impl Service {
|
|||
.expect("Regex compilation should not fail");
|
||||
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
||||
|
||||
// Look for a `[commandbody]()` tag. If it exists, use all lines below it that
|
||||
// start with a `#` in the USAGE section.
|
||||
// Look for a `[commandbody]()` tag. If it exists, use all lines below
|
||||
// it that start with a `#` in the USAGE section.
|
||||
let mut text_lines: Vec<&str> = text.lines().collect();
|
||||
let command_body = text_lines
|
||||
.iter()
|
||||
|
|
@ -936,8 +1113,11 @@ impl Service {
|
|||
// This makes the usage of e.g. `register-appservice` more accurate
|
||||
let re = Regex::new("(?m)^USAGE:\n (.*?)\n\n")
|
||||
.expect("Regex compilation should not fail");
|
||||
re.replace_all(&text, "USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>")
|
||||
.replace("[commandbodyblock]", &command_body)
|
||||
re.replace_all(
|
||||
&text,
|
||||
"USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>",
|
||||
)
|
||||
.replace("[commandbodyblock]", &command_body)
|
||||
};
|
||||
|
||||
// Add HTML line-breaks
|
||||
|
|
@ -949,8 +1129,9 @@ impl Service {
|
|||
|
||||
/// Create the admin room.
|
||||
///
|
||||
/// Users in this room are considered admins by grapevine, and the room can be
|
||||
/// used to issue admin commands by talking to the server user inside it.
|
||||
/// Users in this room are considered admins by grapevine, and the room can
|
||||
/// be used to issue admin commands by talking to the server user inside
|
||||
/// it.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) async fn create_admin_room(&self) -> Result<()> {
|
||||
let room_id = RoomId::new(services().globals.server_name());
|
||||
|
|
@ -993,7 +1174,9 @@ impl Service {
|
|||
| RoomVersionId::V7
|
||||
| RoomVersionId::V8
|
||||
| RoomVersionId::V9
|
||||
| RoomVersionId::V10 => RoomCreateEventContent::new_v1(grapevine_user.clone()),
|
||||
| RoomVersionId::V10 => {
|
||||
RoomCreateEventContent::new_v1(grapevine_user.clone())
|
||||
}
|
||||
RoomVersionId::V11 => RoomCreateEventContent::new_v11(),
|
||||
_ => unreachable!("Validity of room version already checked"),
|
||||
};
|
||||
|
|
@ -1008,7 +1191,8 @@ impl Service {
|
|||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
content: to_raw_value(&content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
|
|
@ -1079,8 +1263,10 @@ impl Service {
|
|||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomJoinRules,
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
||||
.expect("event is valid, we just created it"),
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(
|
||||
JoinRule::Invite,
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
|
|
@ -1098,9 +1284,11 @@ impl Service {
|
|||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
))
|
||||
content: to_raw_value(
|
||||
&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
),
|
||||
)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
|
|
@ -1134,15 +1322,18 @@ impl Service {
|
|||
.await?;
|
||||
|
||||
// 5. Events implied by name and topic
|
||||
let room_name = format!("{} Admin Room", services().globals.server_name());
|
||||
let room_name =
|
||||
format!("{} Admin Room", services().globals.server_name());
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(room_name))
|
||||
.expect("event is valid, we just created it"),
|
||||
content: to_raw_value(&RoomNameEventContent::new(
|
||||
room_name,
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
|
|
@ -1160,7 +1351,10 @@ impl Service {
|
|||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: format!("Manage {}", services().globals.server_name()),
|
||||
topic: format!(
|
||||
"Manage {}",
|
||||
services().globals.server_name()
|
||||
),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
|
@ -1174,9 +1368,10 @@ impl Service {
|
|||
.await?;
|
||||
|
||||
// 6. Room alias
|
||||
let alias: OwnedRoomAliasId = format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
let alias: OwnedRoomAliasId =
|
||||
format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
|
||||
services()
|
||||
.rooms
|
||||
|
|
@ -1206,7 +1401,8 @@ impl Service {
|
|||
|
||||
/// Gets the room ID of the admin room
|
||||
///
|
||||
/// Errors are propagated from the database, and will have None if there is no admin room
|
||||
/// Errors are propagated from the database, and will have None if there is
|
||||
/// no admin room
|
||||
// Allowed because this function uses `services()`
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(crate) fn get_admin_room(&self) -> Result<Option<OwnedRoomId>> {
|
||||
|
|
@ -1215,10 +1411,7 @@ impl Service {
|
|||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&admin_room_alias)
|
||||
services().rooms.alias.resolve_local_alias(&admin_room_alias)
|
||||
}
|
||||
|
||||
/// Invite the user to the grapevine admin room.
|
||||
|
|
@ -1356,11 +1549,13 @@ mod test {
|
|||
}
|
||||
|
||||
fn get_help_inner(input: &str) {
|
||||
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
let error =
|
||||
AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
// Search for a handful of keywords that suggest the help printed properly
|
||||
// Search for a handful of keywords that suggest the help printed
|
||||
// properly
|
||||
assert!(error.contains("Usage:"));
|
||||
assert!(error.contains("Commands:"));
|
||||
assert!(error.contains("Options:"));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ mod data;
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use futures_util::Future;
|
||||
use regex::RegexSet;
|
||||
use ruma::{
|
||||
|
|
@ -48,6 +47,8 @@ impl NamespaceRegex {
|
|||
}
|
||||
|
||||
impl TryFrom<Vec<Namespace>> for NamespaceRegex {
|
||||
type Error = regex::Error;
|
||||
|
||||
fn try_from(value: Vec<Namespace>) -> Result<Self, regex::Error> {
|
||||
let mut exclusive = vec![];
|
||||
let mut non_exclusive = vec![];
|
||||
|
|
@ -73,8 +74,6 @@ impl TryFrom<Vec<Namespace>> for NamespaceRegex {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Error = regex::Error;
|
||||
}
|
||||
|
||||
/// Appservice registration combined with its compiled regular expressions.
|
||||
|
|
@ -99,6 +98,8 @@ impl RegistrationInfo {
|
|||
}
|
||||
|
||||
impl TryFrom<Registration> for RegistrationInfo {
|
||||
type Error = regex::Error;
|
||||
|
||||
fn try_from(value: Registration) -> Result<RegistrationInfo, regex::Error> {
|
||||
Ok(RegistrationInfo {
|
||||
users: value.namespaces.users.clone().try_into()?,
|
||||
|
|
@ -107,8 +108,6 @@ impl TryFrom<Registration> for RegistrationInfo {
|
|||
registration: value,
|
||||
})
|
||||
}
|
||||
|
||||
type Error = regex::Error;
|
||||
}
|
||||
|
||||
pub(crate) struct Service {
|
||||
|
|
@ -135,8 +134,12 @@ impl Service {
|
|||
registration_info: RwLock::new(registration_info),
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an appservice and returns the ID to the caller.
|
||||
pub(crate) async fn register_appservice(&self, yaml: Registration) -> Result<String> {
|
||||
pub(crate) async fn register_appservice(
|
||||
&self,
|
||||
yaml: Registration,
|
||||
) -> Result<String> {
|
||||
//TODO: Check for collisions between exclusive appservice namespaces
|
||||
self.registration_info
|
||||
.write()
|
||||
|
|
@ -151,19 +154,27 @@ impl Service {
|
|||
/// # Arguments
|
||||
///
|
||||
/// * `service_name` - the name you send to register the service previously
|
||||
pub(crate) async fn unregister_appservice(&self, service_name: &str) -> Result<()> {
|
||||
pub(crate) async fn unregister_appservice(
|
||||
&self,
|
||||
service_name: &str,
|
||||
) -> Result<()> {
|
||||
services()
|
||||
.appservice
|
||||
.registration_info
|
||||
.write()
|
||||
.await
|
||||
.remove(service_name)
|
||||
.ok_or_else(|| crate::Error::AdminCommand("Appservice not found"))?;
|
||||
.ok_or_else(|| {
|
||||
crate::Error::AdminCommand("Appservice not found")
|
||||
})?;
|
||||
|
||||
self.db.unregister_appservice(service_name)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_registration(&self, id: &str) -> Option<Registration> {
|
||||
pub(crate) async fn get_registration(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Option<Registration> {
|
||||
self.registration_info
|
||||
.read()
|
||||
.await
|
||||
|
|
@ -173,15 +184,13 @@ impl Service {
|
|||
}
|
||||
|
||||
pub(crate) async fn iter_ids(&self) -> Vec<String> {
|
||||
self.registration_info
|
||||
.read()
|
||||
.await
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
self.registration_info.read().await.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn find_from_token(&self, token: &str) -> Option<RegistrationInfo> {
|
||||
pub(crate) async fn find_from_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Option<RegistrationInfo> {
|
||||
self.read()
|
||||
.await
|
||||
.values()
|
||||
|
|
@ -207,8 +216,12 @@ impl Service {
|
|||
|
||||
pub(crate) fn read(
|
||||
&self,
|
||||
) -> impl Future<Output = tokio::sync::RwLockReadGuard<'_, BTreeMap<String, RegistrationInfo>>>
|
||||
{
|
||||
) -> impl Future<
|
||||
Output = tokio::sync::RwLockReadGuard<
|
||||
'_,
|
||||
BTreeMap<String, RegistrationInfo>,
|
||||
>,
|
||||
> {
|
||||
self.registration_info.read()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ pub(crate) trait Data: Send + Sync {
|
|||
|
||||
fn get_registration(&self, id: &str) -> Result<Option<Registration>>;
|
||||
|
||||
fn iter_ids<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>>;
|
||||
fn iter_ids<'a>(
|
||||
&'a self,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>>;
|
||||
|
||||
fn all(&self) -> Result<Vec<(String, Registration)>>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,4 @@
|
|||
mod data;
|
||||
pub(crate) use data::Data;
|
||||
use ruma::{
|
||||
serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
OwnedServerSigningKeyId, OwnedUserId,
|
||||
};
|
||||
|
||||
use crate::api::server_server::FedDest;
|
||||
|
||||
use crate::{services, Config, Error, Result};
|
||||
use futures_util::FutureExt;
|
||||
use hyper::{
|
||||
client::connect::dns::{GaiResolver, Name},
|
||||
service::Service as HyperService,
|
||||
};
|
||||
use reqwest::dns::{Addrs, Resolve, Resolving};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::sync::sync_events,
|
||||
federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
},
|
||||
DeviceId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
error::Error as StdError,
|
||||
|
|
@ -35,11 +13,29 @@ use std::{
|
|||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
pub(crate) use data::Data;
|
||||
use futures_util::FutureExt;
|
||||
use hyper::{
|
||||
client::connect::dns::{GaiResolver, Name},
|
||||
service::Service as HyperService,
|
||||
};
|
||||
use reqwest::dns::{Addrs, Resolve, Resolving};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::sync::sync_events,
|
||||
federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
},
|
||||
serde::Base64,
|
||||
DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
OwnedServerSigningKeyId, OwnedUserId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
|
||||
use tracing::{error, info};
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use crate::{api::server_server::FedDest, services, Config, Error, Result};
|
||||
|
||||
type WellKnownMap = HashMap<OwnedServerName, (FedDest, String)>;
|
||||
type TlsNameMap = HashMap<String, (Vec<IpAddr>, u16)>;
|
||||
|
|
@ -66,27 +62,40 @@ pub(crate) struct Service {
|
|||
default_client: reqwest::Client,
|
||||
pub(crate) stable_room_versions: Vec<RoomVersionId>,
|
||||
pub(crate) unstable_room_versions: Vec<RoomVersionId>,
|
||||
pub(crate) bad_event_ratelimiter: Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
|
||||
pub(crate) bad_signature_ratelimiter: Arc<RwLock<HashMap<Vec<String>, RateLimitState>>>,
|
||||
pub(crate) bad_query_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, RateLimitState>>>,
|
||||
pub(crate) servername_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, Arc<Semaphore>>>>,
|
||||
pub(crate) sync_receivers: RwLock<HashMap<(OwnedUserId, OwnedDeviceId), SyncHandle>>,
|
||||
pub(crate) roomid_mutex_insert: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub(crate) bad_event_ratelimiter:
|
||||
Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
|
||||
pub(crate) bad_signature_ratelimiter:
|
||||
Arc<RwLock<HashMap<Vec<String>, RateLimitState>>>,
|
||||
pub(crate) bad_query_ratelimiter:
|
||||
Arc<RwLock<HashMap<OwnedServerName, RateLimitState>>>,
|
||||
pub(crate) servername_ratelimiter:
|
||||
Arc<RwLock<HashMap<OwnedServerName, Arc<Semaphore>>>>,
|
||||
pub(crate) sync_receivers:
|
||||
RwLock<HashMap<(OwnedUserId, OwnedDeviceId), SyncHandle>>,
|
||||
pub(crate) roomid_mutex_insert:
|
||||
RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub(crate) roomid_mutex_state: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
|
||||
// this lock will be held longer
|
||||
pub(crate) roomid_mutex_federation: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub(crate) roomid_federationhandletime: RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
||||
pub(crate) roomid_mutex_federation:
|
||||
RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub(crate) roomid_federationhandletime:
|
||||
RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
||||
pub(crate) stateres_mutex: Arc<Mutex<()>>,
|
||||
pub(crate) rotate: RotationHandler,
|
||||
|
||||
pub(crate) shutdown: AtomicBool,
|
||||
}
|
||||
|
||||
/// Handles "rotation" of long-polling requests. "Rotation" in this context is similar to "rotation" of log files and the like.
|
||||
/// Handles "rotation" of long-polling requests. "Rotation" in this context is
|
||||
/// similar to "rotation" of log files and the like.
|
||||
///
|
||||
/// This is utilized to have sync workers return early and release read locks on the database.
|
||||
pub(crate) struct RotationHandler(broadcast::Sender<()>, broadcast::Receiver<()>);
|
||||
/// This is utilized to have sync workers return early and release read locks on
|
||||
/// the database.
|
||||
pub(crate) struct RotationHandler(
|
||||
broadcast::Sender<()>,
|
||||
broadcast::Receiver<()>,
|
||||
);
|
||||
|
||||
impl RotationHandler {
|
||||
pub(crate) fn new() -> Self {
|
||||
|
|
@ -136,7 +145,10 @@ impl Resolve for Resolver {
|
|||
.and_then(|(override_name, port)| {
|
||||
override_name.first().map(|first_name| {
|
||||
let x: Box<dyn Iterator<Item = SocketAddr> + Send> =
|
||||
Box::new(iter::once(SocketAddr::new(*first_name, *port)));
|
||||
Box::new(iter::once(SocketAddr::new(
|
||||
*first_name,
|
||||
*port,
|
||||
)));
|
||||
let x: Resolving = Box::pin(future::ready(Ok(x)));
|
||||
x
|
||||
})
|
||||
|
|
@ -144,9 +156,11 @@ impl Resolve for Resolver {
|
|||
.unwrap_or_else(|| {
|
||||
let this = &mut self.inner.clone();
|
||||
Box::pin(HyperService::<Name>::call(this, name).map(|result| {
|
||||
result
|
||||
.map(|addrs| -> Addrs { Box::new(addrs) })
|
||||
.map_err(|err| -> Box<dyn StdError + Send + Sync> { Box::new(err) })
|
||||
result.map(|addrs| -> Addrs { Box::new(addrs) }).map_err(
|
||||
|err| -> Box<dyn StdError + Send + Sync> {
|
||||
Box::new(err)
|
||||
},
|
||||
)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
@ -167,10 +181,9 @@ impl Service {
|
|||
|
||||
let tls_name_override = Arc::new(StdRwLock::new(TlsNameMap::new()));
|
||||
|
||||
let jwt_decoding_key = config
|
||||
.jwt_secret
|
||||
.as_ref()
|
||||
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()));
|
||||
let jwt_decoding_key = config.jwt_secret.as_ref().map(|secret| {
|
||||
jsonwebtoken::DecodingKey::from_secret(secret.as_bytes())
|
||||
});
|
||||
|
||||
let default_client = reqwest_client_builder(&config)?.build()?;
|
||||
let federation_client = reqwest_client_builder(&config)?
|
||||
|
|
@ -187,20 +200,28 @@ impl Service {
|
|||
RoomVersionId::V11,
|
||||
];
|
||||
// Experimental, partially supported room versions
|
||||
let unstable_room_versions = vec![RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
|
||||
let unstable_room_versions =
|
||||
vec![RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
|
||||
|
||||
let mut s = Self {
|
||||
db,
|
||||
config,
|
||||
keypair: Arc::new(keypair),
|
||||
dns_resolver: TokioAsyncResolver::tokio_from_system_conf().map_err(|e| {
|
||||
error!(
|
||||
"Failed to set up trust dns resolver with system config: {}",
|
||||
e
|
||||
);
|
||||
Error::bad_config("Failed to set up trust dns resolver with system config.")
|
||||
})?,
|
||||
actual_destination_cache: Arc::new(RwLock::new(WellKnownMap::new())),
|
||||
dns_resolver: TokioAsyncResolver::tokio_from_system_conf()
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed to set up trust dns resolver with system \
|
||||
config: {}",
|
||||
e
|
||||
);
|
||||
Error::bad_config(
|
||||
"Failed to set up trust dns resolver with system \
|
||||
config.",
|
||||
)
|
||||
})?,
|
||||
actual_destination_cache: Arc::new(
|
||||
RwLock::new(WellKnownMap::new()),
|
||||
),
|
||||
tls_name_override,
|
||||
federation_client,
|
||||
default_client,
|
||||
|
|
@ -223,12 +244,11 @@ impl Service {
|
|||
|
||||
fs::create_dir_all(s.get_media_folder())?;
|
||||
|
||||
if !s
|
||||
.supported_room_versions()
|
||||
.contains(&s.config.default_room_version)
|
||||
if !s.supported_room_versions().contains(&s.config.default_room_version)
|
||||
{
|
||||
error!(config=?s.config.default_room_version, fallback=?crate::config::default_default_room_version(), "Room version in config isn't supported, falling back to default version");
|
||||
s.config.default_room_version = crate::config::default_default_room_version();
|
||||
s.config.default_room_version =
|
||||
crate::config::default_default_room_version();
|
||||
};
|
||||
|
||||
Ok(s)
|
||||
|
|
@ -261,7 +281,11 @@ impl Service {
|
|||
self.db.current_count()
|
||||
}
|
||||
|
||||
pub(crate) async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||
pub(crate) async fn watch(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<()> {
|
||||
self.db.watch(user_id, device_id).await
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +337,9 @@ impl Service {
|
|||
&self.dns_resolver
|
||||
}
|
||||
|
||||
pub(crate) fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey> {
|
||||
pub(crate) fn jwt_decoding_key(
|
||||
&self,
|
||||
) -> Option<&jsonwebtoken::DecodingKey> {
|
||||
self.jwt_decoding_key.as_ref()
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +379,8 @@ impl Service {
|
|||
/// TODO: the key valid until timestamp is only honored in room version > 4
|
||||
/// Remove the outdated keys and insert the new ones.
|
||||
///
|
||||
/// This doesn't actually check that the keys provided are newer than the old set.
|
||||
/// This doesn't actually check that the keys provided are newer than the
|
||||
/// old set.
|
||||
pub(crate) fn add_signing_key(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
|
|
@ -362,7 +389,8 @@ impl Service {
|
|||
self.db.add_signing_key(origin, new_keys)
|
||||
}
|
||||
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found for the server.
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||
/// for the server.
|
||||
pub(crate) fn signing_keys_for(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ use crate::Result;
|
|||
pub(crate) trait Data: Send + Sync {
|
||||
fn next_count(&self) -> Result<u64>;
|
||||
fn current_count(&self) -> Result<u64>;
|
||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>;
|
||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId)
|
||||
-> Result<()>;
|
||||
fn cleanup(&self) -> Result<()>;
|
||||
fn memory_usage(&self) -> String;
|
||||
fn clear_caches(&self, amount: u32);
|
||||
|
|
@ -25,7 +26,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
new_keys: ServerSigningKeys,
|
||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
|
||||
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found for the server.
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||
/// for the server.
|
||||
fn signing_keys_for(
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::Result;
|
||||
use ruma::{
|
||||
api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
|
||||
serde::Raw,
|
||||
OwnedRoomId, RoomId, UserId,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn create_backup(
|
||||
&self,
|
||||
|
|
@ -23,12 +24,21 @@ pub(crate) trait Data: Send + Sync {
|
|||
backup_metadata: &Raw<BackupAlgorithm>,
|
||||
) -> Result<String>;
|
||||
|
||||
fn get_latest_backup_version(&self, user_id: &UserId) -> Result<Option<String>>;
|
||||
fn get_latest_backup_version(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
fn get_latest_backup(&self, user_id: &UserId)
|
||||
-> Result<Option<(String, Raw<BackupAlgorithm>)>>;
|
||||
fn get_latest_backup(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<(String, Raw<BackupAlgorithm>)>>;
|
||||
|
||||
fn get_backup(&self, user_id: &UserId, version: &str) -> Result<Option<Raw<BackupAlgorithm>>>;
|
||||
fn get_backup(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
) -> Result<Option<Raw<BackupAlgorithm>>>;
|
||||
|
||||
fn add_key(
|
||||
&self,
|
||||
|
|
@ -66,7 +76,12 @@ pub(crate) trait Data: Send + Sync {
|
|||
|
||||
fn delete_all_keys(&self, user_id: &UserId, version: &str) -> Result<()>;
|
||||
|
||||
fn delete_room_keys(&self, user_id: &UserId, version: &str, room_id: &RoomId) -> Result<()>;
|
||||
fn delete_room_keys(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
) -> Result<()>;
|
||||
|
||||
fn delete_room_key(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ mod data;
|
|||
use std::io::Cursor;
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use crate::{services, Result};
|
||||
use image::imageops::FilterType;
|
||||
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
};
|
||||
|
||||
use crate::{services, Result};
|
||||
|
||||
pub(crate) struct FileMeta {
|
||||
pub(crate) content_disposition: Option<String>,
|
||||
pub(crate) content_type: Option<String>,
|
||||
|
|
@ -31,9 +30,13 @@ impl Service {
|
|||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
// Width, Height = 0 if it's not a thumbnail
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, 0, 0, content_disposition, content_type)?;
|
||||
let key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
0,
|
||||
0,
|
||||
content_disposition,
|
||||
content_type,
|
||||
)?;
|
||||
|
||||
let path = services().globals.get_media_file(&key);
|
||||
let mut f = File::create(path).await?;
|
||||
|
|
@ -52,9 +55,13 @@ impl Service {
|
|||
height: u32,
|
||||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
let key =
|
||||
self.db
|
||||
.create_file_metadata(mxc, width, height, content_disposition, content_type)?;
|
||||
let key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
width,
|
||||
height,
|
||||
content_disposition,
|
||||
content_type,
|
||||
)?;
|
||||
|
||||
let path = services().globals.get_media_file(&key);
|
||||
let mut f = File::create(path).await?;
|
||||
|
|
@ -84,9 +91,12 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns width, height of the thumbnail and whether it should be cropped. Returns None when
|
||||
/// the server should send the original file.
|
||||
fn thumbnail_properties(width: u32, height: u32) -> Option<(u32, u32, bool)> {
|
||||
/// Returns width, height of the thumbnail and whether it should be cropped.
|
||||
/// Returns None when the server should send the original file.
|
||||
fn thumbnail_properties(
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<(u32, u32, bool)> {
|
||||
match (width, height) {
|
||||
(0..=32, 0..=32) => Some((32, 32, true)),
|
||||
(0..=96, 0..=96) => Some((96, 96, true)),
|
||||
|
|
@ -102,11 +112,15 @@ impl Service {
|
|||
/// Here's an example on how it works:
|
||||
///
|
||||
/// - Client requests an image with width=567, height=567
|
||||
/// - Server rounds that up to (800, 600), so it doesn't have to save too many thumbnails
|
||||
/// - Server rounds that up again to (958, 600) to fix the aspect ratio (only for width,height>96)
|
||||
/// - Server rounds that up to (800, 600), so it doesn't have to save too
|
||||
/// many thumbnails
|
||||
/// - Server rounds that up again to (958, 600) to fix the aspect ratio
|
||||
/// (only for width,height>96)
|
||||
/// - Server creates the thumbnail and sends it to the user
|
||||
///
|
||||
/// For width,height <= 96 the server uses another thumbnailing algorithm which crops the image afterwards.
|
||||
/// For width,height <= 96 the server uses another thumbnailing algorithm
|
||||
/// which crops the image afterwards.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) async fn get_thumbnail(
|
||||
&self,
|
||||
mxc: String,
|
||||
|
|
@ -154,7 +168,8 @@ impl Service {
|
|||
} else {
|
||||
let (exact_width, exact_height) = {
|
||||
// Copied from image::dynimage::resize_dimensions
|
||||
let use_width = (u64::from(width) * u64::from(original_height))
|
||||
let use_width = (u64::from(width)
|
||||
* u64::from(original_height))
|
||||
<= (u64::from(original_width) * u64::from(height));
|
||||
let intermediate = if use_width {
|
||||
u64::from(original_height) * u64::from(width)
|
||||
|
|
@ -165,21 +180,31 @@ impl Service {
|
|||
};
|
||||
if use_width {
|
||||
if intermediate <= u64::from(::std::u32::MAX) {
|
||||
(width, intermediate.try_into().unwrap_or(u32::MAX))
|
||||
(
|
||||
width,
|
||||
intermediate.try_into().unwrap_or(u32::MAX),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
(u64::from(width) * u64::from(::std::u32::MAX) / intermediate)
|
||||
(u64::from(width)
|
||||
* u64::from(::std::u32::MAX)
|
||||
/ intermediate)
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX),
|
||||
::std::u32::MAX,
|
||||
)
|
||||
}
|
||||
} else if intermediate <= u64::from(::std::u32::MAX) {
|
||||
(intermediate.try_into().unwrap_or(u32::MAX), height)
|
||||
(
|
||||
intermediate.try_into().unwrap_or(u32::MAX),
|
||||
height,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
::std::u32::MAX,
|
||||
(u64::from(height) * u64::from(::std::u32::MAX) / intermediate)
|
||||
(u64::from(height)
|
||||
* u64::from(::std::u32::MAX)
|
||||
/ intermediate)
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX),
|
||||
)
|
||||
|
|
@ -195,7 +220,8 @@ impl Service {
|
|||
image::ImageOutputFormat::Png,
|
||||
)?;
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
// Save thumbnail in database so we don't have to generate it
|
||||
// again next time
|
||||
let thumbnail_key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
width,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,31 @@
|
|||
use crate::Error;
|
||||
use std::{cmp::Ordering, collections::BTreeMap, sync::Arc};
|
||||
|
||||
use ruma::{
|
||||
canonical_json::redact_content_in_place,
|
||||
events::{
|
||||
room::{member::RoomMemberEventContent, redaction::RoomRedactionEventContent},
|
||||
room::{
|
||||
member::RoomMemberEventContent,
|
||||
redaction::RoomRedactionEventContent,
|
||||
},
|
||||
space::child::HierarchySpaceChildEvent,
|
||||
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, TimelineEventType,
|
||||
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent,
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
|
||||
AnyTimelineEvent, StateEvent, TimelineEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UInt, UserId,
|
||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId,
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
||||
RoomVersionId, UInt, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{
|
||||
json,
|
||||
value::{to_raw_value, RawValue as RawJsonValue},
|
||||
};
|
||||
use std::{cmp::Ordering, collections::BTreeMap, sync::Arc};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Content hashes of a PDU.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct EventHash {
|
||||
|
|
@ -61,10 +68,18 @@ impl PduEvent {
|
|||
) -> crate::Result<()> {
|
||||
self.unsigned = None;
|
||||
|
||||
let mut content = serde_json::from_str(self.content.get())
|
||||
.map_err(|_| Error::bad_database("PDU in db has invalid content."))?;
|
||||
redact_content_in_place(&mut content, &room_version_id, self.kind.to_string())
|
||||
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
|
||||
let mut content =
|
||||
serde_json::from_str(self.content.get()).map_err(|_| {
|
||||
Error::bad_database("PDU in db has invalid content.")
|
||||
})?;
|
||||
redact_content_in_place(
|
||||
&mut content,
|
||||
&room_version_id,
|
||||
self.kind.to_string(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
Error::Redaction(self.sender.server_name().to_owned(), e)
|
||||
})?;
|
||||
|
||||
self.unsigned = Some(to_raw_value(&json!({
|
||||
"redacted_because": serde_json::to_value(reason).expect("to_value(PduEvent) always works")
|
||||
|
|
@ -78,10 +93,12 @@ impl PduEvent {
|
|||
pub(crate) fn remove_transaction_id(&mut self) -> crate::Result<()> {
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
let mut unsigned: BTreeMap<String, Box<RawJsonValue>> =
|
||||
serde_json::from_str(unsigned.get())
|
||||
.map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?;
|
||||
serde_json::from_str(unsigned.get()).map_err(|_| {
|
||||
Error::bad_database("Invalid unsigned in pdu event")
|
||||
})?;
|
||||
unsigned.remove("transaction_id");
|
||||
self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||
self.unsigned =
|
||||
Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -91,31 +108,45 @@ impl PduEvent {
|
|||
let mut unsigned: BTreeMap<String, Box<RawJsonValue>> = self
|
||||
.unsigned
|
||||
.as_ref()
|
||||
.map_or_else(|| Ok(BTreeMap::new()), |u| serde_json::from_str(u.get()))
|
||||
.map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?;
|
||||
.map_or_else(
|
||||
|| Ok(BTreeMap::new()),
|
||||
|u| serde_json::from_str(u.get()),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid unsigned in pdu event")
|
||||
})?;
|
||||
|
||||
unsigned.insert("age".to_owned(), to_raw_value(&1).unwrap());
|
||||
self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||
self.unsigned =
|
||||
Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copies the `redacts` property of the event to the `content` dict and vice-versa.
|
||||
/// Copies the `redacts` property of the event to the `content` dict and
|
||||
/// vice-versa.
|
||||
///
|
||||
/// This follows the specification's
|
||||
/// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property):
|
||||
///
|
||||
/// > For backwards-compatibility with older clients, servers should add a redacts
|
||||
/// > property to the top level of m.room.redaction events in when serving such events
|
||||
/// > For backwards-compatibility with older clients, servers should add a
|
||||
/// > redacts
|
||||
/// > property to the top level of m.room.redaction events in when serving
|
||||
/// > such events
|
||||
/// > over the Client-Server API.
|
||||
/// >
|
||||
/// > For improved compatibility with newer clients, servers should add a redacts property
|
||||
/// > to the content of m.room.redaction events in older room versions when serving
|
||||
/// > For improved compatibility with newer clients, servers should add a
|
||||
/// > redacts property
|
||||
/// > to the content of m.room.redaction events in older room versions when
|
||||
/// > serving
|
||||
/// > such events over the Client-Server API.
|
||||
pub(crate) fn copy_redacts(&self) -> (Option<Arc<EventId>>, Box<RawJsonValue>) {
|
||||
pub(crate) fn copy_redacts(
|
||||
&self,
|
||||
) -> (Option<Arc<EventId>>, Box<RawJsonValue>) {
|
||||
if self.kind == TimelineEventType::RoomRedaction {
|
||||
if let Ok(mut content) =
|
||||
serde_json::from_str::<RoomRedactionEventContent>(self.content.get())
|
||||
if let Ok(mut content) = serde_json::from_str::<
|
||||
RoomRedactionEventContent,
|
||||
>(self.content.get())
|
||||
{
|
||||
if let Some(redacts) = content.redacts {
|
||||
return (Some(redacts.into()), self.content.clone());
|
||||
|
|
@ -123,7 +154,9 @@ impl PduEvent {
|
|||
content.redacts = Some(redacts.into());
|
||||
return (
|
||||
self.redacts.clone(),
|
||||
to_raw_value(&content).expect("Must be valid, we only added redacts field"),
|
||||
to_raw_value(&content).expect(
|
||||
"Must be valid, we only added redacts field",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -281,7 +314,9 @@ impl PduEvent {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn to_stripped_spacechild_state_event(&self) -> Raw<HierarchySpaceChildEvent> {
|
||||
pub(crate) fn to_stripped_spacechild_state_event(
|
||||
&self,
|
||||
) -> Raw<HierarchySpaceChildEvent> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
|
|
@ -294,7 +329,9 @@ impl PduEvent {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn to_member_event(&self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
||||
pub(crate) fn to_member_event(
|
||||
&self,
|
||||
) -> Raw<StateEvent<RoomMemberEventContent>> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
|
|
@ -318,16 +355,16 @@ impl PduEvent {
|
|||
pub(crate) fn convert_to_outgoing_federation_event(
|
||||
mut pdu_json: CanonicalJsonObject,
|
||||
) -> Box<RawJsonValue> {
|
||||
if let Some(unsigned) = pdu_json
|
||||
.get_mut("unsigned")
|
||||
.and_then(|val| val.as_object_mut())
|
||||
if let Some(unsigned) =
|
||||
pdu_json.get_mut("unsigned").and_then(|val| val.as_object_mut())
|
||||
{
|
||||
unsigned.remove("transaction_id");
|
||||
}
|
||||
|
||||
pdu_json.remove("event_id");
|
||||
|
||||
to_raw_value(&pdu_json).expect("CanonicalJson is valid serde_json::Value")
|
||||
to_raw_value(&pdu_json)
|
||||
.expect("CanonicalJson is valid serde_json::Value")
|
||||
}
|
||||
|
||||
pub(crate) fn from_id_val(
|
||||
|
|
@ -374,11 +411,15 @@ impl state_res::Event for PduEvent {
|
|||
self.state_key.as_deref()
|
||||
}
|
||||
|
||||
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
fn prev_events(
|
||||
&self,
|
||||
) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
Box::new(self.prev_events.iter())
|
||||
}
|
||||
|
||||
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
fn auth_events(
|
||||
&self,
|
||||
) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||
Box::new(self.auth_events.iter())
|
||||
}
|
||||
|
||||
|
|
@ -408,15 +449,17 @@ impl Ord for PduEvent {
|
|||
|
||||
/// Generates a correct eventId for the incoming pdu.
|
||||
///
|
||||
/// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap<String, CanonicalJsonValue>`.
|
||||
/// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap<String,
|
||||
/// CanonicalJsonValue>`.
|
||||
pub(crate) fn gen_event_id_canonical_json(
|
||||
pdu: &RawJsonValue,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> crate::Result<(OwnedEventId, CanonicalJsonObject)> {
|
||||
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
|
||||
warn!("Error parsing incoming event {:?}: {:?}", pdu, e);
|
||||
Error::BadServerResponse("Invalid PDU in server response")
|
||||
})?;
|
||||
let value: CanonicalJsonObject =
|
||||
serde_json::from_str(pdu.get()).map_err(|e| {
|
||||
warn!("Error parsing incoming event {:?}: {:?}", pdu, e);
|
||||
Error::BadServerResponse("Invalid PDU in server response")
|
||||
})?;
|
||||
|
||||
let event_id = format!(
|
||||
"${}",
|
||||
|
|
|
|||
|
|
@ -1,27 +1,34 @@
|
|||
mod data;
|
||||
pub(crate) use data::Data;
|
||||
use ruma::{events::AnySyncTimelineEvent, push::PushConditionPowerLevelsCtx};
|
||||
use std::{fmt::Debug, mem};
|
||||
|
||||
use crate::{services, Error, PduEvent, Result};
|
||||
use bytes::BytesMut;
|
||||
pub(crate) use data::Data;
|
||||
use ruma::{
|
||||
api::{
|
||||
client::push::{set_pusher, Pusher, PusherKind},
|
||||
push_gateway::send_event_notification::{
|
||||
self,
|
||||
v1::{Device, Notification, NotificationCounts, NotificationPriority},
|
||||
v1::{
|
||||
Device, Notification, NotificationCounts, NotificationPriority,
|
||||
},
|
||||
},
|
||||
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||
},
|
||||
events::{room::power_levels::RoomPowerLevelsEventContent, StateEventType, TimelineEventType},
|
||||
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
||||
events::{
|
||||
room::power_levels::RoomPowerLevelsEventContent, AnySyncTimelineEvent,
|
||||
StateEventType, TimelineEventType,
|
||||
},
|
||||
push::{
|
||||
Action, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat,
|
||||
Ruleset, Tweak,
|
||||
},
|
||||
serde::Raw,
|
||||
uint, RoomId, UInt, UserId,
|
||||
};
|
||||
|
||||
use std::{fmt::Debug, mem};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{services, Error, PduEvent, Result};
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
}
|
||||
|
|
@ -35,7 +42,11 @@ impl Service {
|
|||
self.db.set_pusher(sender, pusher)
|
||||
}
|
||||
|
||||
pub(crate) fn get_pusher(&self, sender: &UserId, pushkey: &str) -> Result<Option<Pusher>> {
|
||||
pub(crate) fn get_pusher(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
pushkey: &str,
|
||||
) -> Result<Option<Pusher>> {
|
||||
self.db.get_pusher(sender, pushkey)
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +54,10 @@ impl Service {
|
|||
self.db.get_pushers(sender)
|
||||
}
|
||||
|
||||
pub(crate) fn get_pushkeys(&self, sender: &UserId) -> Box<dyn Iterator<Item = Result<String>>> {
|
||||
pub(crate) fn get_pushkeys(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<String>>> {
|
||||
self.db.get_pushkeys(sender)
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +87,8 @@ impl Service {
|
|||
let reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||
|
||||
let url = reqwest_request.url().clone();
|
||||
let response = services()
|
||||
.globals
|
||||
.default_client()
|
||||
.execute(reqwest_request)
|
||||
.await;
|
||||
let response =
|
||||
services().globals.default_client().execute(reqwest_request).await;
|
||||
|
||||
match response {
|
||||
Ok(mut response) => {
|
||||
|
|
@ -119,11 +130,16 @@ impl Service {
|
|||
"Push gateway returned invalid response bytes {}\n{}",
|
||||
destination, url
|
||||
);
|
||||
Error::BadServerResponse("Push gateway returned bad response.")
|
||||
Error::BadServerResponse(
|
||||
"Push gateway returned bad response.",
|
||||
)
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not send request to pusher {}: {}", destination, e);
|
||||
warn!(
|
||||
"Could not send request to pusher {}: {}",
|
||||
destination, e
|
||||
);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
|
|
@ -146,8 +162,9 @@ impl Service {
|
|||
.state_accessor
|
||||
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
.map(|ev| {
|
||||
serde_json::from_str(ev.content.get())
|
||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
||||
serde_json::from_str(ev.content.get()).map_err(|_| {
|
||||
Error::bad_database("invalid m.room.power_levels event")
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
|
@ -228,11 +245,16 @@ impl Service {
|
|||
PusherKind::Http(http) => {
|
||||
// TODO:
|
||||
// Two problems with this
|
||||
// 1. if "event_id_only" is the only format kind it seems we should never add more info
|
||||
// 1. if "event_id_only" is the only format kind it seems we
|
||||
// should never add more info
|
||||
// 2. can pusher/devices have conflicting formats
|
||||
let event_id_only = http.format == Some(PushFormat::EventIdOnly);
|
||||
let event_id_only =
|
||||
http.format == Some(PushFormat::EventIdOnly);
|
||||
|
||||
let mut device = Device::new(pusher.ids.app_id.clone(), pusher.ids.pushkey.clone());
|
||||
let mut device = Device::new(
|
||||
pusher.ids.app_id.clone(),
|
||||
pusher.ids.pushkey.clone(),
|
||||
);
|
||||
device.data.default_payload = http.default_payload.clone();
|
||||
device.data.format = http.format.clone();
|
||||
|
||||
|
|
@ -251,32 +273,43 @@ impl Service {
|
|||
notifi.counts = NotificationCounts::new(unread, uint!(0));
|
||||
|
||||
if event.kind == TimelineEventType::RoomEncrypted
|
||||
|| tweaks
|
||||
.iter()
|
||||
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
|
||||
|| tweaks.iter().any(|t| {
|
||||
matches!(t, Tweak::Highlight(true) | Tweak::Sound(_))
|
||||
})
|
||||
{
|
||||
notifi.prio = NotificationPriority::High;
|
||||
}
|
||||
|
||||
if event_id_only {
|
||||
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
||||
.await?;
|
||||
self.send_request(
|
||||
&http.url,
|
||||
send_event_notification::v1::Request::new(notifi),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
notifi.sender = Some(event.sender.clone());
|
||||
notifi.event_type = Some(event.kind.clone());
|
||||
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
|
||||
notifi.content =
|
||||
serde_json::value::to_raw_value(&event.content).ok();
|
||||
|
||||
if event.kind == TimelineEventType::RoomMember {
|
||||
notifi.user_is_target =
|
||||
event.state_key.as_deref() == Some(event.sender.as_str());
|
||||
notifi.user_is_target = event.state_key.as_deref()
|
||||
== Some(event.sender.as_str());
|
||||
}
|
||||
|
||||
notifi.sender_display_name = services().users.displayname(&event.sender)?;
|
||||
notifi.sender_display_name =
|
||||
services().users.displayname(&event.sender)?;
|
||||
|
||||
notifi.room_name = services().rooms.state_accessor.get_name(&event.room_id)?;
|
||||
notifi.room_name = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(&event.room_id)?;
|
||||
|
||||
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
||||
.await?;
|
||||
self.send_request(
|
||||
&http.url,
|
||||
send_event_notification::v1::Request::new(notifi),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,16 +1,27 @@
|
|||
use crate::Result;
|
||||
use ruma::{
|
||||
api::client::push::{set_pusher, Pusher},
|
||||
UserId,
|
||||
};
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn set_pusher(&self, sender: &UserId, pusher: set_pusher::v3::PusherAction) -> Result<()>;
|
||||
use crate::Result;
|
||||
|
||||
fn get_pusher(&self, sender: &UserId, pushkey: &str) -> Result<Option<Pusher>>;
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn set_pusher(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
pusher: set_pusher::v3::PusherAction,
|
||||
) -> Result<()>;
|
||||
|
||||
fn get_pusher(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
pushkey: &str,
|
||||
) -> Result<Option<Pusher>>;
|
||||
|
||||
fn get_pushers(&self, sender: &UserId) -> Result<Vec<Pusher>>;
|
||||
|
||||
fn get_pushkeys<'a>(&'a self, sender: &UserId)
|
||||
-> Box<dyn Iterator<Item = Result<String>> + 'a>;
|
||||
fn get_pushkeys<'a>(
|
||||
&'a self,
|
||||
sender: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<String>> + 'a>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Result;
|
||||
use ruma::{OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Creates or updates the alias to the given room id.
|
||||
fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> Result<()>;
|
||||
|
|
@ -9,7 +10,10 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn remove_alias(&self, alias: &RoomAliasId) -> Result<()>;
|
||||
|
||||
/// Looks up the roomid for the given alias.
|
||||
fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<Option<OwnedRoomId>>;
|
||||
fn resolve_local_alias(
|
||||
&self,
|
||||
alias: &RoomAliasId,
|
||||
) -> Result<Option<OwnedRoomId>>;
|
||||
|
||||
/// Returns all local aliases that point to the given room
|
||||
fn local_aliases_for_room<'a>(
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ impl Service {
|
|||
|
||||
let mut i = 0;
|
||||
for id in starting_events {
|
||||
let short = services().rooms.short.get_or_create_shorteventid(&id)?;
|
||||
let short =
|
||||
services().rooms.short.get_or_create_shorteventid(&id)?;
|
||||
// I'm afraid to change this in case there is accidental reliance on
|
||||
// the truncation
|
||||
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
|
||||
|
|
@ -64,7 +65,8 @@ impl Service {
|
|||
continue;
|
||||
}
|
||||
|
||||
let chunk_key: Vec<u64> = chunk.iter().map(|(short, _)| short).copied().collect();
|
||||
let chunk_key: Vec<u64> =
|
||||
chunk.iter().map(|(short, _)| short).copied().collect();
|
||||
if let Some(cached) = services()
|
||||
.rooms
|
||||
.auth_chain
|
||||
|
|
@ -90,11 +92,13 @@ impl Service {
|
|||
chunk_cache.extend(cached.iter().copied());
|
||||
} else {
|
||||
misses2 += 1;
|
||||
let auth_chain = Arc::new(self.get_auth_chain_inner(room_id, &event_id)?);
|
||||
services()
|
||||
.rooms
|
||||
.auth_chain
|
||||
.cache_auth_chain(vec![sevent_id], Arc::clone(&auth_chain))?;
|
||||
let auth_chain = Arc::new(
|
||||
self.get_auth_chain_inner(room_id, &event_id)?,
|
||||
);
|
||||
services().rooms.auth_chain.cache_auth_chain(
|
||||
vec![sevent_id],
|
||||
Arc::clone(&auth_chain),
|
||||
)?;
|
||||
debug!(
|
||||
event_id = ?event_id,
|
||||
chain_length = ?auth_chain.len(),
|
||||
|
|
@ -129,13 +133,17 @@ impl Service {
|
|||
"Auth chain stats",
|
||||
);
|
||||
|
||||
Ok(full_auth_chain
|
||||
.into_iter()
|
||||
.filter_map(move |sid| services().rooms.short.get_eventid_from_short(sid).ok()))
|
||||
Ok(full_auth_chain.into_iter().filter_map(move |sid| {
|
||||
services().rooms.short.get_eventid_from_short(sid).ok()
|
||||
}))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, event_id))]
|
||||
fn get_auth_chain_inner(&self, room_id: &RoomId, event_id: &EventId) -> Result<HashSet<u64>> {
|
||||
fn get_auth_chain_inner(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_id: &EventId,
|
||||
) -> Result<HashSet<u64>> {
|
||||
let mut todo = vec![Arc::from(event_id)];
|
||||
let mut found = HashSet::new();
|
||||
|
||||
|
|
@ -143,7 +151,10 @@ impl Service {
|
|||
match services().rooms.timeline.get_pdu(&event_id) {
|
||||
Ok(Some(pdu)) => {
|
||||
if pdu.room_id != room_id {
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Evil event in db"));
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Evil event in db",
|
||||
));
|
||||
}
|
||||
for auth_event in &pdu.auth_events {
|
||||
let sauthevent = services()
|
||||
|
|
@ -158,10 +169,17 @@ impl Service {
|
|||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!(?event_id, "Could not find pdu mentioned in auth events");
|
||||
warn!(
|
||||
?event_id,
|
||||
"Could not find pdu mentioned in auth events"
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
error!(?event_id, ?error, "Could not load event in auth chain");
|
||||
error!(
|
||||
?event_id,
|
||||
?error,
|
||||
"Could not load event in auth chain"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use crate::Result;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn get_cached_eventid_authchain(
|
||||
&self,
|
||||
shorteventid: &[u64],
|
||||
) -> Result<Option<Arc<HashSet<u64>>>>;
|
||||
fn cache_auth_chain(&self, shorteventid: Vec<u64>, auth_chain: Arc<HashSet<u64>>)
|
||||
-> Result<()>;
|
||||
fn cache_auth_chain(
|
||||
&self,
|
||||
shorteventid: Vec<u64>,
|
||||
auth_chain: Arc<HashSet<u64>>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Result;
|
||||
use ruma::{OwnedRoomId, RoomId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Adds the room to the public room directory
|
||||
fn set_public(&self, room_id: &RoomId) -> Result<()>;
|
||||
|
|
@ -12,5 +13,7 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn is_public_room(&self, room_id: &RoomId) -> Result<bool>;
|
||||
|
||||
/// Returns the unsorted public room directory
|
||||
fn public_rooms<'a>(&'a self) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||
fn public_rooms<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use ruma::{
|
||||
events::receipt::ReceiptEvent, serde::Raw, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
use ruma::{events::receipt::ReceiptEvent, serde::Raw, OwnedUserId, RoomId, UserId};
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Replaces the previous read receipt.
|
||||
|
|
@ -10,7 +13,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
event: ReceiptEvent,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns an iterator over the most recent read receipts in a room that happened after the event with id `since`.
|
||||
/// Returns an iterator over the most recent read receipts in a room that
|
||||
/// happened after the event with id `since`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn readreceipts_since<'a>(
|
||||
&'a self,
|
||||
|
|
@ -27,11 +31,24 @@ pub(crate) trait Data: Send + Sync {
|
|||
>;
|
||||
|
||||
/// Sets a private read marker at `count`.
|
||||
fn private_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64) -> Result<()>;
|
||||
fn private_read_set(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
count: u64,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns the private read marker.
|
||||
fn private_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>>;
|
||||
fn private_read_get(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<u64>>;
|
||||
|
||||
/// Returns the count of the last typing update in this room.
|
||||
fn last_privateread_update(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64>;
|
||||
fn last_privateread_update(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<u64>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
events::{typing::TypingEventContent, SyncEphemeralRoomEvent},
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use tracing::trace;
|
||||
|
||||
|
|
@ -10,15 +11,16 @@ use crate::{services, utils, Result};
|
|||
|
||||
pub(crate) struct Service {
|
||||
// u64 is unix timestamp of timeout
|
||||
pub(crate) typing: RwLock<BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, u64>>>,
|
||||
pub(crate) typing:
|
||||
RwLock<BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, u64>>>,
|
||||
// timestamp of the last change to typing users
|
||||
pub(crate) last_typing_update: RwLock<BTreeMap<OwnedRoomId, u64>>,
|
||||
pub(crate) typing_update_sender: broadcast::Sender<OwnedRoomId>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Sets a user as typing until the timeout timestamp is reached or `roomtyping_remove` is
|
||||
/// called.
|
||||
/// Sets a user as typing until the timeout timestamp is reached or
|
||||
/// `roomtyping_remove` is called.
|
||||
pub(crate) async fn typing_add(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
|
|
@ -36,13 +38,20 @@ impl Service {
|
|||
.await
|
||||
.insert(room_id.to_owned(), services().globals.next_count()?);
|
||||
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
|
||||
trace!("receiver found what it was looking for and is no longer interested");
|
||||
trace!(
|
||||
"receiver found what it was looking for and is no longer \
|
||||
interested"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a user from typing before the timeout is reached.
|
||||
pub(crate) async fn typing_remove(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
||||
pub(crate) async fn typing_remove(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<()> {
|
||||
self.typing
|
||||
.write()
|
||||
.await
|
||||
|
|
@ -54,7 +63,10 @@ impl Service {
|
|||
.await
|
||||
.insert(room_id.to_owned(), services().globals.next_count()?);
|
||||
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
|
||||
trace!("receiver found what it was looking for and is no longer interested");
|
||||
trace!(
|
||||
"receiver found what it was looking for and is no longer \
|
||||
interested"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -97,14 +109,20 @@ impl Service {
|
|||
.await
|
||||
.insert(room_id.to_owned(), services().globals.next_count()?);
|
||||
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
|
||||
trace!("receiver found what it was looking for and is no longer interested");
|
||||
trace!(
|
||||
"receiver found what it was looking for and is no longer \
|
||||
interested"
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the count of the last typing update in this room.
|
||||
pub(crate) async fn last_typing_update(&self, room_id: &RoomId) -> Result<u64> {
|
||||
pub(crate) async fn last_typing_update(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<u64> {
|
||||
self.typings_maintain(room_id).await?;
|
||||
Ok(self
|
||||
.last_typing_update
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,16 +5,19 @@ pub(crate) use data::Data;
|
|||
use ruma::{DeviceId, OwnedDeviceId, OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
use super::timeline::PduCount;
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) lazy_load_waiting:
|
||||
Mutex<HashMap<(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount), HashSet<OwnedUserId>>>,
|
||||
pub(crate) lazy_load_waiting: Mutex<
|
||||
HashMap<
|
||||
(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount),
|
||||
HashSet<OwnedUserId>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
|
|
@ -26,8 +29,7 @@ impl Service {
|
|||
room_id: &RoomId,
|
||||
ll_user: &UserId,
|
||||
) -> Result<bool> {
|
||||
self.db
|
||||
.lazy_load_was_sent_before(user_id, device_id, room_id, ll_user)
|
||||
self.db.lazy_load_was_sent_before(user_id, device_id, room_id, ll_user)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Result;
|
||||
use ruma::{DeviceId, RoomId, UserId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn lazy_load_was_sent_before(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use crate::Result;
|
||||
use ruma::{OwnedRoomId, RoomId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Checks if a room exists.
|
||||
fn exists(&self, room_id: &RoomId) -> Result<bool>;
|
||||
fn iter_ids<'a>(&'a self) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||
fn iter_ids<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||
fn is_disabled(&self, room_id: &RoomId) -> Result<bool>;
|
||||
fn disable_room(&self, room_id: &RoomId, disabled: bool) -> Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,15 @@ use crate::{PduEvent, Result};
|
|||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Returns the pdu from the outlier tree.
|
||||
fn get_outlier_pdu_json(&self, event_id: &EventId) -> Result<Option<CanonicalJsonObject>>;
|
||||
fn get_outlier_pdu_json(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<CanonicalJsonObject>>;
|
||||
fn get_outlier_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>>;
|
||||
/// Append the PDU as an outlier.
|
||||
fn add_pdu_outlier(&self, event_id: &EventId, pdu: &CanonicalJsonObject) -> Result<()>;
|
||||
fn add_pdu_outlier(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
pdu: &CanonicalJsonObject,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ use ruma::{
|
|||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{services, PduEvent, Result};
|
||||
|
||||
use super::timeline::PduCount;
|
||||
use crate::{services, PduEvent, Result};
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
|
|
@ -29,9 +28,15 @@ struct ExtractRelatesToEventId {
|
|||
|
||||
impl Service {
|
||||
#[tracing::instrument(skip(self, from, to))]
|
||||
pub(crate) fn add_relation(&self, from: PduCount, to: PduCount) -> Result<()> {
|
||||
pub(crate) fn add_relation(
|
||||
&self,
|
||||
from: PduCount,
|
||||
to: PduCount,
|
||||
) -> Result<()> {
|
||||
match (from, to) {
|
||||
(PduCount::Normal(f), PduCount::Normal(t)) => self.db.add_relation(f, t),
|
||||
(PduCount::Normal(f), PduCount::Normal(t)) => {
|
||||
self.db.add_relation(f, t)
|
||||
}
|
||||
_ => {
|
||||
// TODO: Relations with backfilled pdus
|
||||
|
||||
|
|
@ -42,6 +47,7 @@ impl Service {
|
|||
|
||||
#[allow(
|
||||
clippy::too_many_arguments,
|
||||
clippy::too_many_lines,
|
||||
// Allowed because this function uses `services()`
|
||||
clippy::unused_self,
|
||||
)]
|
||||
|
|
@ -68,15 +74,17 @@ impl Service {
|
|||
.relations_until(sender_user, room_id, target, from)?
|
||||
.filter(|r| {
|
||||
r.as_ref().map_or(true, |(_, pdu)| {
|
||||
filter_event_type.as_ref().map_or(true, |t| &&pdu.kind == t)
|
||||
&& if let Ok(content) =
|
||||
serde_json::from_str::<ExtractRelatesToEventId>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
{
|
||||
filter_rel_type
|
||||
.as_ref()
|
||||
.map_or(true, |r| &&content.relates_to.rel_type == r)
|
||||
filter_event_type
|
||||
.as_ref()
|
||||
.map_or(true, |t| &&pdu.kind == t)
|
||||
&& if let Ok(content) = serde_json::from_str::<
|
||||
ExtractRelatesToEventId,
|
||||
>(
|
||||
pdu.content.get()
|
||||
) {
|
||||
filter_rel_type.as_ref().map_or(true, |r| {
|
||||
&&content.relates_to.rel_type == r
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -88,13 +96,18 @@ impl Service {
|
|||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, room_id, &pdu.event_id)
|
||||
.user_can_see_event(
|
||||
sender_user,
|
||||
room_id,
|
||||
&pdu.event_id,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.take_while(|&(k, _)| Some(k) != to)
|
||||
.collect();
|
||||
|
||||
next_token = events_after.last().map(|(count, _)| count).copied();
|
||||
next_token =
|
||||
events_after.last().map(|(count, _)| count).copied();
|
||||
|
||||
// Reversed because relations are always most recent first
|
||||
let events_after: Vec<_> = events_after
|
||||
|
|
@ -116,15 +129,17 @@ impl Service {
|
|||
.relations_until(sender_user, room_id, target, from)?
|
||||
.filter(|r| {
|
||||
r.as_ref().map_or(true, |(_, pdu)| {
|
||||
filter_event_type.as_ref().map_or(true, |t| &&pdu.kind == t)
|
||||
&& if let Ok(content) =
|
||||
serde_json::from_str::<ExtractRelatesToEventId>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
{
|
||||
filter_rel_type
|
||||
.as_ref()
|
||||
.map_or(true, |r| &&content.relates_to.rel_type == r)
|
||||
filter_event_type
|
||||
.as_ref()
|
||||
.map_or(true, |t| &&pdu.kind == t)
|
||||
&& if let Ok(content) = serde_json::from_str::<
|
||||
ExtractRelatesToEventId,
|
||||
>(
|
||||
pdu.content.get()
|
||||
) {
|
||||
filter_rel_type.as_ref().map_or(true, |r| {
|
||||
&&content.relates_to.rel_type == r
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -136,13 +151,18 @@ impl Service {
|
|||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, room_id, &pdu.event_id)
|
||||
.user_can_see_event(
|
||||
sender_user,
|
||||
room_id,
|
||||
&pdu.event_id,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.take_while(|&(k, _)| Some(k) != to)
|
||||
.collect();
|
||||
|
||||
next_token = events_before.last().map(|(count, _)| count).copied();
|
||||
next_token =
|
||||
events_before.last().map(|(count, _)| count).copied();
|
||||
|
||||
let events_before: Vec<_> = events_before
|
||||
.into_iter()
|
||||
|
|
@ -165,7 +185,8 @@ impl Service {
|
|||
target: &'a EventId,
|
||||
until: PduCount,
|
||||
) -> Result<impl Iterator<Item = Result<(PduCount, PduEvent)>> + 'a> {
|
||||
let room_id = services().rooms.short.get_or_create_shortroomid(room_id)?;
|
||||
let room_id =
|
||||
services().rooms.short.get_or_create_shortroomid(room_id)?;
|
||||
let target = match services().rooms.timeline.get_pdu_count(target)? {
|
||||
Some(PduCount::Normal(c)) => c,
|
||||
// TODO: Support backfilled relations
|
||||
|
|
@ -185,17 +206,27 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool> {
|
||||
pub(crate) fn is_event_referenced(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_id: &EventId,
|
||||
) -> Result<bool> {
|
||||
self.db.is_event_referenced(room_id, event_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()> {
|
||||
pub(crate) fn mark_event_soft_failed(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<()> {
|
||||
self.db.mark_event_soft_failed(event_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn is_event_soft_failed(&self, event_id: &EventId) -> Result<bool> {
|
||||
pub(crate) fn is_event_soft_failed(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<bool> {
|
||||
self.db.is_event_soft_failed(event_id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{service::rooms::timeline::PduCount, PduEvent, Result};
|
||||
use ruma::{EventId, RoomId, UserId};
|
||||
|
||||
use crate::{service::rooms::timeline::PduCount, PduEvent, Result};
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn add_relation(&self, from: u64, to: u64) -> Result<()>;
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
|
@ -13,8 +14,16 @@ pub(crate) trait Data: Send + Sync {
|
|||
target: u64,
|
||||
until: PduCount,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
||||
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()>;
|
||||
fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool>;
|
||||
fn mark_as_referenced(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_ids: &[Arc<EventId>],
|
||||
) -> Result<()>;
|
||||
fn is_event_referenced(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_id: &EventId,
|
||||
) -> Result<bool>;
|
||||
fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()>;
|
||||
fn is_event_soft_failed(&self, event_id: &EventId) -> Result<bool>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
use crate::Result;
|
||||
use ruma::RoomId;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn index_pdu(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()>;
|
||||
fn index_pdu(
|
||||
&self,
|
||||
shortroomid: u64,
|
||||
pdu_id: &[u8],
|
||||
message_body: &str,
|
||||
) -> Result<()>;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn search_pdus<'a>(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::Result;
|
||||
use ruma::{events::StateEventType, EventId, RoomId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn get_or_create_shorteventid(&self, event_id: &EventId) -> Result<u64>;
|
||||
|
||||
|
|
@ -18,12 +19,19 @@ pub(crate) trait Data: Send + Sync {
|
|||
state_key: &str,
|
||||
) -> Result<u64>;
|
||||
|
||||
fn get_eventid_from_short(&self, shorteventid: u64) -> Result<Arc<EventId>>;
|
||||
fn get_eventid_from_short(&self, shorteventid: u64)
|
||||
-> Result<Arc<EventId>>;
|
||||
|
||||
fn get_statekey_from_short(&self, shortstatekey: u64) -> Result<(StateEventType, String)>;
|
||||
fn get_statekey_from_short(
|
||||
&self,
|
||||
shortstatekey: u64,
|
||||
) -> Result<(StateEventType, String)>;
|
||||
|
||||
/// Returns `(shortstatehash, already_existed)`
|
||||
fn get_or_create_shortstatehash(&self, state_hash: &[u8]) -> Result<(u64, bool)>;
|
||||
fn get_or_create_shortstatehash(
|
||||
&self,
|
||||
state_hash: &[u8],
|
||||
) -> Result<(u64, bool)>;
|
||||
|
||||
fn get_shortroomid(&self, room_id: &RoomId) -> Result<Option<u64>>;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,12 @@ use ruma::{
|
|||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{self, AllowRule, JoinRule, RoomJoinRulesEventContent},
|
||||
history_visibility::{
|
||||
HistoryVisibility, RoomHistoryVisibilityEventContent,
|
||||
},
|
||||
join_rules::{
|
||||
self, AllowRule, JoinRule, RoomJoinRulesEventContent,
|
||||
},
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
space::child::SpaceChildEventContent,
|
||||
|
|
@ -26,7 +30,6 @@ use ruma::{
|
|||
OwnedRoomId, RoomId, UserId,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{services, Error, PduEvent, Result};
|
||||
|
|
@ -42,7 +45,8 @@ pub(crate) struct CachedSpaceChunk {
|
|||
}
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) roomid_spacechunk_cache: Mutex<LruCache<OwnedRoomId, Option<CachedSpaceChunk>>>,
|
||||
pub(crate) roomid_spacechunk_cache:
|
||||
Mutex<LruCache<OwnedRoomId, Option<CachedSpaceChunk>>>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
|
|
@ -86,9 +90,11 @@ impl Service {
|
|||
{
|
||||
if let Some(cached) = cached {
|
||||
let allowed = match &cached.join_rule {
|
||||
CachedJoinRule::Full(f) => {
|
||||
self.handle_join_rule(f, sender_user, ¤t_room)?
|
||||
}
|
||||
CachedJoinRule::Full(f) => self.handle_join_rule(
|
||||
f,
|
||||
sender_user,
|
||||
¤t_room,
|
||||
)?,
|
||||
};
|
||||
if allowed {
|
||||
if left_to_skip > 0 {
|
||||
|
|
@ -104,10 +110,8 @@ impl Service {
|
|||
continue;
|
||||
}
|
||||
|
||||
if let Some(current_shortstatehash) = services()
|
||||
.rooms
|
||||
.state
|
||||
.get_room_shortstatehash(¤t_room)?
|
||||
if let Some(current_shortstatehash) =
|
||||
services().rooms.state.get_room_shortstatehash(¤t_room)?
|
||||
{
|
||||
let state = services()
|
||||
.rooms
|
||||
|
|
@ -124,16 +128,21 @@ impl Service {
|
|||
continue;
|
||||
}
|
||||
|
||||
let pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&id)?
|
||||
.ok_or_else(|| Error::bad_database("Event in space state not found"))?;
|
||||
let pdu =
|
||||
services().rooms.timeline.get_pdu(&id)?.ok_or_else(
|
||||
|| {
|
||||
Error::bad_database(
|
||||
"Event in space state not found",
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
if serde_json::from_str::<SpaceChildEventContent>(pdu.content.get())
|
||||
.ok()
|
||||
.map(|c| c.via)
|
||||
.map_or(true, |v| v.is_empty())
|
||||
if serde_json::from_str::<SpaceChildEventContent>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
.ok()
|
||||
.map(|c| c.via)
|
||||
.map_or(true, |v| v.is_empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -147,7 +156,11 @@ impl Service {
|
|||
// TODO: Sort children
|
||||
children_ids.reverse();
|
||||
|
||||
let chunk = self.get_room_chunk(sender_user, ¤t_room, children_pdus);
|
||||
let chunk = self.get_room_chunk(
|
||||
sender_user,
|
||||
¤t_room,
|
||||
children_pdus,
|
||||
);
|
||||
if let Ok(chunk) = chunk {
|
||||
if left_to_skip > 0 {
|
||||
left_to_skip -= 1;
|
||||
|
|
@ -157,13 +170,24 @@ impl Service {
|
|||
let join_rule = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(¤t_room, &StateEventType::RoomJoinRules, "")?
|
||||
.room_state_get(
|
||||
¤t_room,
|
||||
&StateEventType::RoomJoinRules,
|
||||
"",
|
||||
)?
|
||||
.map(|s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
||||
.map_err(|e| {
|
||||
error!("Invalid room join rule event in database: {}", e);
|
||||
Error::BadDatabase("Invalid room join rule event in database.")
|
||||
error!(
|
||||
"Invalid room join rule event in \
|
||||
database: {}",
|
||||
e
|
||||
);
|
||||
Error::BadDatabase(
|
||||
"Invalid room join rule event in \
|
||||
database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
|
|
@ -205,7 +229,10 @@ impl Service {
|
|||
)
|
||||
.await
|
||||
{
|
||||
warn!("Got response from {server} for /hierarchy\n{response:?}");
|
||||
warn!(
|
||||
"Got response from {server} for \
|
||||
/hierarchy\n{response:?}"
|
||||
);
|
||||
let chunk = SpaceHierarchyRoomsChunk {
|
||||
canonical_alias: response.room.canonical_alias,
|
||||
name: response.room.name,
|
||||
|
|
@ -250,9 +277,17 @@ impl Service {
|
|||
})
|
||||
}
|
||||
SpaceRoomJoinRule::Public => JoinRule::Public,
|
||||
_ => return Err(Error::BadServerResponse("Unknown join rule")),
|
||||
_ => {
|
||||
return Err(Error::BadServerResponse(
|
||||
"Unknown join rule",
|
||||
))
|
||||
}
|
||||
};
|
||||
if self.handle_join_rule(&join_rule, sender_user, ¤t_room)? {
|
||||
if self.handle_join_rule(
|
||||
&join_rule,
|
||||
sender_user,
|
||||
¤t_room,
|
||||
)? {
|
||||
if left_to_skip > 0 {
|
||||
left_to_skip -= 1;
|
||||
} else {
|
||||
|
|
@ -301,12 +336,18 @@ impl Service {
|
|||
canonical_alias: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCanonicalAlias, "")?
|
||||
.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomCanonicalAlias,
|
||||
"",
|
||||
)?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomCanonicalAliasEventContent| c.alias)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid canonical alias event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid canonical alias event in database.",
|
||||
)
|
||||
})
|
||||
})?,
|
||||
name: services().rooms.state_accessor.get_name(room_id)?,
|
||||
|
|
@ -329,22 +370,34 @@ impl Service {
|
|||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomTopicEventContent| Some(c.topic))
|
||||
.map_err(|_| {
|
||||
error!("Invalid room topic event in database for room {}", room_id);
|
||||
Error::bad_database("Invalid room topic event in database.")
|
||||
error!(
|
||||
"Invalid room topic event in database for \
|
||||
room {}",
|
||||
room_id
|
||||
);
|
||||
Error::bad_database(
|
||||
"Invalid room topic event in database.",
|
||||
)
|
||||
})
|
||||
})?,
|
||||
world_readable: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomHistoryVisibility,
|
||||
"",
|
||||
)?
|
||||
.map_or(Ok(false), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility == HistoryVisibility::WorldReadable
|
||||
c.history_visibility
|
||||
== HistoryVisibility::WorldReadable
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
"Invalid room history visibility event in \
|
||||
database.",
|
||||
)
|
||||
})
|
||||
})?,
|
||||
|
|
@ -358,7 +411,9 @@ impl Service {
|
|||
c.guest_access == GuestAccess::CanJoin
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid room guest access event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid room guest access event in database.",
|
||||
)
|
||||
})
|
||||
})?,
|
||||
avatar_url: services()
|
||||
|
|
@ -368,7 +423,11 @@ impl Service {
|
|||
.map(|s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomAvatarEventContent| c.url)
|
||||
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room avatar event in database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.flatten(),
|
||||
|
|
@ -376,13 +435,23 @@ impl Service {
|
|||
let join_rule = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomJoinRules, "")?
|
||||
.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomJoinRules,
|
||||
"",
|
||||
)?
|
||||
.map(|s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
||||
.map_err(|e| {
|
||||
error!("Invalid room join rule event in database: {}", e);
|
||||
Error::BadDatabase("Invalid room join rule event in database.")
|
||||
error!(
|
||||
"Invalid room join rule event in \
|
||||
database: {}",
|
||||
e
|
||||
);
|
||||
Error::BadDatabase(
|
||||
"Invalid room join rule event in database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
|
|
@ -404,9 +473,14 @@ impl Service {
|
|||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||
.map(|s| {
|
||||
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
|
||||
serde_json::from_str::<RoomCreateEventContent>(
|
||||
s.content.get(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
error!("Invalid room create event in database: {}", e);
|
||||
Error::BadDatabase("Invalid room create event in database.")
|
||||
Error::BadDatabase(
|
||||
"Invalid room create event in database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
|
|
@ -424,7 +498,9 @@ impl Service {
|
|||
JoinRule::Knock => Ok(SpaceRoomJoinRule::Knock),
|
||||
JoinRule::Private => Ok(SpaceRoomJoinRule::Private),
|
||||
JoinRule::Restricted(_) => Ok(SpaceRoomJoinRule::Restricted),
|
||||
JoinRule::KnockRestricted(_) => Ok(SpaceRoomJoinRule::KnockRestricted),
|
||||
JoinRule::KnockRestricted(_) => {
|
||||
Ok(SpaceRoomJoinRule::KnockRestricted)
|
||||
}
|
||||
JoinRule::Public => Ok(SpaceRoomJoinRule::Public),
|
||||
_ => Err(Error::BadServerResponse("Unknown join rule")),
|
||||
}
|
||||
|
|
@ -440,10 +516,9 @@ impl Service {
|
|||
) -> Result<bool> {
|
||||
let allowed = match join_rule {
|
||||
SpaceRoomJoinRule::Knock | SpaceRoomJoinRule::Public => true,
|
||||
SpaceRoomJoinRule::Invite => services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, room_id)?,
|
||||
SpaceRoomJoinRule::Invite => {
|
||||
services().rooms.state_cache.is_joined(sender_user, room_id)?
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ use serde::Deserialize;
|
|||
use tokio::sync::MutexGuard;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{services, utils::calculate_hash, Error, PduEvent, Result};
|
||||
|
||||
use super::state_compressor::CompressedStateEvent;
|
||||
use crate::{services, utils::calculate_hash, Error, PduEvent, Result};
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
|
|
@ -46,12 +45,15 @@ impl Service {
|
|||
.ok()
|
||||
.map(|(_, id)| id)
|
||||
}) {
|
||||
let Some(pdu) = services().rooms.timeline.get_pdu_json(&event_id)? else {
|
||||
let Some(pdu) =
|
||||
services().rooms.timeline.get_pdu_json(&event_id)?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pdu: PduEvent = match serde_json::from_str(
|
||||
&serde_json::to_string(&pdu).expect("CanonicalJsonObj can be serialized to JSON"),
|
||||
&serde_json::to_string(&pdu)
|
||||
.expect("CanonicalJsonObj can be serialized to JSON"),
|
||||
) {
|
||||
Ok(pdu) => pdu,
|
||||
Err(_) => continue,
|
||||
|
|
@ -65,7 +67,9 @@ impl Service {
|
|||
}
|
||||
|
||||
let membership =
|
||||
match serde_json::from_str::<ExtractMembership>(pdu.content.get()) {
|
||||
match serde_json::from_str::<ExtractMembership>(
|
||||
pdu.content.get(),
|
||||
) {
|
||||
Ok(e) => e.membership,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
|
@ -102,8 +106,7 @@ impl Service {
|
|||
|
||||
services().rooms.state_cache.update_joined_count(room_id)?;
|
||||
|
||||
self.db
|
||||
.set_room_state(room_id, shortstatehash, state_lock)?;
|
||||
self.db.set_room_state(room_id, shortstatehash, state_lock)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -119,24 +122,18 @@ impl Service {
|
|||
room_id: &RoomId,
|
||||
state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
||||
) -> Result<u64> {
|
||||
let shorteventid = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shorteventid(event_id)?;
|
||||
let shorteventid =
|
||||
services().rooms.short.get_or_create_shorteventid(event_id)?;
|
||||
|
||||
let previous_shortstatehash = self.db.get_room_shortstatehash(room_id)?;
|
||||
let previous_shortstatehash =
|
||||
self.db.get_room_shortstatehash(room_id)?;
|
||||
|
||||
let state_hash = calculate_hash(
|
||||
&state_ids_compressed
|
||||
.iter()
|
||||
.map(|s| &s[..])
|
||||
.collect::<Vec<_>>(),
|
||||
&state_ids_compressed.iter().map(|s| &s[..]).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let (shortstatehash, already_existed) = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatehash(&state_hash)?;
|
||||
let (shortstatehash, already_existed) =
|
||||
services().rooms.short.get_or_create_shortstatehash(&state_hash)?;
|
||||
|
||||
if !already_existed {
|
||||
let states_parents = previous_shortstatehash.map_or_else(
|
||||
|
|
@ -192,7 +189,8 @@ impl Service {
|
|||
.short
|
||||
.get_or_create_shorteventid(&new_pdu.event_id)?;
|
||||
|
||||
let previous_shortstatehash = self.get_room_shortstatehash(&new_pdu.room_id)?;
|
||||
let previous_shortstatehash =
|
||||
self.get_room_shortstatehash(&new_pdu.room_id)?;
|
||||
|
||||
if let Some(p) = previous_shortstatehash {
|
||||
self.db.set_event_state(shorteventid, p)?;
|
||||
|
|
@ -209,10 +207,11 @@ impl Service {
|
|||
},
|
||||
)?;
|
||||
|
||||
let shortstatekey = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&new_pdu.kind.to_string().into(), state_key)?;
|
||||
let shortstatekey =
|
||||
services().rooms.short.get_or_create_shortstatekey(
|
||||
&new_pdu.kind.to_string().into(),
|
||||
state_key,
|
||||
)?;
|
||||
|
||||
let new = services()
|
||||
.rooms
|
||||
|
|
@ -222,9 +221,9 @@ impl Service {
|
|||
let replaces = states_parents
|
||||
.last()
|
||||
.map(|info| {
|
||||
info.1
|
||||
.iter()
|
||||
.find(|bytes| bytes.starts_with(&shortstatekey.to_be_bytes()))
|
||||
info.1.iter().find(|bytes| {
|
||||
bytes.starts_with(&shortstatekey.to_be_bytes())
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
|
|
@ -253,7 +252,8 @@ impl Service {
|
|||
|
||||
Ok(shortstatehash)
|
||||
} else {
|
||||
Ok(previous_shortstatehash.expect("first event in room must be a state event"))
|
||||
Ok(previous_shortstatehash
|
||||
.expect("first event in room must be a state event"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +325,10 @@ impl Service {
|
|||
|
||||
/// Returns the room's version.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn get_room_version(&self, room_id: &RoomId) -> Result<RoomVersionId> {
|
||||
pub(crate) fn get_room_version(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<RoomVersionId> {
|
||||
let create_event = services().rooms.state_accessor.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomCreate,
|
||||
|
|
@ -341,12 +344,20 @@ impl Service {
|
|||
})
|
||||
})
|
||||
.transpose()?
|
||||
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "No create event found"))?;
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"No create event found",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(create_event_content.room_version)
|
||||
}
|
||||
|
||||
pub(crate) fn get_room_shortstatehash(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||
pub(crate) fn get_room_shortstatehash(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.get_room_shortstatehash(room_id)
|
||||
}
|
||||
|
||||
|
|
@ -364,8 +375,7 @@ impl Service {
|
|||
// Take mutex guard to make sure users get the room state mutex
|
||||
state_lock: &MutexGuard<'_, ()>,
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.set_forward_extremities(room_id, event_ids, state_lock)
|
||||
self.db.set_forward_extremities(room_id, event_ids, state_lock)
|
||||
}
|
||||
|
||||
/// This fetches auth events from the current state.
|
||||
|
|
@ -378,12 +388,15 @@ impl Service {
|
|||
state_key: Option<&str>,
|
||||
content: &serde_json::value::RawValue,
|
||||
) -> Result<StateMap<Arc<PduEvent>>> {
|
||||
let Some(shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else {
|
||||
let Some(shortstatehash) =
|
||||
services().rooms.state.get_room_shortstatehash(room_id)?
|
||||
else {
|
||||
return Ok(HashMap::new());
|
||||
};
|
||||
|
||||
let auth_events = state_res::auth_types_for_event(kind, sender, state_key, content)
|
||||
.expect("content is a valid JSON object");
|
||||
let auth_events =
|
||||
state_res::auth_types_for_event(kind, sender, state_key, content)
|
||||
.expect("content is a valid JSON object");
|
||||
|
||||
let mut sauthevents = auth_events
|
||||
.into_iter()
|
||||
|
|
@ -391,7 +404,10 @@ impl Service {
|
|||
services()
|
||||
.rooms
|
||||
.short
|
||||
.get_shortstatekey(&event_type.to_string().into(), &state_key)
|
||||
.get_shortstatekey(
|
||||
&event_type.to_string().into(),
|
||||
&state_key,
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| (s, (event_type, state_key)))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use crate::Result;
|
||||
use ruma::{EventId, OwnedEventId, RoomId};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use ruma::{EventId, OwnedEventId, RoomId};
|
||||
use tokio::sync::MutexGuard;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Returns the last state hash key added to the db for the given room.
|
||||
fn get_room_shortstatehash(&self, room_id: &RoomId) -> Result<Option<u64>>;
|
||||
|
|
@ -17,10 +19,17 @@ pub(crate) trait Data: Send + Sync {
|
|||
) -> Result<()>;
|
||||
|
||||
/// Associates a state with an event.
|
||||
fn set_event_state(&self, shorteventid: u64, shortstatehash: u64) -> Result<()>;
|
||||
fn set_event_state(
|
||||
&self,
|
||||
shorteventid: u64,
|
||||
shortstatehash: u64,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns all events we would send as the `prev_events` of the next event.
|
||||
fn get_forward_extremities(&self, room_id: &RoomId) -> Result<HashSet<Arc<EventId>>>;
|
||||
fn get_forward_extremities(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<HashSet<Arc<EventId>>>;
|
||||
|
||||
/// Replace the forward extremities of the room.
|
||||
fn set_forward_extremities(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ use ruma::{
|
|||
events::{
|
||||
room::{
|
||||
avatar::RoomAvatarEventContent,
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
history_visibility::{
|
||||
HistoryVisibility, RoomHistoryVisibilityEventContent,
|
||||
},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
|
|
@ -18,7 +20,8 @@ use ruma::{
|
|||
StateEventType,
|
||||
},
|
||||
state_res::Event,
|
||||
EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||
EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName,
|
||||
UserId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use tokio::sync::MutexGuard;
|
||||
|
|
@ -28,7 +31,8 @@ use crate::{service::pdu::PduBuilder, services, Error, PduEvent, Result};
|
|||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
pub(crate) server_visibility_cache: Mutex<LruCache<(OwnedServerName, u64), bool>>,
|
||||
pub(crate) server_visibility_cache:
|
||||
Mutex<LruCache<(OwnedServerName, u64), bool>>,
|
||||
pub(crate) user_visibility_cache: Mutex<LruCache<(OwnedUserId, u64), bool>>,
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +54,8 @@ impl Service {
|
|||
self.db.state_full(shortstatehash).await
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn state_get_id(
|
||||
&self,
|
||||
|
|
@ -61,7 +66,8 @@ impl Service {
|
|||
self.db.state_get_id(shortstatehash, event_type, state_key)
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
pub(crate) fn state_get(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
|
|
@ -72,7 +78,11 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Get membership for given user in state
|
||||
fn user_membership(&self, shortstatehash: u64, user_id: &UserId) -> Result<MembershipState> {
|
||||
fn user_membership(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
user_id: &UserId,
|
||||
) -> Result<MembershipState> {
|
||||
self.state_get(
|
||||
shortstatehash,
|
||||
&StateEventType::RoomMember,
|
||||
|
|
@ -81,7 +91,11 @@ impl Service {
|
|||
.map_or(Ok(MembershipState::Leave), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomMemberEventContent| c.membership)
|
||||
.map_err(|_| Error::bad_database("Invalid room membership event in database."))
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room membership event in database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -123,12 +137,20 @@ impl Service {
|
|||
}
|
||||
|
||||
let history_visibility = self
|
||||
.state_get(shortstatehash, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.state_get(
|
||||
shortstatehash,
|
||||
&StateEventType::RoomHistoryVisibility,
|
||||
"",
|
||||
)?
|
||||
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid history visibility event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid history visibility event in database.",
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
|
|
@ -140,14 +162,20 @@ impl Service {
|
|||
.filter(|member| member.server_name() == origin);
|
||||
|
||||
let visibility = match history_visibility {
|
||||
HistoryVisibility::WorldReadable | HistoryVisibility::Shared => true,
|
||||
HistoryVisibility::WorldReadable | HistoryVisibility::Shared => {
|
||||
true
|
||||
}
|
||||
HistoryVisibility::Invited => {
|
||||
// Allow if any member on requesting server was AT LEAST invited, else deny
|
||||
current_server_members.any(|member| self.user_was_invited(shortstatehash, &member))
|
||||
// Allow if any member on requesting server was AT LEAST
|
||||
// invited, else deny
|
||||
current_server_members.any(|member| {
|
||||
self.user_was_invited(shortstatehash, &member)
|
||||
})
|
||||
}
|
||||
HistoryVisibility::Joined => {
|
||||
// Allow if any member on requested server was joined, else deny
|
||||
current_server_members.any(|member| self.user_was_joined(shortstatehash, &member))
|
||||
current_server_members
|
||||
.any(|member| self.user_was_joined(shortstatehash, &member))
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown history visibility {history_visibility}");
|
||||
|
|
@ -185,15 +213,24 @@ impl Service {
|
|||
return Ok(*visibility);
|
||||
}
|
||||
|
||||
let currently_member = services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||
let currently_member =
|
||||
services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||
|
||||
let history_visibility = self
|
||||
.state_get(shortstatehash, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.state_get(
|
||||
shortstatehash,
|
||||
&StateEventType::RoomHistoryVisibility,
|
||||
"",
|
||||
)?
|
||||
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid history visibility event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid history visibility event in database.",
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
|
|
@ -201,7 +238,8 @@ impl Service {
|
|||
HistoryVisibility::WorldReadable => true,
|
||||
HistoryVisibility::Shared => currently_member,
|
||||
HistoryVisibility::Invited => {
|
||||
// Allow if any member on requesting server was AT LEAST invited, else deny
|
||||
// Allow if any member on requesting server was AT LEAST
|
||||
// invited, else deny
|
||||
self.user_was_invited(shortstatehash, user_id)
|
||||
}
|
||||
HistoryVisibility::Joined => {
|
||||
|
|
@ -230,23 +268,36 @@ impl Service {
|
|||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
let currently_member = services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||
let currently_member =
|
||||
services().rooms.state_cache.is_joined(user_id, room_id)?;
|
||||
|
||||
let history_visibility = self
|
||||
.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomHistoryVisibility,
|
||||
"",
|
||||
)?
|
||||
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid history visibility event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid history visibility event in database.",
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(currently_member || history_visibility == HistoryVisibility::WorldReadable)
|
||||
Ok(currently_member
|
||||
|| history_visibility == HistoryVisibility::WorldReadable)
|
||||
}
|
||||
|
||||
/// Returns the state hash for this pdu.
|
||||
pub(crate) fn pdu_shortstatehash(&self, event_id: &EventId) -> Result<Option<u64>> {
|
||||
pub(crate) fn pdu_shortstatehash(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.pdu_shortstatehash(event_id)
|
||||
}
|
||||
|
||||
|
|
@ -259,7 +310,8 @@ impl Service {
|
|||
self.db.room_state_full(room_id).await
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn room_state_get_id(
|
||||
&self,
|
||||
|
|
@ -270,7 +322,8 @@ impl Service {
|
|||
self.db.room_state_get_id(room_id, event_type, state_key)
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn room_state_get(
|
||||
&self,
|
||||
|
|
@ -282,26 +335,39 @@ impl Service {
|
|||
}
|
||||
|
||||
pub(crate) fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> {
|
||||
self.room_state_get(room_id, &StateEventType::RoomName, "")?
|
||||
.map_or(Ok(None), |s| {
|
||||
self.room_state_get(room_id, &StateEventType::RoomName, "")?.map_or(
|
||||
Ok(None),
|
||||
|s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomNameEventContent| Some(c.name))
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Invalid room name event in database for room {}. {}",
|
||||
"Invalid room name event in database for room {}. \
|
||||
{}",
|
||||
room_id, e
|
||||
);
|
||||
Error::bad_database("Invalid room name event in database.")
|
||||
Error::bad_database(
|
||||
"Invalid room name event in database.",
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_avatar(&self, room_id: &RoomId) -> Result<JsOption<RoomAvatarEventContent>> {
|
||||
self.room_state_get(room_id, &StateEventType::RoomAvatar, "")?
|
||||
.map_or(Ok(JsOption::Undefined), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
|
||||
})
|
||||
pub(crate) fn get_avatar(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<JsOption<RoomAvatarEventContent>> {
|
||||
self.room_state_get(room_id, &StateEventType::RoomAvatar, "")?.map_or(
|
||||
Ok(JsOption::Undefined),
|
||||
|s| {
|
||||
serde_json::from_str(s.content.get()).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room avatar event in database.",
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Allowed because this function uses `services()`
|
||||
|
|
@ -313,8 +379,9 @@ impl Service {
|
|||
target_user: &UserId,
|
||||
state_lock: &MutexGuard<'_, ()>,
|
||||
) -> bool {
|
||||
let content = to_raw_value(&RoomMemberEventContent::new(MembershipState::Invite))
|
||||
.expect("Event content always serializes");
|
||||
let content =
|
||||
to_raw_value(&RoomMemberEventContent::new(MembershipState::Invite))
|
||||
.expect("Event content always serializes");
|
||||
|
||||
let new_event = PduBuilder {
|
||||
event_type: ruma::events::TimelineEventType::RoomMember,
|
||||
|
|
@ -336,18 +403,23 @@ impl Service {
|
|||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<RoomMemberEventContent>> {
|
||||
self.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid room member event in database."))
|
||||
self.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomMember,
|
||||
user_id.as_str(),
|
||||
)?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get()).map_err(|_| {
|
||||
Error::bad_database("Invalid room member event in database.")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if a given user can redact a given event
|
||||
///
|
||||
/// If `federation` is `true`, it allows redaction events from any user of the same server
|
||||
/// as the original event sender, [as required by room versions >=
|
||||
/// v3](https://spec.matrix.org/v1.10/rooms/v11/#handling-redactions)
|
||||
/// If `federation` is `true`, it allows redaction events from any user of
|
||||
/// the same server as the original event sender, [as required by room
|
||||
/// versions >= v3](https://spec.matrix.org/v1.10/rooms/v11/#handling-redactions)
|
||||
pub(crate) fn user_can_redact(
|
||||
&self,
|
||||
redacts: &EventId,
|
||||
|
|
@ -359,18 +431,23 @@ impl Service {
|
|||
.map_or_else(
|
||||
// Falling back on m.room.create to judge power levels
|
||||
|| {
|
||||
if let Some(pdu) =
|
||||
self.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||
{
|
||||
if let Some(pdu) = self.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomCreate,
|
||||
"",
|
||||
)? {
|
||||
Ok(pdu.sender == sender
|
||||
|| if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) {
|
||||
|| if let Ok(Some(pdu)) =
|
||||
services().rooms.timeline.get_pdu(redacts)
|
||||
{
|
||||
pdu.sender == sender
|
||||
} else {
|
||||
false
|
||||
})
|
||||
} else {
|
||||
Err(Error::bad_database(
|
||||
"No m.room.power_levels or m.room.create events in database for room",
|
||||
"No m.room.power_levels or m.room.create events \
|
||||
in database for room",
|
||||
))
|
||||
}
|
||||
},
|
||||
|
|
@ -380,11 +457,14 @@ impl Service {
|
|||
.map(|e: RoomPowerLevels| {
|
||||
e.user_can_redact_event_of_other(sender)
|
||||
|| e.user_can_redact_own_event(sender)
|
||||
&& if let Ok(Some(pdu)) =
|
||||
services().rooms.timeline.get_pdu(redacts)
|
||||
&& if let Ok(Some(pdu)) = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(redacts)
|
||||
{
|
||||
if federation {
|
||||
pdu.sender().server_name() == sender.server_name()
|
||||
pdu.sender().server_name()
|
||||
== sender.server_name()
|
||||
} else {
|
||||
pdu.sender == sender
|
||||
}
|
||||
|
|
@ -393,7 +473,9 @@ impl Service {
|
|||
}
|
||||
})
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid m.room.power_levels event in database")
|
||||
Error::bad_database(
|
||||
"Invalid m.room.power_levels event in database",
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,14 +9,18 @@ use crate::{PduEvent, Result};
|
|||
pub(crate) trait Data: Send + Sync {
|
||||
/// Builds a StateMap by iterating over all keys that start
|
||||
/// with state_hash, this gives the full state for the given state_hash.
|
||||
async fn state_full_ids(&self, shortstatehash: u64) -> Result<HashMap<u64, Arc<EventId>>>;
|
||||
async fn state_full_ids(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
) -> Result<HashMap<u64, Arc<EventId>>>;
|
||||
|
||||
async fn state_full(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
) -> Result<HashMap<(StateEventType, String), Arc<PduEvent>>>;
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
fn state_get_id(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
|
|
@ -24,7 +28,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
state_key: &str,
|
||||
) -> Result<Option<Arc<EventId>>>;
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
fn state_get(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
|
|
@ -41,7 +46,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
room_id: &RoomId,
|
||||
) -> Result<HashMap<(StateEventType, String), Arc<PduEvent>>>;
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
fn room_state_get_id(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
|
|
@ -49,7 +55,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
state_key: &str,
|
||||
) -> Result<Option<Arc<EventId>>>;
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`,
|
||||
/// `state_key`).
|
||||
fn room_state_get(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ mod data;
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
direct::DirectEvent,
|
||||
|
|
@ -34,7 +33,8 @@ impl Service {
|
|||
last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
|
||||
update_joined_count: bool,
|
||||
) -> Result<()> {
|
||||
// Keep track what remote users exist by adding them as "deactivated" users
|
||||
// Keep track what remote users exist by adding them as "deactivated"
|
||||
// users
|
||||
if user_id.server_name() != services().globals.server_name() {
|
||||
services().users.create(user_id, None)?;
|
||||
// TODO: displayname, avatar url
|
||||
|
|
@ -51,17 +51,26 @@ impl Service {
|
|||
if let Some(predecessor) = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||
.and_then(|create| serde_json::from_str(create.content.get()).ok())
|
||||
.and_then(|content: RoomCreateEventContent| content.predecessor)
|
||||
.room_state_get(
|
||||
room_id,
|
||||
&StateEventType::RoomCreate,
|
||||
"",
|
||||
)?
|
||||
.and_then(|create| {
|
||||
serde_json::from_str(create.content.get()).ok()
|
||||
})
|
||||
.and_then(|content: RoomCreateEventContent| {
|
||||
content.predecessor
|
||||
})
|
||||
{
|
||||
// Copy user settings from predecessor to the current room:
|
||||
// Copy user settings from predecessor to the current
|
||||
// room:
|
||||
// - Push rules
|
||||
//
|
||||
// TODO: finish this once push rules are implemented.
|
||||
//
|
||||
// let mut push_rules_event_content: PushRulesEvent = account_data
|
||||
// .get(
|
||||
// let mut push_rules_event_content: PushRulesEvent =
|
||||
// account_data .get(
|
||||
// None,
|
||||
// user_id,
|
||||
// EventType::PushRules,
|
||||
|
|
@ -90,8 +99,13 @@ impl Service {
|
|||
)?
|
||||
.map(|event| {
|
||||
serde_json::from_str(event.get()).map_err(|e| {
|
||||
warn!("Invalid account data event in db: {e:?}");
|
||||
Error::BadDatabase("Invalid account data event in db.")
|
||||
warn!(
|
||||
"Invalid account data event in db: \
|
||||
{e:?}"
|
||||
);
|
||||
Error::BadDatabase(
|
||||
"Invalid account data event in db.",
|
||||
)
|
||||
})
|
||||
})
|
||||
{
|
||||
|
|
@ -112,20 +126,32 @@ impl Service {
|
|||
.get(
|
||||
None,
|
||||
user_id,
|
||||
GlobalAccountDataEventType::Direct.to_string().into(),
|
||||
GlobalAccountDataEventType::Direct
|
||||
.to_string()
|
||||
.into(),
|
||||
)?
|
||||
.map(|event| {
|
||||
serde_json::from_str::<DirectEvent>(event.get()).map_err(|e| {
|
||||
warn!("Invalid account data event in db: {e:?}");
|
||||
Error::BadDatabase("Invalid account data event in db.")
|
||||
})
|
||||
serde_json::from_str::<DirectEvent>(event.get())
|
||||
.map_err(|e| {
|
||||
warn!(
|
||||
"Invalid account data event in \
|
||||
db: {e:?}"
|
||||
);
|
||||
Error::BadDatabase(
|
||||
"Invalid account data event in db.",
|
||||
)
|
||||
})
|
||||
})
|
||||
{
|
||||
let mut direct_event = direct_event?;
|
||||
let mut room_ids_updated = false;
|
||||
|
||||
for room_ids in direct_event.content.0.values_mut() {
|
||||
if room_ids.iter().any(|r| r == &predecessor.room_id) {
|
||||
for room_ids in direct_event.content.0.values_mut()
|
||||
{
|
||||
if room_ids
|
||||
.iter()
|
||||
.any(|r| r == &predecessor.room_id)
|
||||
{
|
||||
room_ids.push(room_id.to_owned());
|
||||
room_ids_updated = true;
|
||||
}
|
||||
|
|
@ -135,7 +161,9 @@ impl Service {
|
|||
services().account_data.update(
|
||||
None,
|
||||
user_id,
|
||||
GlobalAccountDataEventType::Direct.to_string().into(),
|
||||
GlobalAccountDataEventType::Direct
|
||||
.to_string()
|
||||
.into(),
|
||||
&serde_json::to_value(&direct_event)
|
||||
.expect("to json always works"),
|
||||
)?;
|
||||
|
|
@ -160,9 +188,14 @@ impl Service {
|
|||
.into(),
|
||||
)?
|
||||
.map(|event| {
|
||||
serde_json::from_str::<IgnoredUserListEvent>(event.get()).map_err(|e| {
|
||||
serde_json::from_str::<IgnoredUserListEvent>(
|
||||
event.get(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
warn!("Invalid account data event in db: {e:?}");
|
||||
Error::BadDatabase("Invalid account data event in db.")
|
||||
Error::BadDatabase(
|
||||
"Invalid account data event in db.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
|
|
@ -199,7 +232,10 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self, room_id))]
|
||||
pub(crate) fn get_our_real_users(&self, room_id: &RoomId) -> Result<Arc<HashSet<OwnedUserId>>> {
|
||||
pub(crate) fn get_our_real_users(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Arc<HashSet<OwnedUserId>>> {
|
||||
self.db.get_our_real_users(room_id)
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +250,11 @@ impl Service {
|
|||
|
||||
/// Makes a user forget a room.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()> {
|
||||
pub(crate) fn forget(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<()> {
|
||||
self.db.forget(room_id, user_id)
|
||||
}
|
||||
|
||||
|
|
@ -228,11 +268,16 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn server_in_room(&self, server: &ServerName, room_id: &RoomId) -> Result<bool> {
|
||||
pub(crate) fn server_in_room(
|
||||
&self,
|
||||
server: &ServerName,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
self.db.server_in_room(server, room_id)
|
||||
}
|
||||
|
||||
/// Returns an iterator of all rooms a server participates in (as far as we know).
|
||||
/// Returns an iterator of all rooms a server participates in (as far as we
|
||||
/// know).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn server_rooms<'a>(
|
||||
&'a self,
|
||||
|
|
@ -251,12 +296,18 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||
pub(crate) fn room_joined_count(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.room_joined_count(room_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||
pub(crate) fn room_invited_count(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.room_invited_count(room_id)
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +339,11 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn get_left_count(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>> {
|
||||
pub(crate) fn get_left_count(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.get_left_count(room_id, user_id)
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +361,9 @@ impl Service {
|
|||
pub(crate) fn rooms_invited<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a {
|
||||
) -> impl Iterator<
|
||||
Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>,
|
||||
> + 'a {
|
||||
self.db.rooms_invited(user_id)
|
||||
}
|
||||
|
||||
|
|
@ -333,27 +390,44 @@ impl Service {
|
|||
pub(crate) fn rooms_left<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a {
|
||||
) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a
|
||||
{
|
||||
self.db.rooms_left(user_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||
pub(crate) fn once_joined(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
self.db.once_joined(user_id, room_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn is_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||
pub(crate) fn is_joined(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
self.db.is_joined(user_id, room_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn is_invited(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||
pub(crate) fn is_invited(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
self.db.is_invited(user_id, room_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn is_left(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||
pub(crate) fn is_left(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
self.db.is_left(user_id, room_id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use crate::{service::appservice::RegistrationInfo, Result};
|
||||
use ruma::{
|
||||
events::{AnyStrippedStateEvent, AnySyncStateEvent},
|
||||
serde::Raw,
|
||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||
};
|
||||
|
||||
use crate::{service::appservice::RegistrationInfo, Result};
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
||||
fn mark_as_once_joined(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<()>;
|
||||
fn mark_as_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
||||
fn mark_as_invited(
|
||||
&self,
|
||||
|
|
@ -20,9 +25,16 @@ pub(crate) trait Data: Send + Sync {
|
|||
|
||||
fn update_joined_count(&self, room_id: &RoomId) -> Result<()>;
|
||||
|
||||
fn get_our_real_users(&self, room_id: &RoomId) -> Result<Arc<HashSet<OwnedUserId>>>;
|
||||
fn get_our_real_users(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Arc<HashSet<OwnedUserId>>>;
|
||||
|
||||
fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result<bool>;
|
||||
fn appservice_in_room(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
appservice: &RegistrationInfo,
|
||||
) -> Result<bool>;
|
||||
|
||||
/// Makes a user forget a room.
|
||||
fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()>;
|
||||
|
|
@ -33,9 +45,14 @@ pub(crate) trait Data: Send + Sync {
|
|||
room_id: &RoomId,
|
||||
) -> Box<dyn Iterator<Item = Result<OwnedServerName>> + 'a>;
|
||||
|
||||
fn server_in_room(&self, server: &ServerName, room_id: &RoomId) -> Result<bool>;
|
||||
fn server_in_room(
|
||||
&self,
|
||||
server: &ServerName,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool>;
|
||||
|
||||
/// Returns an iterator of all rooms a server participates in (as far as we know).
|
||||
/// Returns an iterator of all rooms a server participates in (as far as we
|
||||
/// know).
|
||||
fn server_rooms<'a>(
|
||||
&'a self,
|
||||
server: &ServerName,
|
||||
|
|
@ -63,9 +80,17 @@ pub(crate) trait Data: Send + Sync {
|
|||
room_id: &RoomId,
|
||||
) -> Box<dyn Iterator<Item = Result<OwnedUserId>> + 'a>;
|
||||
|
||||
fn get_invite_count(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>>;
|
||||
fn get_invite_count(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<u64>>;
|
||||
|
||||
fn get_left_count(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>>;
|
||||
fn get_left_count(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<u64>>;
|
||||
|
||||
/// Returns an iterator over all rooms this user joined.
|
||||
fn rooms_joined<'a>(
|
||||
|
|
@ -78,7 +103,11 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn rooms_invited<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
|
||||
) -> Box<
|
||||
dyn Iterator<
|
||||
Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>,
|
||||
> + 'a,
|
||||
>;
|
||||
|
||||
fn invite_state(
|
||||
&self,
|
||||
|
|
@ -97,7 +126,10 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn rooms_left<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a>;
|
||||
) -> Box<
|
||||
dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>>
|
||||
+ 'a,
|
||||
>;
|
||||
|
||||
fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool>;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ pub(crate) use data::Data;
|
|||
use lru_cache::LruCache;
|
||||
use ruma::{EventId, RoomId};
|
||||
|
||||
use crate::{services, utils, Result};
|
||||
|
||||
use self::data::StateDiff;
|
||||
use crate::{services, utils, Result};
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
|
|
@ -37,7 +36,8 @@ pub(crate) struct Service {
|
|||
pub(crate) type CompressedStateEvent = [u8; 2 * size_of::<u64>()];
|
||||
|
||||
impl Service {
|
||||
/// Returns a stack with info on shortstatehash, full state, added diff and removed diff for the selected shortstatehash and each parent layer.
|
||||
/// Returns a stack with info on shortstatehash, full state, added diff and
|
||||
/// removed diff for the selected shortstatehash and each parent layer.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn load_shortstatehash_info(
|
||||
|
|
@ -55,11 +55,8 @@ impl Service {
|
|||
Arc<HashSet<CompressedStateEvent>>,
|
||||
)>,
|
||||
> {
|
||||
if let Some(r) = self
|
||||
.stateinfo_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_mut(&shortstatehash)
|
||||
if let Some(r) =
|
||||
self.stateinfo_cache.lock().unwrap().get_mut(&shortstatehash)
|
||||
{
|
||||
return Ok(r.clone());
|
||||
}
|
||||
|
|
@ -79,7 +76,12 @@ impl Service {
|
|||
state.remove(r);
|
||||
}
|
||||
|
||||
response.push((shortstatehash, Arc::new(state), added, Arc::new(removed)));
|
||||
response.push((
|
||||
shortstatehash,
|
||||
Arc::new(state),
|
||||
added,
|
||||
Arc::new(removed),
|
||||
));
|
||||
|
||||
self.stateinfo_cache
|
||||
.lock()
|
||||
|
|
@ -88,7 +90,8 @@ impl Service {
|
|||
|
||||
Ok(response)
|
||||
} else {
|
||||
let response = vec![(shortstatehash, added.clone(), added, removed)];
|
||||
let response =
|
||||
vec![(shortstatehash, added.clone(), added, removed)];
|
||||
self.stateinfo_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
|
|
@ -132,19 +135,24 @@ impl Service {
|
|||
))
|
||||
}
|
||||
|
||||
/// Creates a new shortstatehash that often is just a diff to an already existing
|
||||
/// shortstatehash and therefore very efficient.
|
||||
/// Creates a new shortstatehash that often is just a diff to an already
|
||||
/// existing shortstatehash and therefore very efficient.
|
||||
///
|
||||
/// There are multiple layers of diffs. The bottom layer 0 always contains the full state. Layer
|
||||
/// 1 contains diffs to states of layer 0, layer 2 diffs to layer 1 and so on. If layer n > 0
|
||||
/// grows too big, it will be combined with layer n-1 to create a new diff on layer n-1 that's
|
||||
/// based on layer n-2. If that layer is also too big, it will recursively fix above layers too.
|
||||
/// There are multiple layers of diffs. The bottom layer 0 always contains
|
||||
/// the full state. Layer 1 contains diffs to states of layer 0, layer 2
|
||||
/// diffs to layer 1 and so on. If layer n > 0 grows too big, it will be
|
||||
/// combined with layer n-1 to create a new diff on layer n-1 that's
|
||||
/// based on layer n-2. If that layer is also too big, it will recursively
|
||||
/// fix above layers too.
|
||||
///
|
||||
/// * `shortstatehash` - Shortstatehash of this state
|
||||
/// * `statediffnew` - Added to base. Each vec is shortstatekey+shorteventid
|
||||
/// * `statediffremoved` - Removed from base. Each vec is shortstatekey+shorteventid
|
||||
/// * `diff_to_sibling` - Approximately how much the diff grows each time for this layer
|
||||
/// * `parent_states` - A stack with info on shortstatehash, full state, added diff and removed diff for each parent layer
|
||||
/// * `statediffremoved` - Removed from base. Each vec is
|
||||
/// shortstatekey+shorteventid
|
||||
/// * `diff_to_sibling` - Approximately how much the diff grows each time
|
||||
/// for this layer
|
||||
/// * `parent_states` - A stack with info on shortstatehash, full state,
|
||||
/// added diff and removed diff for each parent layer
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[tracing::instrument(skip(
|
||||
self,
|
||||
|
|
@ -185,7 +193,8 @@ impl Service {
|
|||
// It was not added in the parent and we removed it
|
||||
parent_removed.insert(*removed);
|
||||
}
|
||||
// Else it was added in the parent and we removed it again. We can forget this change
|
||||
// Else it was added in the parent and we removed it again. We
|
||||
// can forget this change
|
||||
}
|
||||
|
||||
for new in statediffnew.iter() {
|
||||
|
|
@ -193,7 +202,8 @@ impl Service {
|
|||
// It was not touched in the parent and we added it
|
||||
parent_new.insert(*new);
|
||||
}
|
||||
// Else it was removed in the parent and we added it again. We can forget this change
|
||||
// Else it was removed in the parent and we added it again. We
|
||||
// can forget this change
|
||||
}
|
||||
|
||||
self.save_state_from_diff(
|
||||
|
|
@ -238,7 +248,8 @@ impl Service {
|
|||
// It was not added in the parent and we removed it
|
||||
parent_removed.insert(*removed);
|
||||
}
|
||||
// Else it was added in the parent and we removed it again. We can forget this change
|
||||
// Else it was added in the parent and we removed it again. We
|
||||
// can forget this change
|
||||
}
|
||||
|
||||
for new in statediffnew.iter() {
|
||||
|
|
@ -246,7 +257,8 @@ impl Service {
|
|||
// It was not touched in the parent and we added it
|
||||
parent_new.insert(*new);
|
||||
}
|
||||
// Else it was removed in the parent and we added it again. We can forget this change
|
||||
// Else it was removed in the parent and we added it again. We
|
||||
// can forget this change
|
||||
}
|
||||
|
||||
self.save_state_from_diff(
|
||||
|
|
@ -271,7 +283,8 @@ impl Service {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the new shortstatehash, and the state diff from the previous room state
|
||||
/// Returns the new shortstatehash, and the state diff from the previous
|
||||
/// room state
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn save_state(
|
||||
&self,
|
||||
|
|
@ -282,7 +295,8 @@ impl Service {
|
|||
Arc<HashSet<CompressedStateEvent>>,
|
||||
Arc<HashSet<CompressedStateEvent>>,
|
||||
)> {
|
||||
let previous_shortstatehash = services().rooms.state.get_room_shortstatehash(room_id)?;
|
||||
let previous_shortstatehash =
|
||||
services().rooms.state.get_room_shortstatehash(room_id)?;
|
||||
|
||||
let state_hash = utils::calculate_hash(
|
||||
&new_state_ids_compressed
|
||||
|
|
@ -291,10 +305,8 @@ impl Service {
|
|||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let (new_shortstatehash, already_existed) = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatehash(&state_hash)?;
|
||||
let (new_shortstatehash, already_existed) =
|
||||
services().rooms.short.get_or_create_shortstatehash(&state_hash)?;
|
||||
|
||||
if Some(new_shortstatehash) == previous_shortstatehash {
|
||||
return Ok((
|
||||
|
|
@ -304,26 +316,28 @@ impl Service {
|
|||
));
|
||||
}
|
||||
|
||||
let states_parents = previous_shortstatehash
|
||||
.map_or_else(|| Ok(Vec::new()), |p| self.load_shortstatehash_info(p))?;
|
||||
let states_parents = previous_shortstatehash.map_or_else(
|
||||
|| Ok(Vec::new()),
|
||||
|p| self.load_shortstatehash_info(p),
|
||||
)?;
|
||||
|
||||
let (statediffnew, statediffremoved) = if let Some(parent_stateinfo) = states_parents.last()
|
||||
{
|
||||
let statediffnew: HashSet<_> = new_state_ids_compressed
|
||||
.difference(&parent_stateinfo.1)
|
||||
.copied()
|
||||
.collect();
|
||||
let (statediffnew, statediffremoved) =
|
||||
if let Some(parent_stateinfo) = states_parents.last() {
|
||||
let statediffnew: HashSet<_> = new_state_ids_compressed
|
||||
.difference(&parent_stateinfo.1)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let statediffremoved: HashSet<_> = parent_stateinfo
|
||||
.1
|
||||
.difference(&new_state_ids_compressed)
|
||||
.copied()
|
||||
.collect();
|
||||
let statediffremoved: HashSet<_> = parent_stateinfo
|
||||
.1
|
||||
.difference(&new_state_ids_compressed)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
(Arc::new(statediffnew), Arc::new(statediffremoved))
|
||||
} else {
|
||||
(new_state_ids_compressed, Arc::new(HashSet::new()))
|
||||
};
|
||||
(Arc::new(statediffnew), Arc::new(statediffremoved))
|
||||
} else {
|
||||
(new_state_ids_compressed, Arc::new(HashSet::new()))
|
||||
};
|
||||
|
||||
if !already_existed {
|
||||
self.save_state_from_diff(
|
||||
|
|
|
|||
|
|
@ -11,5 +11,9 @@ pub(crate) struct StateDiff {
|
|||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn get_statediff(&self, shortstatehash: u64) -> Result<StateDiff>;
|
||||
fn save_statediff(&self, shortstatehash: u64, diff: StateDiff) -> Result<()>;
|
||||
fn save_statediff(
|
||||
&self,
|
||||
shortstatehash: u64,
|
||||
diff: StateDiff,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use ruma::{
|
|||
events::relation::BundledThread,
|
||||
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, UserId,
|
||||
};
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{services, Error, PduEvent, Result};
|
||||
|
|
@ -26,51 +25,64 @@ impl Service {
|
|||
self.db.threads_until(user_id, room_id, until, include)
|
||||
}
|
||||
|
||||
pub(crate) fn add_to_thread(&self, root_event_id: &EventId, pdu: &PduEvent) -> Result<()> {
|
||||
let root_id = &services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_id(root_event_id)?
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid event id in thread message",
|
||||
)
|
||||
})?;
|
||||
pub(crate) fn add_to_thread(
|
||||
&self,
|
||||
root_event_id: &EventId,
|
||||
pdu: &PduEvent,
|
||||
) -> Result<()> {
|
||||
let root_id =
|
||||
&services().rooms.timeline.get_pdu_id(root_event_id)?.ok_or_else(
|
||||
|| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid event id in thread message",
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let root_pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(root_id)?
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found")
|
||||
})?;
|
||||
let root_pdu =
|
||||
services().rooms.timeline.get_pdu_from_id(root_id)?.ok_or_else(
|
||||
|| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Thread root pdu not found",
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut root_pdu_json = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json_from_id(root_id)?
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found")
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Thread root pdu not found",
|
||||
)
|
||||
})?;
|
||||
|
||||
if let CanonicalJsonValue::Object(unsigned) = root_pdu_json
|
||||
.entry("unsigned".to_owned())
|
||||
.or_insert_with(|| CanonicalJsonValue::Object(CanonicalJsonObject::default()))
|
||||
if let CanonicalJsonValue::Object(unsigned) =
|
||||
root_pdu_json.entry("unsigned".to_owned()).or_insert_with(|| {
|
||||
CanonicalJsonValue::Object(CanonicalJsonObject::default())
|
||||
})
|
||||
{
|
||||
if let Some(mut relations) = unsigned
|
||||
.get("m.relations")
|
||||
.and_then(|r| r.as_object())
|
||||
.and_then(|r| r.get("m.thread"))
|
||||
.and_then(|relations| {
|
||||
serde_json::from_value::<BundledThread>(relations.clone().into()).ok()
|
||||
serde_json::from_value::<BundledThread>(
|
||||
relations.clone().into(),
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
{
|
||||
// Thread already existed
|
||||
relations.count += uint!(1);
|
||||
relations.latest_event = pdu.to_message_like_event();
|
||||
|
||||
let content = serde_json::to_value(relations).expect("to_value always works");
|
||||
let content = serde_json::to_value(relations)
|
||||
.expect("to_value always works");
|
||||
|
||||
unsigned.insert(
|
||||
"m.relations".to_owned(),
|
||||
|
|
@ -86,7 +98,8 @@ impl Service {
|
|||
current_user_participated: true,
|
||||
};
|
||||
|
||||
let content = serde_json::to_value(relations).expect("to_value always works");
|
||||
let content = serde_json::to_value(relations)
|
||||
.expect("to_value always works");
|
||||
|
||||
unsigned.insert(
|
||||
"m.relations".to_owned(),
|
||||
|
|
@ -96,10 +109,11 @@ impl Service {
|
|||
);
|
||||
}
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.replace_pdu(root_id, &root_pdu_json, &root_pdu)?;
|
||||
services().rooms.timeline.replace_pdu(
|
||||
root_id,
|
||||
&root_pdu_json,
|
||||
&root_pdu,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut users = Vec::new();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use ruma::{
|
||||
api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId,
|
||||
UserId,
|
||||
};
|
||||
|
||||
use crate::{PduEvent, Result};
|
||||
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
|
@ -11,6 +15,13 @@ pub(crate) trait Data: Send + Sync {
|
|||
include: &'a IncludeThreads,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>;
|
||||
|
||||
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()>;
|
||||
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>>;
|
||||
fn update_participants(
|
||||
&self,
|
||||
root_id: &[u8],
|
||||
participants: &[OwnedUserId],
|
||||
) -> Result<()>;
|
||||
fn get_participants(
|
||||
&self,
|
||||
root_id: &[u8],
|
||||
) -> Result<Option<Vec<OwnedUserId>>>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,29 +7,31 @@ use std::{
|
|||
};
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, federation},
|
||||
canonical_json::to_canonical_value,
|
||||
events::{
|
||||
push_rules::PushRulesEvent,
|
||||
room::{
|
||||
create::RoomCreateEventContent, encrypted::Relation, member::MembershipState,
|
||||
power_levels::RoomPowerLevelsEventContent, redaction::RoomRedactionEventContent,
|
||||
create::RoomCreateEventContent, encrypted::Relation,
|
||||
member::MembershipState, power_levels::RoomPowerLevelsEventContent,
|
||||
redaction::RoomRedactionEventContent,
|
||||
},
|
||||
GlobalAccountDataEventType, StateEventType, TimelineEventType,
|
||||
},
|
||||
push::{Action, Ruleset, Tweak},
|
||||
serde::Base64,
|
||||
state_res::{self, Event, RoomVersion},
|
||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||
OwnedServerName, RoomId, RoomVersionId, ServerName, UserId,
|
||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId,
|
||||
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, RoomVersionId,
|
||||
ServerName, UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
use tokio::sync::{Mutex, MutexGuard, RwLock};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::state_compressor::CompressedStateEvent;
|
||||
use crate::{
|
||||
api::server_server,
|
||||
service::{
|
||||
|
|
@ -39,8 +41,6 @@ use crate::{
|
|||
services, utils, Error, PduEvent, Result,
|
||||
};
|
||||
|
||||
use super::state_compressor::CompressedStateEvent;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub(crate) enum PduCount {
|
||||
Backfilled(u64),
|
||||
|
|
@ -48,8 +48,8 @@ pub(crate) enum PduCount {
|
|||
}
|
||||
|
||||
impl PduCount {
|
||||
pub(crate) const MIN: Self = Self::Backfilled(u64::MAX);
|
||||
pub(crate) const MAX: Self = Self::Normal(u64::MAX);
|
||||
pub(crate) const MIN: Self = Self::Backfilled(u64::MAX);
|
||||
|
||||
pub(crate) fn try_from_string(token: &str) -> Result<Self> {
|
||||
if let Some(stripped) = token.strip_prefix('-') {
|
||||
|
|
@ -57,7 +57,12 @@ impl PduCount {
|
|||
} else {
|
||||
token.parse().map(PduCount::Normal)
|
||||
}
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid pagination token."))
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid pagination token.",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn stringify(&self) -> String {
|
||||
|
|
@ -93,7 +98,10 @@ pub(crate) struct Service {
|
|||
|
||||
impl Service {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn first_pdu_in_room(&self, room_id: &RoomId) -> Result<Option<Arc<PduEvent>>> {
|
||||
pub(crate) fn first_pdu_in_room(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Option<Arc<PduEvent>>> {
|
||||
self.all_pdus(user_id!("@doesntmatter:grapevine"), room_id)?
|
||||
.next()
|
||||
.map(|o| o.map(|(_, p)| Arc::new(p)))
|
||||
|
|
@ -110,12 +118,18 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
pub(crate) fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<PduCount>> {
|
||||
pub(crate) fn get_pdu_count(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<PduCount>> {
|
||||
self.db.get_pdu_count(event_id)
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
pub(crate) fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<CanonicalJsonObject>> {
|
||||
pub(crate) fn get_pdu_json(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<CanonicalJsonObject>> {
|
||||
self.db.get_pdu_json(event_id)
|
||||
}
|
||||
|
||||
|
|
@ -128,21 +142,30 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Returns the pdu's id.
|
||||
pub(crate) fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<Vec<u8>>> {
|
||||
pub(crate) fn get_pdu_id(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
self.db.get_pdu_id(event_id)
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
|
||||
pub(crate) fn get_pdu(&self, event_id: &EventId) -> Result<Option<Arc<PduEvent>>> {
|
||||
pub(crate) fn get_pdu(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<Arc<PduEvent>>> {
|
||||
self.db.get_pdu(event_id)
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// This does __NOT__ check the outliers `Tree`.
|
||||
pub(crate) fn get_pdu_from_id(&self, pdu_id: &[u8]) -> Result<Option<PduEvent>> {
|
||||
pub(crate) fn get_pdu_from_id(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
) -> Result<Option<PduEvent>> {
|
||||
self.db.get_pdu_from_id(pdu_id)
|
||||
}
|
||||
|
||||
|
|
@ -167,8 +190,8 @@ impl Service {
|
|||
|
||||
/// Creates a new persisted data unit and adds it to a room.
|
||||
///
|
||||
/// By this point the incoming event should be fully authenticated, no auth happens
|
||||
/// in `append_pdu`.
|
||||
/// By this point the incoming event should be fully authenticated, no auth
|
||||
/// happens in `append_pdu`.
|
||||
///
|
||||
/// Returns pdu id
|
||||
#[tracing::instrument(skip(self, pdu, pdu_json, leaves))]
|
||||
|
|
@ -186,13 +209,15 @@ impl Service {
|
|||
.get_shortroomid(&pdu.room_id)?
|
||||
.expect("room exists");
|
||||
|
||||
// Make unsigned fields correct. This is not properly documented in the spec, but state
|
||||
// events need to have previous content in the unsigned field, so clients can easily
|
||||
// interpret things like membership changes
|
||||
// Make unsigned fields correct. This is not properly documented in the
|
||||
// spec, but state events need to have previous content in the
|
||||
// unsigned field, so clients can easily interpret things like
|
||||
// membership changes
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
if let CanonicalJsonValue::Object(unsigned) = pdu_json
|
||||
.entry("unsigned".to_owned())
|
||||
.or_insert_with(|| CanonicalJsonValue::Object(CanonicalJsonObject::default()))
|
||||
if let CanonicalJsonValue::Object(unsigned) =
|
||||
pdu_json.entry("unsigned".to_owned()).or_insert_with(|| {
|
||||
CanonicalJsonValue::Object(CanonicalJsonObject::default())
|
||||
})
|
||||
{
|
||||
if let Some(shortstatehash) = services()
|
||||
.rooms
|
||||
|
|
@ -203,14 +228,20 @@ impl Service {
|
|||
if let Some(prev_state) = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get(shortstatehash, &pdu.kind.to_string().into(), state_key)
|
||||
.state_get(
|
||||
shortstatehash,
|
||||
&pdu.kind.to_string().into(),
|
||||
state_key,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
unsigned.insert(
|
||||
"prev_content".to_owned(),
|
||||
CanonicalJsonValue::Object(
|
||||
utils::to_canonical_object(prev_state.content.clone())
|
||||
.expect("event is valid, we just created it"),
|
||||
utils::to_canonical_object(
|
||||
prev_state.content.clone(),
|
||||
)
|
||||
.expect("event is valid, we just created it"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -225,10 +256,11 @@ impl Service {
|
|||
.rooms
|
||||
.pdu_metadata
|
||||
.mark_as_referenced(&pdu.room_id, &pdu.prev_events)?;
|
||||
services()
|
||||
.rooms
|
||||
.state
|
||||
.set_forward_extremities(&pdu.room_id, leaves, state_lock)?;
|
||||
services().rooms.state.set_forward_extremities(
|
||||
&pdu.room_id,
|
||||
leaves,
|
||||
state_lock,
|
||||
)?;
|
||||
|
||||
let mutex_insert = Arc::clone(
|
||||
services()
|
||||
|
|
@ -242,13 +274,13 @@ impl Service {
|
|||
let insert_lock = mutex_insert.lock().await;
|
||||
|
||||
let count1 = services().globals.next_count()?;
|
||||
// Mark as read first so the sending client doesn't get a notification even if appending
|
||||
// fails
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.read_receipt
|
||||
.private_read_set(&pdu.room_id, &pdu.sender, count1)?;
|
||||
// Mark as read first so the sending client doesn't get a notification
|
||||
// even if appending fails
|
||||
services().rooms.edus.read_receipt.private_read_set(
|
||||
&pdu.room_id,
|
||||
&pdu.sender,
|
||||
count1,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.user
|
||||
|
|
@ -269,8 +301,9 @@ impl Service {
|
|||
.state_accessor
|
||||
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
.map(|ev| {
|
||||
serde_json::from_str(ev.content.get())
|
||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
||||
serde_json::from_str(ev.content.get()).map_err(|_| {
|
||||
Error::bad_database("invalid m.room.power_levels event")
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
|
@ -280,10 +313,8 @@ impl Service {
|
|||
let mut notifies = Vec::new();
|
||||
let mut highlights = Vec::new();
|
||||
|
||||
let mut push_target = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_our_real_users(&pdu.room_id)?;
|
||||
let mut push_target =
|
||||
services().rooms.state_cache.get_our_real_users(&pdu.room_id)?;
|
||||
|
||||
if pdu.kind == TimelineEventType::RoomMember {
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
|
|
@ -312,8 +343,13 @@ impl Service {
|
|||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
)?
|
||||
.map(|event| {
|
||||
serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid push rules event in db."))
|
||||
serde_json::from_str::<PushRulesEvent>(event.get()).map_err(
|
||||
|_| {
|
||||
Error::bad_database(
|
||||
"Invalid push rules event in db.",
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.transpose()?
|
||||
.map_or_else(
|
||||
|
|
@ -353,12 +389,16 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
self.db
|
||||
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
|
||||
self.db.increment_notification_counts(
|
||||
&pdu.room_id,
|
||||
notifies,
|
||||
highlights,
|
||||
)?;
|
||||
|
||||
match pdu.kind {
|
||||
TimelineEventType::RoomRedaction => {
|
||||
let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?;
|
||||
let room_version_id =
|
||||
services().rooms.state.get_room_version(&pdu.room_id)?;
|
||||
match room_version_id {
|
||||
RoomVersionId::V1
|
||||
| RoomVersionId::V2
|
||||
|
|
@ -383,10 +423,14 @@ impl Service {
|
|||
}
|
||||
RoomVersionId::V11 => {
|
||||
let content =
|
||||
serde_json::from_str::<RoomRedactionEventContent>(pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in redaction pdu.")
|
||||
})?;
|
||||
serde_json::from_str::<RoomRedactionEventContent>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid content in redaction pdu.",
|
||||
)
|
||||
})?;
|
||||
if let Some(redact_id) = &content.redacts {
|
||||
if services().rooms.state_accessor.user_can_redact(
|
||||
redact_id,
|
||||
|
|
@ -398,7 +442,9 @@ impl Service {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Validity of room version already checked"),
|
||||
_ => {
|
||||
unreachable!("Validity of room version already checked")
|
||||
}
|
||||
};
|
||||
}
|
||||
TimelineEventType::SpaceChild => {
|
||||
|
|
@ -423,19 +469,27 @@ impl Service {
|
|||
let target_user_id = UserId::parse(state_key.clone())
|
||||
.expect("This state_key was previously validated");
|
||||
|
||||
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
let content = serde_json::from_str::<ExtractMembership>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in pdu.")
|
||||
})?;
|
||||
|
||||
let invite_state = match content.membership {
|
||||
MembershipState::Invite => {
|
||||
let state = services().rooms.state.calculate_invite_state(pdu)?;
|
||||
let state = services()
|
||||
.rooms
|
||||
.state
|
||||
.calculate_invite_state(pdu)?;
|
||||
Some(state)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Update our membership info, we do this here incase a user is invited
|
||||
// and immediately leaves we need the DB to record the invite event for auth
|
||||
// Update our membership info, we do this here incase a user
|
||||
// is invited and immediately leaves we
|
||||
// need the DB to record the invite event for auth
|
||||
services().rooms.state_cache.update_membership(
|
||||
&pdu.room_id,
|
||||
&target_user_id,
|
||||
|
|
@ -452,14 +506,18 @@ impl Service {
|
|||
body: Option<String>,
|
||||
}
|
||||
|
||||
let content = serde_json::from_str::<ExtractBody>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
let content =
|
||||
serde_json::from_str::<ExtractBody>(pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in pdu.")
|
||||
})?;
|
||||
|
||||
if let Some(body) = content.body {
|
||||
services()
|
||||
.rooms
|
||||
.search
|
||||
.index_pdu(shortroomid, &pdu_id, &body)?;
|
||||
services().rooms.search.index_pdu(
|
||||
shortroomid,
|
||||
&pdu_id,
|
||||
&body,
|
||||
)?;
|
||||
|
||||
let server_user = format!(
|
||||
"@{}:{}",
|
||||
|
|
@ -471,18 +529,25 @@ impl Service {
|
|||
services().globals.server_name()
|
||||
);
|
||||
|
||||
let to_grapevine = body.starts_with(&format!("{server_user}: "))
|
||||
let to_grapevine = body
|
||||
.starts_with(&format!("{server_user}: "))
|
||||
|| body.starts_with(&format!("{server_user} "))
|
||||
|| body == format!("{server_user}:")
|
||||
|| body == server_user;
|
||||
|
||||
// This will evaluate to false if the emergency password is set up so that
|
||||
// the administrator can execute commands as grapevine
|
||||
// This will evaluate to false if the emergency password is
|
||||
// set up so that the administrator can
|
||||
// execute commands as grapevine
|
||||
let from_grapevine = pdu.sender == server_user
|
||||
&& services().globals.emergency_password().is_none();
|
||||
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if to_grapevine && !from_grapevine && admin_room == pdu.room_id {
|
||||
if let Some(admin_room) =
|
||||
services().admin.get_admin_room()?
|
||||
{
|
||||
if to_grapevine
|
||||
&& !from_grapevine
|
||||
&& admin_room == pdu.room_id
|
||||
{
|
||||
services().admin.process_message(body);
|
||||
}
|
||||
}
|
||||
|
|
@ -493,7 +558,9 @@ impl Service {
|
|||
|
||||
// Update Relationships
|
||||
|
||||
if let Ok(content) = serde_json::from_str::<ExtractRelatesToEventId>(pdu.content.get()) {
|
||||
if let Ok(content) =
|
||||
serde_json::from_str::<ExtractRelatesToEventId>(pdu.content.get())
|
||||
{
|
||||
if let Some(related_pducount) = services()
|
||||
.rooms
|
||||
.timeline
|
||||
|
|
@ -506,9 +573,13 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
if let Ok(content) = serde_json::from_str::<ExtractRelatesTo>(pdu.content.get()) {
|
||||
if let Ok(content) =
|
||||
serde_json::from_str::<ExtractRelatesTo>(pdu.content.get())
|
||||
{
|
||||
match content.relates_to {
|
||||
Relation::Reply { in_reply_to } => {
|
||||
Relation::Reply {
|
||||
in_reply_to,
|
||||
} => {
|
||||
// We need to do it again here, because replies don't have
|
||||
// event_id as a top level field
|
||||
if let Some(related_pducount) = services()
|
||||
|
|
@ -516,10 +587,10 @@ impl Service {
|
|||
.timeline
|
||||
.get_pdu_count(&in_reply_to.event_id)?
|
||||
{
|
||||
services()
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.add_relation(PduCount::Normal(count2), related_pducount)?;
|
||||
services().rooms.pdu_metadata.add_relation(
|
||||
PduCount::Normal(count2),
|
||||
related_pducount,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Relation::Thread(thread) => {
|
||||
|
|
@ -539,21 +610,24 @@ impl Service {
|
|||
.state_cache
|
||||
.appservice_in_room(&pdu.room_id, appservice)?
|
||||
{
|
||||
services()
|
||||
.sending
|
||||
.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?;
|
||||
services().sending.send_pdu_appservice(
|
||||
appservice.registration.id.clone(),
|
||||
pdu_id.clone(),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the RoomMember event has a non-empty state_key, it is targeted at someone.
|
||||
// If it is our appservice user, we send this PDU to it.
|
||||
// If the RoomMember event has a non-empty state_key, it is targeted
|
||||
// at someone. If it is our appservice user, we send
|
||||
// this PDU to it.
|
||||
if pdu.kind == TimelineEventType::RoomMember {
|
||||
if let Some(state_key_uid) = &pdu
|
||||
.state_key
|
||||
.as_ref()
|
||||
.and_then(|state_key| UserId::parse(state_key.as_str()).ok())
|
||||
if let Some(state_key_uid) =
|
||||
&pdu.state_key.as_ref().and_then(|state_key| {
|
||||
UserId::parse(state_key.as_str()).ok()
|
||||
})
|
||||
{
|
||||
let appservice_uid = appservice.registration.sender_localpart.as_str();
|
||||
let appservice_uid =
|
||||
appservice.registration.sender_localpart.as_str();
|
||||
if state_key_uid == appservice_uid {
|
||||
services().sending.send_pdu_appservice(
|
||||
appservice.registration.id.clone(),
|
||||
|
|
@ -567,10 +641,9 @@ impl Service {
|
|||
let matching_users = |users: &NamespaceRegex| {
|
||||
appservice.users.is_match(pdu.sender.as_str())
|
||||
|| pdu.kind == TimelineEventType::RoomMember
|
||||
&& pdu
|
||||
.state_key
|
||||
.as_ref()
|
||||
.map_or(false, |state_key| users.is_match(state_key))
|
||||
&& pdu.state_key.as_ref().map_or(false, |state_key| {
|
||||
users.is_match(state_key)
|
||||
})
|
||||
};
|
||||
let matching_aliases = |aliases: &NamespaceRegex| {
|
||||
services()
|
||||
|
|
@ -585,9 +658,10 @@ impl Service {
|
|||
|| appservice.rooms.is_match(pdu.room_id.as_str())
|
||||
|| matching_users(&appservice.users)
|
||||
{
|
||||
services()
|
||||
.sending
|
||||
.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?;
|
||||
services().sending.send_pdu_appservice(
|
||||
appservice.registration.id.clone(),
|
||||
pdu_id.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -620,13 +694,13 @@ impl Service {
|
|||
.collect();
|
||||
|
||||
// If there was no create event yet, assume we are creating a room
|
||||
let room_version_id = services()
|
||||
.rooms
|
||||
.state
|
||||
.get_room_version(room_id)
|
||||
.or_else(|_| {
|
||||
let room_version_id =
|
||||
services().rooms.state.get_room_version(room_id).or_else(|_| {
|
||||
if event_type == TimelineEventType::RoomCreate {
|
||||
let content = serde_json::from_str::<RoomCreateEventContent>(content.get())
|
||||
let content =
|
||||
serde_json::from_str::<RoomCreateEventContent>(
|
||||
content.get(),
|
||||
)
|
||||
.expect("Invalid content in RoomCreate pdu.");
|
||||
Ok(content.room_version)
|
||||
} else {
|
||||
|
|
@ -637,7 +711,8 @@ impl Service {
|
|||
}
|
||||
})?;
|
||||
|
||||
let room_version = RoomVersion::new(&room_version_id).expect("room version is supported");
|
||||
let room_version = RoomVersion::new(&room_version_id)
|
||||
.expect("room version is supported");
|
||||
|
||||
let auth_events = services().rooms.state.get_auth_events(
|
||||
room_id,
|
||||
|
|
@ -658,18 +733,22 @@ impl Service {
|
|||
let mut unsigned = unsigned.unwrap_or_default();
|
||||
|
||||
if let Some(state_key) = &state_key {
|
||||
if let Some(prev_pdu) = services().rooms.state_accessor.room_state_get(
|
||||
room_id,
|
||||
&event_type.to_string().into(),
|
||||
state_key,
|
||||
)? {
|
||||
if let Some(prev_pdu) =
|
||||
services().rooms.state_accessor.room_state_get(
|
||||
room_id,
|
||||
&event_type.to_string().into(),
|
||||
state_key,
|
||||
)?
|
||||
{
|
||||
unsigned.insert(
|
||||
"prev_content".to_owned(),
|
||||
serde_json::from_str(prev_pdu.content.get()).expect("string is valid json"),
|
||||
serde_json::from_str(prev_pdu.content.get())
|
||||
.expect("string is valid json"),
|
||||
);
|
||||
unsigned.insert(
|
||||
"prev_sender".to_owned(),
|
||||
serde_json::to_value(&prev_pdu.sender).expect("UserId::to_value always works"),
|
||||
serde_json::to_value(&prev_pdu.sender)
|
||||
.expect("UserId::to_value always works"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -694,7 +773,9 @@ impl Service {
|
|||
unsigned: if unsigned.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(to_raw_value(&unsigned).expect("to_raw_value always works"))
|
||||
Some(
|
||||
to_raw_value(&unsigned).expect("to_raw_value always works"),
|
||||
)
|
||||
},
|
||||
hashes: EventHash {
|
||||
sha256: "aaa".to_owned(),
|
||||
|
|
@ -722,8 +803,8 @@ impl Service {
|
|||
}
|
||||
|
||||
// Hash and sign
|
||||
let mut pdu_json =
|
||||
utils::to_canonical_object(&pdu).expect("event is valid, we just created it");
|
||||
let mut pdu_json = utils::to_canonical_object(&pdu)
|
||||
.expect("event is valid, we just created it");
|
||||
|
||||
pdu_json.remove("event_id");
|
||||
|
||||
|
|
@ -769,16 +850,15 @@ impl Service {
|
|||
);
|
||||
|
||||
// Generate short event id
|
||||
let _shorteventid = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shorteventid(&pdu.event_id)?;
|
||||
let _shorteventid =
|
||||
services().rooms.short.get_or_create_shorteventid(&pdu.event_id)?;
|
||||
|
||||
Ok((pdu, pdu_json))
|
||||
}
|
||||
|
||||
/// Creates a new persisted data unit and adds it to a room. This function takes a
|
||||
/// roomid_mutex_state, meaning that only this function is able to mutate the room state.
|
||||
/// Creates a new persisted data unit and adds it to a room. This function
|
||||
/// takes a roomid_mutex_state, meaning that only this function is able
|
||||
/// to mutate the room state.
|
||||
#[tracing::instrument(skip(self, state_lock))]
|
||||
pub(crate) async fn build_and_append_pdu(
|
||||
&self,
|
||||
|
|
@ -788,8 +868,12 @@ impl Service {
|
|||
// Take mutex guard to make sure users get the room state mutex
|
||||
state_lock: &MutexGuard<'_, ()>,
|
||||
) -> Result<Arc<EventId>> {
|
||||
let (pdu, pdu_json) =
|
||||
self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?;
|
||||
let (pdu, pdu_json) = self.create_hash_and_sign_event(
|
||||
pdu_builder,
|
||||
sender,
|
||||
room_id,
|
||||
state_lock,
|
||||
)?;
|
||||
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if admin_room == room_id {
|
||||
|
|
@ -820,15 +904,24 @@ impl Service {
|
|||
"grapevine"
|
||||
},
|
||||
);
|
||||
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
let content =
|
||||
serde_json::from_str::<ExtractMembership>(
|
||||
pdu.content.get(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in pdu.")
|
||||
})?;
|
||||
|
||||
if content.membership == MembershipState::Leave {
|
||||
if target == server_user {
|
||||
warn!("Grapevine user cannot leave from admins room");
|
||||
warn!(
|
||||
"Grapevine user cannot leave from admins \
|
||||
room"
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Grapevine user cannot leave from admins room.",
|
||||
"Grapevine user cannot leave from admins \
|
||||
room.",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -841,7 +934,9 @@ impl Service {
|
|||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot leave from admins room");
|
||||
warn!(
|
||||
"Last admin cannot leave from admins room"
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot leave from admins room.",
|
||||
|
|
@ -849,12 +944,18 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
|
||||
if content.membership == MembershipState::Ban
|
||||
&& pdu.state_key().is_some()
|
||||
{
|
||||
if target == server_user {
|
||||
warn!("Grapevine user cannot be banned in admins room");
|
||||
warn!(
|
||||
"Grapevine user cannot be banned in \
|
||||
admins room"
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Grapevine user cannot be banned in admins room.",
|
||||
"Grapevine user cannot be banned in \
|
||||
admins room.",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -867,10 +968,14 @@ impl Service {
|
|||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot be banned in admins room");
|
||||
warn!(
|
||||
"Last admin cannot be banned in admins \
|
||||
room"
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot be banned in admins room.",
|
||||
"Last admin cannot be banned in admins \
|
||||
room.",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -880,7 +985,8 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
// If redaction event is not authorized, do not append it to the timeline
|
||||
// If redaction event is not authorized, do not append it to the
|
||||
// timeline
|
||||
if pdu.kind == TimelineEventType::RoomRedaction {
|
||||
match services().rooms.state.get_room_version(&pdu.room_id)? {
|
||||
RoomVersionId::V1
|
||||
|
|
@ -908,11 +1014,12 @@ impl Service {
|
|||
};
|
||||
}
|
||||
RoomVersionId::V11 => {
|
||||
let content =
|
||||
serde_json::from_str::<RoomRedactionEventContent>(pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in redaction pdu.")
|
||||
})?;
|
||||
let content = serde_json::from_str::<
|
||||
RoomRedactionEventContent,
|
||||
>(pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in redaction pdu.")
|
||||
})?;
|
||||
|
||||
if let Some(redact_id) = &content.redacts {
|
||||
if !services().rooms.state_accessor.user_can_redact(
|
||||
|
|
@ -937,27 +1044,30 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
// We append to state before appending the pdu, so we don't have a moment in time with the
|
||||
// pdu without it's state. This is okay because append_pdu can't fail.
|
||||
// We append to state before appending the pdu, so we don't have a
|
||||
// moment in time with the pdu without it's state. This is okay
|
||||
// because append_pdu can't fail.
|
||||
let statehashid = services().rooms.state.append_to_state(&pdu)?;
|
||||
|
||||
let pdu_id = self
|
||||
.append_pdu(
|
||||
&pdu,
|
||||
pdu_json,
|
||||
// Since this PDU references all pdu_leaves we can update the leaves
|
||||
// of the room
|
||||
// Since this PDU references all pdu_leaves we can update the
|
||||
// leaves of the room
|
||||
vec![(*pdu.event_id).to_owned()],
|
||||
state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// We set the room state after inserting the pdu, so that we never have a moment in time
|
||||
// where events in the current room state do not exist
|
||||
services()
|
||||
.rooms
|
||||
.state
|
||||
.set_room_state(room_id, statehashid, state_lock)?;
|
||||
// We set the room state after inserting the pdu, so that we never have
|
||||
// a moment in time where events in the current room state do
|
||||
// not exist
|
||||
services().rooms.state.set_room_state(
|
||||
room_id,
|
||||
statehashid,
|
||||
state_lock,
|
||||
)?;
|
||||
|
||||
let mut servers: HashSet<OwnedServerName> = services()
|
||||
.rooms
|
||||
|
|
@ -966,7 +1076,8 @@ impl Service {
|
|||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
// In case we are kicking or banning a user, we need to inform their server of the change
|
||||
// In case we are kicking or banning a user, we need to inform their
|
||||
// server of the change
|
||||
if pdu.kind == TimelineEventType::RoomMember {
|
||||
if let Some(state_key_uid) = &pdu
|
||||
.state_key
|
||||
|
|
@ -977,7 +1088,8 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove our server from the server list since it will be added to it by room_servers() and/or the if statement above
|
||||
// Remove our server from the server list since it will be added to it
|
||||
// by room_servers() and/or the if statement above
|
||||
servers.remove(services().globals.server_name());
|
||||
|
||||
services().sending.send_pdu(servers.into_iter(), &pdu_id)?;
|
||||
|
|
@ -985,8 +1097,8 @@ impl Service {
|
|||
Ok(pdu.event_id)
|
||||
}
|
||||
|
||||
/// Append the incoming event setting the state snapshot to the state from the
|
||||
/// server that sent the event.
|
||||
/// Append the incoming event setting the state snapshot to the state from
|
||||
/// the server that sent the event.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn append_incoming_pdu(
|
||||
&self,
|
||||
|
|
@ -998,8 +1110,9 @@ impl Service {
|
|||
// Take mutex guard to make sure users get the room state mutex
|
||||
state_lock: &MutexGuard<'_, ()>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
// We append to state before appending the pdu, so we don't have a moment in time with the
|
||||
// pdu without it's state. This is okay because append_pdu can't fail.
|
||||
// We append to state before appending the pdu, so we don't have a
|
||||
// moment in time with the pdu without it's state. This is okay
|
||||
// because append_pdu can't fail.
|
||||
services().rooms.state.set_event_state(
|
||||
&pdu.event_id,
|
||||
&pdu.room_id,
|
||||
|
|
@ -1037,8 +1150,9 @@ impl Service {
|
|||
self.pdus_after(user_id, room_id, PduCount::MIN)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||
/// event with id `until` in reverse-chronological order.
|
||||
/// Returns an iterator over all events and their tokens in a room that
|
||||
/// happened before the event with id `until` in reverse-chronological
|
||||
/// order.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn pdus_until<'a>(
|
||||
&'a self,
|
||||
|
|
@ -1049,8 +1163,8 @@ impl Service {
|
|||
self.db.pdus_until(user_id, room_id, until)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events and their token in a room that happened after the event
|
||||
/// with id `from` in chronological order.
|
||||
/// Returns an iterator over all events and their token in a room that
|
||||
/// happened after the event with id `from` in chronological order.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn pdus_after<'a>(
|
||||
&'a self,
|
||||
|
|
@ -1063,13 +1177,18 @@ impl Service {
|
|||
|
||||
/// Replace a PDU with the redacted form.
|
||||
#[tracing::instrument(skip(self, reason))]
|
||||
pub(crate) fn redact_pdu(&self, event_id: &EventId, reason: &PduEvent) -> Result<()> {
|
||||
pub(crate) fn redact_pdu(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
reason: &PduEvent,
|
||||
) -> Result<()> {
|
||||
// TODO: Don't reserialize, keep original json
|
||||
if let Some(pdu_id) = self.get_pdu_id(event_id)? {
|
||||
let mut pdu = self
|
||||
.get_pdu_from_id(&pdu_id)?
|
||||
.ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?;
|
||||
let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?;
|
||||
let mut pdu = self.get_pdu_from_id(&pdu_id)?.ok_or_else(|| {
|
||||
Error::bad_database("PDU ID points to invalid PDU.")
|
||||
})?;
|
||||
let room_version_id =
|
||||
services().rooms.state.get_room_version(&pdu.room_id)?;
|
||||
pdu.redact(room_version_id, reason)?;
|
||||
self.replace_pdu(
|
||||
&pdu_id,
|
||||
|
|
@ -1102,8 +1221,9 @@ impl Service {
|
|||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
.map(|ev| {
|
||||
serde_json::from_str(ev.content.get())
|
||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
||||
serde_json::from_str(ev.content.get()).map_err(|_| {
|
||||
Error::bad_database("invalid m.room.power_levels event")
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
|
@ -1133,7 +1253,9 @@ impl Service {
|
|||
Ok(response) => {
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
for pdu in response.pdus {
|
||||
if let Err(e) = self.backfill_pdu(backfill_server, pdu, &pub_key_map).await
|
||||
if let Err(e) = self
|
||||
.backfill_pdu(backfill_server, pdu, &pub_key_map)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to add backfilled pdu: {e}");
|
||||
}
|
||||
|
|
@ -1157,7 +1279,8 @@ impl Service {
|
|||
pdu: Box<RawJsonValue>,
|
||||
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
||||
) -> Result<()> {
|
||||
let (event_id, value, room_id) = server_server::parse_incoming_pdu(&pdu)?;
|
||||
let (event_id, value, room_id) =
|
||||
server_server::parse_incoming_pdu(&pdu)?;
|
||||
|
||||
// Lock so we cannot backfill the same pdu twice at the same time
|
||||
let mutex = Arc::clone(
|
||||
|
|
@ -1180,7 +1303,14 @@ impl Service {
|
|||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(origin, &event_id, &room_id, value, false, pub_key_map)
|
||||
.handle_incoming_pdu(
|
||||
origin,
|
||||
&event_id,
|
||||
&room_id,
|
||||
value,
|
||||
false,
|
||||
pub_key_map,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let value = self.get_pdu_json(&event_id)?.expect("We just created it");
|
||||
|
|
@ -1219,14 +1349,18 @@ impl Service {
|
|||
body: Option<String>,
|
||||
}
|
||||
|
||||
let content = serde_json::from_str::<ExtractBody>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
let content =
|
||||
serde_json::from_str::<ExtractBody>(pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid content in pdu.")
|
||||
})?;
|
||||
|
||||
if let Some(body) = content.body {
|
||||
services()
|
||||
.rooms
|
||||
.search
|
||||
.index_pdu(shortroomid, &pdu_id, &body)?;
|
||||
services().rooms.search.index_pdu(
|
||||
shortroomid,
|
||||
&pdu_id,
|
||||
&body,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
drop(mutex_lock);
|
||||
|
|
|
|||
|
|
@ -2,21 +2,30 @@ use std::sync::Arc;
|
|||
|
||||
use ruma::{CanonicalJsonObject, EventId, OwnedUserId, RoomId, UserId};
|
||||
|
||||
use super::PduCount;
|
||||
use crate::{PduEvent, Result};
|
||||
|
||||
use super::PduCount;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn last_timeline_count(&self, sender_user: &UserId, room_id: &RoomId) -> Result<PduCount>;
|
||||
fn last_timeline_count(
|
||||
&self,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<PduCount>;
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<PduCount>>;
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<CanonicalJsonObject>>;
|
||||
fn get_pdu_json(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<CanonicalJsonObject>>;
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
fn get_non_outlier_pdu_json(&self, event_id: &EventId) -> Result<Option<CanonicalJsonObject>>;
|
||||
fn get_non_outlier_pdu_json(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<CanonicalJsonObject>>;
|
||||
|
||||
/// Returns the pdu's id.
|
||||
fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<Vec<u8>>>;
|
||||
|
|
@ -24,7 +33,10 @@ pub(crate) trait Data: Send + Sync {
|
|||
/// Returns the pdu.
|
||||
///
|
||||
/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
|
||||
fn get_non_outlier_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>>;
|
||||
fn get_non_outlier_pdu(
|
||||
&self,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<PduEvent>>;
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
|
|
@ -37,7 +49,10 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn get_pdu_from_id(&self, pdu_id: &[u8]) -> Result<Option<PduEvent>>;
|
||||
|
||||
/// Returns the pdu as a `BTreeMap<String, CanonicalJsonValue>`.
|
||||
fn get_pdu_json_from_id(&self, pdu_id: &[u8]) -> Result<Option<CanonicalJsonObject>>;
|
||||
fn get_pdu_json_from_id(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
) -> Result<Option<CanonicalJsonObject>>;
|
||||
|
||||
/// Adds a new pdu to the timeline
|
||||
fn append_pdu(
|
||||
|
|
@ -64,8 +79,9 @@ pub(crate) trait Data: Send + Sync {
|
|||
pdu: &PduEvent,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||
/// event with id `until` in reverse-chronological order.
|
||||
/// Returns an iterator over all events and their tokens in a room that
|
||||
/// happened before the event with id `until` in reverse-chronological
|
||||
/// order.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn pdus_until<'a>(
|
||||
&'a self,
|
||||
|
|
@ -74,8 +90,8 @@ pub(crate) trait Data: Send + Sync {
|
|||
until: PduCount,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
||||
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `from`
|
||||
/// in chronological order.
|
||||
/// Returns an iterator over all events in a room that happened after the
|
||||
/// event with id `from` in chronological order.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn pdus_after<'a>(
|
||||
&'a self,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
use crate::Result;
|
||||
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn reset_notification_counts(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
||||
fn reset_notification_counts(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<()>;
|
||||
|
||||
fn notification_count(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64>;
|
||||
fn notification_count(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<u64>;
|
||||
|
||||
fn highlight_count(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64>;
|
||||
fn highlight_count(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<u64>;
|
||||
|
||||
// Returns the count at which the last reset_notification_counts was called
|
||||
fn last_notification_read(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64>;
|
||||
fn last_notification_read(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<u64>;
|
||||
|
||||
fn associate_token_shortstatehash(
|
||||
&self,
|
||||
|
|
@ -18,7 +35,11 @@ pub(crate) trait Data: Send + Sync {
|
|||
shortstatehash: u64,
|
||||
) -> Result<()>;
|
||||
|
||||
fn get_token_shortstatehash(&self, room_id: &RoomId, token: u64) -> Result<Option<u64>>;
|
||||
fn get_token_shortstatehash(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
token: u64,
|
||||
) -> Result<Option<u64>>;
|
||||
|
||||
fn get_shared_rooms<'a>(
|
||||
&'a self,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
mod data;
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
|
|
@ -9,34 +7,29 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api::{appservice_server, server_server},
|
||||
services,
|
||||
utils::calculate_hash,
|
||||
Config, Error, PduEvent, Result,
|
||||
};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
pub(crate) use data::Data;
|
||||
use federation::transactions::send_transaction_message;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice::{self, Registration},
|
||||
federation::{
|
||||
self,
|
||||
transactions::edu::{
|
||||
DeviceListUpdateContent, Edu, ReceiptContent, ReceiptData, ReceiptMap,
|
||||
DeviceListUpdateContent, Edu, ReceiptContent, ReceiptData,
|
||||
ReceiptMap,
|
||||
},
|
||||
},
|
||||
OutgoingRequest,
|
||||
},
|
||||
device_id,
|
||||
events::{
|
||||
push_rules::PushRulesEvent, receipt::ReceiptType, AnySyncEphemeralRoomEvent,
|
||||
GlobalAccountDataEventType,
|
||||
push_rules::PushRulesEvent, receipt::ReceiptType,
|
||||
AnySyncEphemeralRoomEvent, GlobalAccountDataEventType,
|
||||
},
|
||||
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId, ServerName, UInt, UserId,
|
||||
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId,
|
||||
ServerName, UInt, UserId,
|
||||
};
|
||||
use tokio::{
|
||||
select,
|
||||
|
|
@ -44,6 +37,13 @@ use tokio::{
|
|||
};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{
|
||||
api::{appservice_server, server_server},
|
||||
services,
|
||||
utils::calculate_hash,
|
||||
Config, Error, PduEvent, Result,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum OutgoingKind {
|
||||
Appservice(String),
|
||||
|
|
@ -64,7 +64,7 @@ impl OutgoingKind {
|
|||
OutgoingKind::Push(user, pushkey) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(user.as_bytes());
|
||||
p.push(0xff);
|
||||
p.push(0xFF);
|
||||
p.extend_from_slice(pushkey.as_bytes());
|
||||
p
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ impl OutgoingKind {
|
|||
p
|
||||
}
|
||||
};
|
||||
prefix.push(0xff);
|
||||
prefix.push(0xFF);
|
||||
|
||||
prefix
|
||||
}
|
||||
|
|
@ -93,8 +93,11 @@ pub(crate) struct Service {
|
|||
|
||||
/// The state for a given state hash.
|
||||
pub(super) maximum_requests: Arc<Semaphore>,
|
||||
pub(crate) sender: mpsc::UnboundedSender<(OutgoingKind, SendingEventType, Vec<u8>)>,
|
||||
receiver: Mutex<mpsc::UnboundedReceiver<(OutgoingKind, SendingEventType, Vec<u8>)>>,
|
||||
pub(crate) sender:
|
||||
mpsc::UnboundedSender<(OutgoingKind, SendingEventType, Vec<u8>)>,
|
||||
receiver: Mutex<
|
||||
mpsc::UnboundedReceiver<(OutgoingKind, SendingEventType, Vec<u8>)>,
|
||||
>,
|
||||
}
|
||||
|
||||
enum TransactionStatus {
|
||||
|
|
@ -112,7 +115,9 @@ impl Service {
|
|||
db,
|
||||
sender,
|
||||
receiver: Mutex::new(receiver),
|
||||
maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests.into())),
|
||||
maximum_requests: Arc::new(Semaphore::new(
|
||||
config.max_concurrent_requests.into(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -129,15 +134,18 @@ impl Service {
|
|||
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
let mut current_transaction_status = HashMap::<OutgoingKind, TransactionStatus>::new();
|
||||
let mut current_transaction_status =
|
||||
HashMap::<OutgoingKind, TransactionStatus>::new();
|
||||
|
||||
// Retry requests we could not finish yet
|
||||
let mut initial_transactions = HashMap::<OutgoingKind, Vec<SendingEventType>>::new();
|
||||
let mut initial_transactions =
|
||||
HashMap::<OutgoingKind, Vec<SendingEventType>>::new();
|
||||
|
||||
for (key, outgoing_kind, event) in self.db.active_requests().filter_map(Result::ok) {
|
||||
let entry = initial_transactions
|
||||
.entry(outgoing_kind.clone())
|
||||
.or_default();
|
||||
for (key, outgoing_kind, event) in
|
||||
self.db.active_requests().filter_map(Result::ok)
|
||||
{
|
||||
let entry =
|
||||
initial_transactions.entry(outgoing_kind.clone()).or_default();
|
||||
|
||||
if entry.len() > 30 {
|
||||
warn!(
|
||||
|
|
@ -152,77 +160,90 @@ impl Service {
|
|||
}
|
||||
|
||||
for (outgoing_kind, events) in initial_transactions {
|
||||
current_transaction_status.insert(outgoing_kind.clone(), TransactionStatus::Running);
|
||||
current_transaction_status
|
||||
.insert(outgoing_kind.clone(), TransactionStatus::Running);
|
||||
futures.push(Self::handle_events(outgoing_kind.clone(), events));
|
||||
}
|
||||
|
||||
let handle_futures = |response,
|
||||
current_transaction_status: &mut HashMap<_, _>,
|
||||
futures: &mut FuturesUnordered<_>| {
|
||||
match response {
|
||||
Ok(outgoing_kind) => {
|
||||
self.db.delete_all_active_requests_for(&outgoing_kind)?;
|
||||
let handle_futures =
|
||||
|response,
|
||||
current_transaction_status: &mut HashMap<_, _>,
|
||||
futures: &mut FuturesUnordered<_>| {
|
||||
match response {
|
||||
Ok(outgoing_kind) => {
|
||||
self.db
|
||||
.delete_all_active_requests_for(&outgoing_kind)?;
|
||||
|
||||
// Find events that have been added since starting the
|
||||
// last request
|
||||
let new_events = self
|
||||
.db
|
||||
.queued_requests(&outgoing_kind)
|
||||
.filter_map(Result::ok)
|
||||
.take(30)
|
||||
.collect::<Vec<_>>();
|
||||
// Find events that have been added since starting the
|
||||
// last request
|
||||
let new_events = self
|
||||
.db
|
||||
.queued_requests(&outgoing_kind)
|
||||
.filter_map(Result::ok)
|
||||
.take(30)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if new_events.is_empty() {
|
||||
current_transaction_status.remove(&outgoing_kind);
|
||||
} else {
|
||||
// Insert pdus we found
|
||||
self.db.mark_as_active(&new_events)?;
|
||||
if new_events.is_empty() {
|
||||
current_transaction_status.remove(&outgoing_kind);
|
||||
} else {
|
||||
// Insert pdus we found
|
||||
self.db.mark_as_active(&new_events)?;
|
||||
|
||||
futures.push(Self::handle_events(
|
||||
outgoing_kind.clone(),
|
||||
new_events.into_iter().map(|(event, _)| event).collect(),
|
||||
));
|
||||
futures.push(Self::handle_events(
|
||||
outgoing_kind.clone(),
|
||||
new_events
|
||||
.into_iter()
|
||||
.map(|(event, _)| event)
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err((outgoing_kind, _)) => {
|
||||
current_transaction_status
|
||||
.entry(outgoing_kind)
|
||||
.and_modify(|e| {
|
||||
*e = match e {
|
||||
TransactionStatus::Running => {
|
||||
TransactionStatus::Failed(1, Instant::now())
|
||||
}
|
||||
TransactionStatus::Retrying(n) => {
|
||||
TransactionStatus::Failed(*n + 1, Instant::now())
|
||||
}
|
||||
TransactionStatus::Failed(..) => {
|
||||
error!(
|
||||
"Request that was not even \
|
||||
Err((outgoing_kind, _)) => {
|
||||
current_transaction_status
|
||||
.entry(outgoing_kind)
|
||||
.and_modify(|e| {
|
||||
*e = match e {
|
||||
TransactionStatus::Running => {
|
||||
TransactionStatus::Failed(
|
||||
1,
|
||||
Instant::now(),
|
||||
)
|
||||
}
|
||||
TransactionStatus::Retrying(n) => {
|
||||
TransactionStatus::Failed(
|
||||
*n + 1,
|
||||
Instant::now(),
|
||||
)
|
||||
}
|
||||
TransactionStatus::Failed(..) => {
|
||||
error!(
|
||||
"Request that was not even \
|
||||
running failed?!"
|
||||
);
|
||||
return;
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Result::<_>::Ok(())
|
||||
};
|
||||
|
||||
Result::<_>::Ok(())
|
||||
};
|
||||
|
||||
let handle_receiver = |outgoing_kind,
|
||||
event,
|
||||
key,
|
||||
current_transaction_status: &mut HashMap<_, _>,
|
||||
futures: &mut FuturesUnordered<_>| {
|
||||
if let Ok(Some(events)) = self.select_events(
|
||||
&outgoing_kind,
|
||||
vec![(event, key)],
|
||||
current_transaction_status,
|
||||
) {
|
||||
futures.push(Self::handle_events(outgoing_kind, events));
|
||||
}
|
||||
};
|
||||
let handle_receiver =
|
||||
|outgoing_kind,
|
||||
event,
|
||||
key,
|
||||
current_transaction_status: &mut HashMap<_, _>,
|
||||
futures: &mut FuturesUnordered<_>| {
|
||||
if let Ok(Some(events)) = self.select_events(
|
||||
&outgoing_kind,
|
||||
vec![(event, key)],
|
||||
current_transaction_status,
|
||||
) {
|
||||
futures.push(Self::handle_events(outgoing_kind, events));
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
select! {
|
||||
|
|
@ -244,13 +265,21 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, outgoing_kind, new_events, current_transaction_status))]
|
||||
#[tracing::instrument(skip(
|
||||
self,
|
||||
outgoing_kind,
|
||||
new_events,
|
||||
current_transaction_status
|
||||
))]
|
||||
fn select_events(
|
||||
&self,
|
||||
outgoing_kind: &OutgoingKind,
|
||||
// Events we want to send: event and full key
|
||||
new_events: Vec<(SendingEventType, Vec<u8>)>,
|
||||
current_transaction_status: &mut HashMap<OutgoingKind, TransactionStatus>,
|
||||
current_transaction_status: &mut HashMap<
|
||||
OutgoingKind,
|
||||
TransactionStatus,
|
||||
>,
|
||||
) -> Result<Option<Vec<SendingEventType>>> {
|
||||
let mut retry = false;
|
||||
let mut allow = true;
|
||||
|
|
@ -264,10 +293,14 @@ impl Service {
|
|||
allow = false;
|
||||
}
|
||||
TransactionStatus::Failed(tries, time) => {
|
||||
// Fail if a request has failed recently (exponential backoff)
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
// Fail if a request has failed recently (exponential
|
||||
// backoff)
|
||||
let mut min_elapsed_duration =
|
||||
Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24)
|
||||
{
|
||||
min_elapsed_duration =
|
||||
Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
|
|
@ -302,8 +335,12 @@ impl Service {
|
|||
}
|
||||
|
||||
if let OutgoingKind::Normal(server_name) = outgoing_kind {
|
||||
if let Ok((select_edus, last_count)) = self.select_edus(server_name) {
|
||||
events.extend(select_edus.into_iter().map(SendingEventType::Edu));
|
||||
if let Ok((select_edus, last_count)) =
|
||||
self.select_edus(server_name)
|
||||
{
|
||||
events.extend(
|
||||
select_edus.into_iter().map(SendingEventType::Edu),
|
||||
);
|
||||
|
||||
self.db.set_latest_educount(server_name, last_count)?;
|
||||
}
|
||||
|
|
@ -314,14 +351,19 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self, server_name))]
|
||||
pub(crate) fn select_edus(&self, server_name: &ServerName) -> Result<(Vec<Vec<u8>>, u64)> {
|
||||
pub(crate) fn select_edus(
|
||||
&self,
|
||||
server_name: &ServerName,
|
||||
) -> Result<(Vec<Vec<u8>>, u64)> {
|
||||
// u64: count of last edu
|
||||
let since = self.db.get_latest_educount(server_name)?;
|
||||
let mut events = Vec::new();
|
||||
let mut max_edu_count = since;
|
||||
let mut device_list_changes = HashSet::new();
|
||||
|
||||
'outer: for room_id in services().rooms.state_cache.server_rooms(server_name) {
|
||||
'outer: for room_id in
|
||||
services().rooms.state_cache.server_rooms(server_name)
|
||||
{
|
||||
let room_id = room_id?;
|
||||
// Look for device list updates in this room
|
||||
device_list_changes.extend(
|
||||
|
|
@ -329,7 +371,10 @@ impl Service {
|
|||
.users
|
||||
.keys_changed(room_id.as_ref(), since, None)
|
||||
.filter_map(Result::ok)
|
||||
.filter(|user_id| user_id.server_name() == services().globals.server_name()),
|
||||
.filter(|user_id| {
|
||||
user_id.server_name()
|
||||
== services().globals.server_name()
|
||||
}),
|
||||
);
|
||||
|
||||
// Look for read receipts in this room
|
||||
|
|
@ -349,44 +394,57 @@ impl Service {
|
|||
continue;
|
||||
}
|
||||
|
||||
let event: AnySyncEphemeralRoomEvent =
|
||||
serde_json::from_str(read_receipt.json().get())
|
||||
.map_err(|_| Error::bad_database("Invalid edu event in read_receipts."))?;
|
||||
let federation_event = if let AnySyncEphemeralRoomEvent::Receipt(r) = event {
|
||||
let mut read = BTreeMap::new();
|
||||
let event: AnySyncEphemeralRoomEvent = serde_json::from_str(
|
||||
read_receipt.json().get(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid edu event in read_receipts.")
|
||||
})?;
|
||||
let federation_event =
|
||||
if let AnySyncEphemeralRoomEvent::Receipt(r) = event {
|
||||
let mut read = BTreeMap::new();
|
||||
|
||||
let (event_id, mut receipt) = r
|
||||
.content
|
||||
.0
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("we only use one event per read receipt");
|
||||
let receipt = receipt
|
||||
.remove(&ReceiptType::Read)
|
||||
.expect("our read receipts always set this")
|
||||
.remove(&user_id)
|
||||
.expect("our read receipts always have the user here");
|
||||
let (event_id, mut receipt) =
|
||||
r.content.0.into_iter().next().expect(
|
||||
"we only use one event per read receipt",
|
||||
);
|
||||
let receipt = receipt
|
||||
.remove(&ReceiptType::Read)
|
||||
.expect("our read receipts always set this")
|
||||
.remove(&user_id)
|
||||
.expect(
|
||||
"our read receipts always have the user here",
|
||||
);
|
||||
|
||||
read.insert(
|
||||
user_id,
|
||||
ReceiptData {
|
||||
data: receipt.clone(),
|
||||
event_ids: vec![event_id.clone()],
|
||||
},
|
||||
);
|
||||
read.insert(
|
||||
user_id,
|
||||
ReceiptData {
|
||||
data: receipt.clone(),
|
||||
event_ids: vec![event_id.clone()],
|
||||
},
|
||||
);
|
||||
|
||||
let receipt_map = ReceiptMap { read };
|
||||
let receipt_map = ReceiptMap {
|
||||
read,
|
||||
};
|
||||
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(room_id.clone(), receipt_map);
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(room_id.clone(), receipt_map);
|
||||
|
||||
Edu::Receipt(ReceiptContent { receipts })
|
||||
} else {
|
||||
Error::bad_database("Invalid event type in read_receipts");
|
||||
continue;
|
||||
};
|
||||
Edu::Receipt(ReceiptContent {
|
||||
receipts,
|
||||
})
|
||||
} else {
|
||||
Error::bad_database(
|
||||
"Invalid event type in read_receipts",
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
events.push(serde_json::to_vec(&federation_event).expect("json can be serialized"));
|
||||
events.push(
|
||||
serde_json::to_vec(&federation_event)
|
||||
.expect("json can be serialized"),
|
||||
);
|
||||
|
||||
if events.len() >= 20 {
|
||||
break 'outer;
|
||||
|
|
@ -407,7 +465,9 @@ impl Service {
|
|||
keys: None,
|
||||
});
|
||||
|
||||
events.push(serde_json::to_vec(&edu).expect("json can be serialized"));
|
||||
events.push(
|
||||
serde_json::to_vec(&edu).expect("json can be serialized"),
|
||||
);
|
||||
}
|
||||
|
||||
Ok((events, max_edu_count))
|
||||
|
|
@ -422,7 +482,8 @@ impl Service {
|
|||
) -> Result<()> {
|
||||
let outgoing_kind = OutgoingKind::Push(user.to_owned(), pushkey);
|
||||
let event = SendingEventType::Pdu(pdu_id.to_owned());
|
||||
let keys = self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
let keys =
|
||||
self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
self.sender
|
||||
.send((outgoing_kind, event, keys.into_iter().next().unwrap()))
|
||||
.unwrap();
|
||||
|
|
@ -446,15 +507,10 @@ impl Service {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
let keys = self.db.queue_requests(
|
||||
&requests
|
||||
.iter()
|
||||
.map(|(o, e)| (o, e.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
&requests.iter().map(|(o, e)| (o, e.clone())).collect::<Vec<_>>(),
|
||||
)?;
|
||||
for ((outgoing_kind, event), key) in requests.into_iter().zip(keys) {
|
||||
self.sender
|
||||
.send((outgoing_kind.clone(), event, key))
|
||||
.unwrap();
|
||||
self.sender.send((outgoing_kind.clone(), event, key)).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -469,7 +525,8 @@ impl Service {
|
|||
) -> Result<()> {
|
||||
let outgoing_kind = OutgoingKind::Normal(server.to_owned());
|
||||
let event = SendingEventType::Edu(serialized);
|
||||
let keys = self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
let keys =
|
||||
self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
self.sender
|
||||
.send((outgoing_kind, event, keys.into_iter().next().unwrap()))
|
||||
.unwrap();
|
||||
|
|
@ -478,10 +535,15 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn send_pdu_appservice(&self, appservice_id: String, pdu_id: Vec<u8>) -> Result<()> {
|
||||
pub(crate) fn send_pdu_appservice(
|
||||
&self,
|
||||
appservice_id: String,
|
||||
pdu_id: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let outgoing_kind = OutgoingKind::Appservice(appservice_id);
|
||||
let event = SendingEventType::Pdu(pdu_id);
|
||||
let keys = self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
let keys =
|
||||
self.db.queue_requests(&[(&outgoing_kind, event.clone())])?;
|
||||
self.sender
|
||||
.send((outgoing_kind, event, keys.into_iter().next().unwrap()))
|
||||
.unwrap();
|
||||
|
|
@ -493,8 +555,9 @@ impl Service {
|
|||
/// Used for instance after we remove an appservice registration
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(crate) fn cleanup_events(&self, appservice_id: String) -> Result<()> {
|
||||
self.db
|
||||
.delete_all_requests_for(&OutgoingKind::Appservice(appservice_id))?;
|
||||
self.db.delete_all_requests_for(&OutgoingKind::Appservice(
|
||||
appservice_id,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -511,18 +574,24 @@ impl Service {
|
|||
for event in &events {
|
||||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
pdu_jsons.push(services().rooms.timeline
|
||||
.get_pdu_from_id(pdu_id)
|
||||
.map_err(|e| (kind.clone(), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Appservice] Event in servernameevent_data not found in db.",
|
||||
),
|
||||
)
|
||||
})?
|
||||
.to_room_event());
|
||||
pdu_jsons.push(
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(pdu_id)
|
||||
.map_err(|e| (kind.clone(), e))?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Appservice] Event in \
|
||||
servernameevent_data not \
|
||||
found in db.",
|
||||
),
|
||||
)
|
||||
})?
|
||||
.to_room_event(),
|
||||
);
|
||||
}
|
||||
SendingEventType::Edu(_) => {
|
||||
// Appservices don't need EDUs (?)
|
||||
|
|
@ -530,7 +599,8 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
let permit = services().sending.maximum_requests.acquire().await;
|
||||
let permit =
|
||||
services().sending.maximum_requests.acquire().await;
|
||||
|
||||
let response = match appservice_server::send_request(
|
||||
services()
|
||||
|
|
@ -541,20 +611,24 @@ impl Service {
|
|||
(
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Appservice] Could not load registration from db.",
|
||||
"[Appservice] Could not load registration \
|
||||
from db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
appservice::event::push_events::v1::Request {
|
||||
events: pdu_jsons,
|
||||
txn_id: (&*general_purpose::URL_SAFE_NO_PAD.encode(calculate_hash(
|
||||
&events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)))
|
||||
txn_id: (&*general_purpose::URL_SAFE_NO_PAD.encode(
|
||||
calculate_hash(
|
||||
&events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b)
|
||||
| SendingEventType::Pdu(b) => &**b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
))
|
||||
.into(),
|
||||
},
|
||||
)
|
||||
|
|
@ -575,7 +649,8 @@ impl Service {
|
|||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
pdus.push(
|
||||
services().rooms
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(pdu_id)
|
||||
.map_err(|e| (kind.clone(), e))?
|
||||
|
|
@ -583,7 +658,9 @@ impl Service {
|
|||
(
|
||||
kind.clone(),
|
||||
Error::bad_database(
|
||||
"[Push] Event in servernamevent_datas not found in db.",
|
||||
"[Push] Event in \
|
||||
servernamevent_datas not \
|
||||
found in db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
|
|
@ -595,10 +672,13 @@ impl Service {
|
|||
}
|
||||
|
||||
for pdu in pdus {
|
||||
// Redacted events are not notification targets (we don't send push for them)
|
||||
// Redacted events are not notification targets (we don't
|
||||
// send push for them)
|
||||
if let Some(unsigned) = &pdu.unsigned {
|
||||
if let Ok(unsigned) =
|
||||
serde_json::from_str::<serde_json::Value>(unsigned.get())
|
||||
serde_json::from_str::<serde_json::Value>(
|
||||
unsigned.get(),
|
||||
)
|
||||
{
|
||||
if unsigned.get("redacted_because").is_some() {
|
||||
continue;
|
||||
|
|
@ -609,7 +689,15 @@ impl Service {
|
|||
let Some(pusher) = services()
|
||||
.pusher
|
||||
.get_pusher(userid, pushkey)
|
||||
.map_err(|e| (OutgoingKind::Push(userid.clone(), pushkey.clone()), e))?
|
||||
.map_err(|e| {
|
||||
(
|
||||
OutgoingKind::Push(
|
||||
userid.clone(),
|
||||
pushkey.clone(),
|
||||
),
|
||||
e,
|
||||
)
|
||||
})?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -619,10 +707,15 @@ impl Service {
|
|||
.get(
|
||||
None,
|
||||
userid,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
GlobalAccountDataEventType::PushRules
|
||||
.to_string()
|
||||
.into(),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.and_then(|event| serde_json::from_str::<PushRulesEvent>(event.get()).ok())
|
||||
.and_then(|event| {
|
||||
serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.ok()
|
||||
})
|
||||
.map_or_else(
|
||||
|| push::Ruleset::server_default(userid),
|
||||
|ev: PushRulesEvent| ev.content.global,
|
||||
|
|
@ -636,11 +729,18 @@ impl Service {
|
|||
.try_into()
|
||||
.expect("notification count can't go that high");
|
||||
|
||||
let permit = services().sending.maximum_requests.acquire().await;
|
||||
let permit =
|
||||
services().sending.maximum_requests.acquire().await;
|
||||
|
||||
let _response = services()
|
||||
.pusher
|
||||
.send_push_notice(userid, unread, &pusher, rules_for_user, &pdu)
|
||||
.send_push_notice(
|
||||
userid,
|
||||
unread,
|
||||
&pusher,
|
||||
rules_for_user,
|
||||
&pdu,
|
||||
)
|
||||
.await
|
||||
.map(|_response| kind.clone())
|
||||
.map_err(|e| (kind.clone(), e));
|
||||
|
|
@ -656,22 +756,39 @@ impl Service {
|
|||
for event in &events {
|
||||
match event {
|
||||
SendingEventType::Pdu(pdu_id) => {
|
||||
// TODO: check room version and remove event_id if needed
|
||||
let raw = PduEvent::convert_to_outgoing_federation_event(
|
||||
services().rooms
|
||||
.timeline
|
||||
.get_pdu_json_from_id(pdu_id)
|
||||
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))?
|
||||
.ok_or_else(|| {
|
||||
error!("event not found: {server} {pdu_id:?}");
|
||||
(
|
||||
OutgoingKind::Normal(server.clone()),
|
||||
Error::bad_database(
|
||||
"[Normal] Event in servernamevent_datas not found in db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
// TODO: check room version and remove event_id if
|
||||
// needed
|
||||
let raw =
|
||||
PduEvent::convert_to_outgoing_federation_event(
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json_from_id(pdu_id)
|
||||
.map_err(|e| {
|
||||
(
|
||||
OutgoingKind::Normal(
|
||||
server.clone(),
|
||||
),
|
||||
e,
|
||||
)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
error!(
|
||||
"event not found: {server} \
|
||||
{pdu_id:?}"
|
||||
);
|
||||
(
|
||||
OutgoingKind::Normal(
|
||||
server.clone(),
|
||||
),
|
||||
Error::bad_database(
|
||||
"[Normal] Event in \
|
||||
servernamevent_datas not \
|
||||
found in db.",
|
||||
),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
pdu_jsons.push(raw);
|
||||
}
|
||||
SendingEventType::Edu(edu) => {
|
||||
|
|
@ -682,7 +799,8 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
let permit = services().sending.maximum_requests.acquire().await;
|
||||
let permit =
|
||||
services().sending.maximum_requests.acquire().await;
|
||||
|
||||
let response = server_server::send_request(
|
||||
server,
|
||||
|
|
@ -691,16 +809,16 @@ impl Service {
|
|||
pdus: pdu_jsons,
|
||||
edus: edu_jsons,
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
transaction_id: (&*general_purpose::URL_SAFE_NO_PAD.encode(
|
||||
calculate_hash(
|
||||
transaction_id: (&*general_purpose::URL_SAFE_NO_PAD
|
||||
.encode(calculate_hash(
|
||||
&events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
SendingEventType::Edu(b)
|
||||
| SendingEventType::Pdu(b) => &**b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
))
|
||||
)))
|
||||
.into(),
|
||||
},
|
||||
)
|
||||
|
|
@ -750,7 +868,8 @@ impl Service {
|
|||
|
||||
/// Sends a request to an appservice
|
||||
///
|
||||
/// Only returns None if there is no url specified in the appservice registration file
|
||||
/// Only returns None if there is no url specified in the appservice
|
||||
/// registration file
|
||||
#[tracing::instrument(skip(self, registration, request))]
|
||||
pub(crate) async fn send_appservice_request<T: OutgoingRequest>(
|
||||
&self,
|
||||
|
|
@ -761,7 +880,8 @@ impl Service {
|
|||
T: Debug,
|
||||
{
|
||||
let permit = self.maximum_requests.acquire().await;
|
||||
let response = appservice_server::send_request(registration, request).await;
|
||||
let response =
|
||||
appservice_server::send_request(registration, request).await;
|
||||
drop(permit);
|
||||
|
||||
response
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
use ruma::ServerName;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
use super::{OutgoingKind, SendingEventType};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn active_requests<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>> + 'a>;
|
||||
) -> Box<
|
||||
dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>>
|
||||
+ 'a,
|
||||
>;
|
||||
fn active_requests_for<'a>(
|
||||
&'a self,
|
||||
outgoing_kind: &OutgoingKind,
|
||||
) -> Box<dyn Iterator<Item = Result<(Vec<u8>, SendingEventType)>> + 'a>;
|
||||
fn delete_active_request(&self, key: Vec<u8>) -> Result<()>;
|
||||
fn delete_all_active_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
||||
fn delete_all_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
||||
fn delete_all_active_requests_for(
|
||||
&self,
|
||||
outgoing_kind: &OutgoingKind,
|
||||
) -> Result<()>;
|
||||
fn delete_all_requests_for(
|
||||
&self,
|
||||
outgoing_kind: &OutgoingKind,
|
||||
) -> Result<()>;
|
||||
fn queue_requests(
|
||||
&self,
|
||||
requests: &[(&OutgoingKind, SendingEventType)],
|
||||
|
|
@ -24,7 +32,14 @@ pub(crate) trait Data: Send + Sync {
|
|||
&'a self,
|
||||
outgoing_kind: &OutgoingKind,
|
||||
) -> Box<dyn Iterator<Item = Result<(SendingEventType, Vec<u8>)>> + 'a>;
|
||||
fn mark_as_active(&self, events: &[(SendingEventType, Vec<u8>)]) -> Result<()>;
|
||||
fn set_latest_educount(&self, server_name: &ServerName, educount: u64) -> Result<()>;
|
||||
fn mark_as_active(
|
||||
&self,
|
||||
events: &[(SendingEventType, Vec<u8>)],
|
||||
) -> Result<()>;
|
||||
fn set_latest_educount(
|
||||
&self,
|
||||
server_name: &ServerName,
|
||||
educount: u64,
|
||||
) -> Result<()>;
|
||||
fn get_latest_educount(&self, server_name: &ServerName) -> Result<u64>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Result;
|
||||
use ruma::{DeviceId, TransactionId, UserId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn add_txnid(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
mod data;
|
||||
|
||||
pub(crate) use data::Data;
|
||||
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
|
|
@ -11,7 +10,9 @@ use ruma::{
|
|||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{api::client_server::SESSION_ID_LENGTH, services, utils, Error, Result};
|
||||
use crate::{
|
||||
api::client_server::SESSION_ID_LENGTH, services, utils, Error, Result,
|
||||
};
|
||||
|
||||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
|
|
@ -29,7 +30,8 @@ impl Service {
|
|||
self.db.set_uiaa_request(
|
||||
user_id,
|
||||
device_id,
|
||||
// TODO: better session error handling (why is it optional in ruma?)
|
||||
// TODO: better session error handling (why is it optional in
|
||||
// ruma?)
|
||||
uiaainfo.session.as_ref().expect("session should be set"),
|
||||
json_body,
|
||||
)?;
|
||||
|
|
@ -64,7 +66,8 @@ impl Service {
|
|||
password,
|
||||
..
|
||||
}) => {
|
||||
let UserIdentifier::UserIdOrLocalpart(username) = identifier else {
|
||||
let UserIdentifier::UserIdOrLocalpart(username) = identifier
|
||||
else {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Unrecognized,
|
||||
"Identifier type not recognized.",
|
||||
|
|
@ -75,18 +78,26 @@ impl Service {
|
|||
username.clone(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "User ID is invalid."))?;
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User ID is invalid.",
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if password is correct
|
||||
if let Some(hash) = services().users.password_hash(&user_id)? {
|
||||
let hash_matches =
|
||||
argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false);
|
||||
argon2::verify_encoded(&hash, password.as_bytes())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !hash_matches {
|
||||
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "Invalid username or password.".to_owned(),
|
||||
});
|
||||
uiaainfo.auth_error =
|
||||
Some(ruma::api::client::error::StandardErrorBody {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "Invalid username or password."
|
||||
.to_owned(),
|
||||
});
|
||||
return Ok((false, uiaainfo));
|
||||
}
|
||||
}
|
||||
|
|
@ -95,13 +106,16 @@ impl Service {
|
|||
uiaainfo.completed.push(AuthType::Password);
|
||||
}
|
||||
AuthData::RegistrationToken(t) => {
|
||||
if Some(t.token.trim()) == services().globals.config.registration_token.as_deref() {
|
||||
if Some(t.token.trim())
|
||||
== services().globals.config.registration_token.as_deref()
|
||||
{
|
||||
uiaainfo.completed.push(AuthType::RegistrationToken);
|
||||
} else {
|
||||
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "Invalid registration token.".to_owned(),
|
||||
});
|
||||
uiaainfo.auth_error =
|
||||
Some(ruma::api::client::error::StandardErrorBody {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "Invalid registration token.".to_owned(),
|
||||
});
|
||||
return Ok((false, uiaainfo));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Result;
|
||||
use ruma::{api::client::uiaa::UiaaInfo, CanonicalJsonValue, DeviceId, UserId};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
fn set_uiaa_request(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ use ruma::{
|
|||
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
||||
events::AnyToDeviceEvent,
|
||||
serde::Raw,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedMxcUri,
|
||||
OwnedRoomId, OwnedUserId, RoomAliasId, UInt, UserId,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId,
|
||||
OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, UInt, UserId,
|
||||
};
|
||||
|
||||
use crate::{services, Error, Result};
|
||||
|
|
@ -36,8 +36,12 @@ pub(crate) struct SlidingSyncCache {
|
|||
pub(crate) struct Service {
|
||||
pub(crate) db: &'static dyn Data,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) connections:
|
||||
Mutex<BTreeMap<(OwnedUserId, OwnedDeviceId, String), Arc<Mutex<SlidingSyncCache>>>>,
|
||||
pub(crate) connections: Mutex<
|
||||
BTreeMap<
|
||||
(OwnedUserId, OwnedDeviceId, String),
|
||||
Arc<Mutex<SlidingSyncCache>>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
|
|
@ -52,10 +56,7 @@ impl Service {
|
|||
device_id: OwnedDeviceId,
|
||||
conn_id: String,
|
||||
) {
|
||||
self.connections
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&(user_id, device_id, conn_id));
|
||||
self.connections.lock().unwrap().remove(&(user_id, device_id, conn_id));
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -71,16 +72,14 @@ impl Service {
|
|||
|
||||
let mut cache = self.connections.lock().unwrap();
|
||||
let cached = Arc::clone(
|
||||
cache
|
||||
.entry((user_id, device_id, conn_id))
|
||||
.or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
cache.entry((user_id, device_id, conn_id)).or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
);
|
||||
let cached = &mut cached.lock().unwrap();
|
||||
drop(cache);
|
||||
|
|
@ -104,19 +103,22 @@ impl Service {
|
|||
.or(cached_list.include_old_rooms.clone());
|
||||
match (&mut list.filters, cached_list.filters.clone()) {
|
||||
(Some(list_filters), Some(cached_filters)) => {
|
||||
list_filters.is_dm = list_filters.is_dm.or(cached_filters.is_dm);
|
||||
list_filters.is_dm =
|
||||
list_filters.is_dm.or(cached_filters.is_dm);
|
||||
if list_filters.spaces.is_empty() {
|
||||
list_filters.spaces = cached_filters.spaces;
|
||||
}
|
||||
list_filters.is_encrypted =
|
||||
list_filters.is_encrypted.or(cached_filters.is_encrypted);
|
||||
list_filters.is_encrypted = list_filters
|
||||
.is_encrypted
|
||||
.or(cached_filters.is_encrypted);
|
||||
list_filters.is_invite =
|
||||
list_filters.is_invite.or(cached_filters.is_invite);
|
||||
if list_filters.room_types.is_empty() {
|
||||
list_filters.room_types = cached_filters.room_types;
|
||||
}
|
||||
if list_filters.not_room_types.is_empty() {
|
||||
list_filters.not_room_types = cached_filters.not_room_types;
|
||||
list_filters.not_room_types =
|
||||
cached_filters.not_room_types;
|
||||
}
|
||||
list_filters.room_name_like = list_filters
|
||||
.room_name_like
|
||||
|
|
@ -129,12 +131,17 @@ impl Service {
|
|||
list_filters.not_tags = cached_filters.not_tags;
|
||||
}
|
||||
}
|
||||
(_, Some(cached_filters)) => list.filters = Some(cached_filters),
|
||||
(Some(list_filters), _) => list.filters = Some(list_filters.clone()),
|
||||
(_, _) => {}
|
||||
(_, Some(cached_filters)) => {
|
||||
list.filters = Some(cached_filters);
|
||||
}
|
||||
(Some(list_filters), _) => {
|
||||
list.filters = Some(list_filters.clone());
|
||||
}
|
||||
(..) => {}
|
||||
}
|
||||
if list.bump_event_types.is_empty() {
|
||||
list.bump_event_types = cached_list.bump_event_types.clone();
|
||||
list.bump_event_types =
|
||||
cached_list.bump_event_types.clone();
|
||||
};
|
||||
}
|
||||
cached.lists.insert(list_id.clone(), list.clone());
|
||||
|
|
@ -147,17 +154,11 @@ impl Service {
|
|||
.map(|(k, v)| (k.clone(), v.clone())),
|
||||
);
|
||||
request.room_subscriptions.extend(
|
||||
cached
|
||||
.subscriptions
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone())),
|
||||
cached.subscriptions.iter().map(|(k, v)| (k.clone(), v.clone())),
|
||||
);
|
||||
|
||||
request.extensions.e2ee.enabled = request
|
||||
.extensions
|
||||
.e2ee
|
||||
.enabled
|
||||
.or(cached.extensions.e2ee.enabled);
|
||||
request.extensions.e2ee.enabled =
|
||||
request.extensions.e2ee.enabled.or(cached.extensions.e2ee.enabled);
|
||||
|
||||
request.extensions.to_device.enabled = request
|
||||
.extensions
|
||||
|
|
@ -197,16 +198,14 @@ impl Service {
|
|||
) {
|
||||
let mut cache = self.connections.lock().unwrap();
|
||||
let cached = Arc::clone(
|
||||
cache
|
||||
.entry((user_id, device_id, conn_id))
|
||||
.or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
cache.entry((user_id, device_id, conn_id)).or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
);
|
||||
let cached = &mut cached.lock().unwrap();
|
||||
drop(cache);
|
||||
|
|
@ -225,25 +224,20 @@ impl Service {
|
|||
) {
|
||||
let mut cache = self.connections.lock().unwrap();
|
||||
let cached = Arc::clone(
|
||||
cache
|
||||
.entry((user_id, device_id, conn_id))
|
||||
.or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
cache.entry((user_id, device_id, conn_id)).or_insert_with(|| {
|
||||
Arc::new(Mutex::new(SlidingSyncCache {
|
||||
lists: BTreeMap::new(),
|
||||
subscriptions: BTreeMap::new(),
|
||||
known_rooms: BTreeMap::new(),
|
||||
extensions: ExtensionsConfig::default(),
|
||||
}))
|
||||
}),
|
||||
);
|
||||
let cached = &mut cached.lock().unwrap();
|
||||
drop(cache);
|
||||
|
||||
for (roomid, lastsince) in cached
|
||||
.known_rooms
|
||||
.entry(list_id.clone())
|
||||
.or_default()
|
||||
.iter_mut()
|
||||
for (roomid, lastsince) in
|
||||
cached.known_rooms.entry(list_id.clone()).or_default().iter_mut()
|
||||
{
|
||||
if !new_cached_rooms.contains(roomid) {
|
||||
*lastsince = 0;
|
||||
|
|
@ -264,23 +258,28 @@ impl Service {
|
|||
// Allowed because this function uses `services()`
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(crate) fn is_admin(&self, user_id: &UserId) -> Result<bool> {
|
||||
let admin_room_alias_id =
|
||||
RoomAliasId::parse(format!("#admins:{}", services().globals.server_name()))
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid alias."))?;
|
||||
let admin_room_alias_id = RoomAliasId::parse(format!(
|
||||
"#admins:{}",
|
||||
services().globals.server_name()
|
||||
))
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Invalid alias.")
|
||||
})?;
|
||||
let admin_room_id = services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&admin_room_alias_id)?
|
||||
.unwrap();
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, &admin_room_id)
|
||||
services().rooms.state_cache.is_joined(user_id, &admin_room_id)
|
||||
}
|
||||
|
||||
/// Create a new user account on this homeserver.
|
||||
pub(crate) fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||
pub(crate) fn create(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
password: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.db.set_password(user_id, password)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -291,38 +290,55 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Find out which user an access token belongs to.
|
||||
pub(crate) fn find_from_token(&self, token: &str) -> Result<Option<(OwnedUserId, String)>> {
|
||||
pub(crate) fn find_from_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(OwnedUserId, String)>> {
|
||||
self.db.find_from_token(token)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all users on this homeserver.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = Result<OwnedUserId>> + '_ {
|
||||
pub(crate) fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<OwnedUserId>> + '_ {
|
||||
self.db.iter()
|
||||
}
|
||||
|
||||
/// Returns a list of local users as list of usernames.
|
||||
///
|
||||
/// A user account is considered `local` if the length of it's password is greater then zero.
|
||||
/// A user account is considered `local` if the length of it's password is
|
||||
/// greater then zero.
|
||||
pub(crate) fn list_local_users(&self) -> Result<Vec<String>> {
|
||||
self.db.list_local_users()
|
||||
}
|
||||
|
||||
/// Returns the password hash for the given user.
|
||||
pub(crate) fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
pub(crate) fn password_hash(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<String>> {
|
||||
self.db.password_hash(user_id)
|
||||
}
|
||||
|
||||
/// Hash and set the user's password to the Argon2 hash
|
||||
pub(crate) fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||
pub(crate) fn set_password(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
password: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.db.set_password(user_id, password)
|
||||
}
|
||||
|
||||
/// Returns the displayname of a user on this homeserver.
|
||||
pub(crate) fn displayname(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
pub(crate) fn displayname(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<String>> {
|
||||
self.db.displayname(user_id)
|
||||
}
|
||||
|
||||
/// Sets a new displayname or removes it if displayname is None. You still need to nofify all rooms of this change.
|
||||
/// Sets a new displayname or removes it if displayname is None. You still
|
||||
/// need to nofify all rooms of this change.
|
||||
pub(crate) fn set_displayname(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
|
|
@ -332,7 +348,10 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Get the `avatar_url` of a user.
|
||||
pub(crate) fn avatar_url(&self, user_id: &UserId) -> Result<Option<OwnedMxcUri>> {
|
||||
pub(crate) fn avatar_url(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<OwnedMxcUri>> {
|
||||
self.db.avatar_url(user_id)
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +370,11 @@ impl Service {
|
|||
}
|
||||
|
||||
/// Sets a new `avatar_url` or removes it if `avatar_url` is `None`.
|
||||
pub(crate) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
||||
pub(crate) fn set_blurhash(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
blurhash: Option<String>,
|
||||
) -> Result<()> {
|
||||
self.db.set_blurhash(user_id, blurhash)
|
||||
}
|
||||
|
||||
|
|
@ -363,12 +386,20 @@ impl Service {
|
|||
token: &str,
|
||||
initial_device_display_name: Option<String>,
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.create_device(user_id, device_id, token, initial_device_display_name)
|
||||
self.db.create_device(
|
||||
user_id,
|
||||
device_id,
|
||||
token,
|
||||
initial_device_display_name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes a device from a user.
|
||||
pub(crate) fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||
pub(crate) fn remove_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<()> {
|
||||
self.db.remove_device(user_id, device_id)
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +428,12 @@ impl Service {
|
|||
one_time_key_key: &DeviceKeyId,
|
||||
one_time_key_value: &Raw<OneTimeKey>,
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.add_one_time_key(user_id, device_id, one_time_key_key, one_time_key_value)
|
||||
self.db.add_one_time_key(
|
||||
user_id,
|
||||
device_id,
|
||||
one_time_key_key,
|
||||
one_time_key_value,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn take_one_time_key(
|
||||
|
|
@ -463,7 +498,10 @@ impl Service {
|
|||
self.db.keys_changed(user_or_room_id, from, to)
|
||||
}
|
||||
|
||||
pub(crate) fn mark_device_key_update(&self, user_id: &UserId) -> Result<()> {
|
||||
pub(crate) fn mark_device_key_update(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<()> {
|
||||
self.db.mark_device_key_update(user_id)
|
||||
}
|
||||
|
||||
|
|
@ -490,8 +528,7 @@ impl Service {
|
|||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.db
|
||||
.get_key(key, sender_user, user_id, allowed_signatures)
|
||||
self.db.get_key(key, sender_user, user_id, allowed_signatures)
|
||||
}
|
||||
|
||||
pub(crate) fn get_master_key(
|
||||
|
|
@ -500,8 +537,7 @@ impl Service {
|
|||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.db
|
||||
.get_master_key(sender_user, user_id, allowed_signatures)
|
||||
self.db.get_master_key(sender_user, user_id, allowed_signatures)
|
||||
}
|
||||
|
||||
pub(crate) fn get_self_signing_key(
|
||||
|
|
@ -510,8 +546,7 @@ impl Service {
|
|||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.db
|
||||
.get_self_signing_key(sender_user, user_id, allowed_signatures)
|
||||
self.db.get_self_signing_key(sender_user, user_id, allowed_signatures)
|
||||
}
|
||||
|
||||
pub(crate) fn get_user_signing_key(
|
||||
|
|
@ -573,7 +608,10 @@ impl Service {
|
|||
self.db.get_device_metadata(user_id, device_id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_devicelist_version(&self, user_id: &UserId) -> Result<Option<u64>> {
|
||||
pub(crate) fn get_devicelist_version(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<u64>> {
|
||||
self.db.get_devicelist_version(user_id)
|
||||
}
|
||||
|
||||
|
|
@ -591,9 +629,10 @@ impl Service {
|
|||
self.remove_device(user_id, &device_id?)?;
|
||||
}
|
||||
|
||||
// Set the password to "" to indicate a deactivated account. Hashes will never result in an
|
||||
// empty string, so the user will not be able to log in again. Systems like changing the
|
||||
// password without logging in should check if the account is deactivated.
|
||||
// Set the password to "" to indicate a deactivated account. Hashes will
|
||||
// never result in an empty string, so the user will not be able
|
||||
// to log in again. Systems like changing the password without
|
||||
// logging in should check if the account is deactivated.
|
||||
self.db.set_password(user_id, None)?;
|
||||
|
||||
// TODO: Unhook 3PID
|
||||
|
|
@ -625,19 +664,23 @@ pub(crate) fn clean_signatures<F: Fn(&UserId) -> bool>(
|
|||
user_id: &UserId,
|
||||
allowed_signatures: F,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(signatures) = cross_signing_key
|
||||
.get_mut("signatures")
|
||||
.and_then(|v| v.as_object_mut())
|
||||
if let Some(signatures) =
|
||||
cross_signing_key.get_mut("signatures").and_then(|v| v.as_object_mut())
|
||||
{
|
||||
// Don't allocate for the full size of the current signatures, but require
|
||||
// at most one resize if nothing is dropped
|
||||
// Don't allocate for the full size of the current signatures, but
|
||||
// require at most one resize if nothing is dropped
|
||||
let new_capacity = signatures.len() / 2;
|
||||
for (user, signature) in
|
||||
mem::replace(signatures, serde_json::Map::with_capacity(new_capacity))
|
||||
{
|
||||
let sid = <&UserId>::try_from(user.as_str())
|
||||
.map_err(|_| Error::bad_database("Invalid user ID in database."))?;
|
||||
if sender_user == Some(user_id) || sid == user_id || allowed_signatures(sid) {
|
||||
for (user, signature) in mem::replace(
|
||||
signatures,
|
||||
serde_json::Map::with_capacity(new_capacity),
|
||||
) {
|
||||
let sid = <&UserId>::try_from(user.as_str()).map_err(|_| {
|
||||
Error::bad_database("Invalid user ID in database.")
|
||||
})?;
|
||||
if sender_user == Some(user_id)
|
||||
|| sid == user_id
|
||||
|| allowed_signatures(sid)
|
||||
{
|
||||
signatures.insert(user, signature);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
use crate::Result;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
api::client::{device::Device, filter::FilterDefinition},
|
||||
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
||||
events::AnyToDeviceEvent,
|
||||
serde::Raw,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedMxcUri,
|
||||
OwnedUserId, UInt, UserId,
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId,
|
||||
OwnedMxcUri, OwnedUserId, UInt, UserId,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) trait Data: Send + Sync {
|
||||
/// Check if a user has an account on this homeserver.
|
||||
|
|
@ -20,39 +22,61 @@ pub(crate) trait Data: Send + Sync {
|
|||
fn count(&self) -> Result<usize>;
|
||||
|
||||
/// Find out which user an access token belongs to.
|
||||
fn find_from_token(&self, token: &str) -> Result<Option<(OwnedUserId, String)>>;
|
||||
fn find_from_token(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<Option<(OwnedUserId, String)>>;
|
||||
|
||||
/// Returns an iterator over all users on this homeserver.
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Result<OwnedUserId>> + 'a>;
|
||||
fn iter<'a>(&'a self)
|
||||
-> Box<dyn Iterator<Item = Result<OwnedUserId>> + 'a>;
|
||||
|
||||
/// Returns a list of local users as list of usernames.
|
||||
///
|
||||
/// A user account is considered `local` if the length of it's password is greater then zero.
|
||||
/// A user account is considered `local` if the length of it's password is
|
||||
/// greater then zero.
|
||||
fn list_local_users(&self) -> Result<Vec<String>>;
|
||||
|
||||
/// Returns the password hash for the given user.
|
||||
fn password_hash(&self, user_id: &UserId) -> Result<Option<String>>;
|
||||
|
||||
/// Hash and set the user's password to the Argon2 hash
|
||||
fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()>;
|
||||
fn set_password(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
password: Option<&str>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns the displayname of a user on this homeserver.
|
||||
fn displayname(&self, user_id: &UserId) -> Result<Option<String>>;
|
||||
|
||||
/// Sets a new `displayname` or removes it if `displayname` is `None`. You still need to nofify all rooms of this change.
|
||||
fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()>;
|
||||
/// Sets a new `displayname` or removes it if `displayname` is `None`. You
|
||||
/// still need to nofify all rooms of this change.
|
||||
fn set_displayname(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
displayname: Option<String>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Get the `avatar_url` of a user.
|
||||
fn avatar_url(&self, user_id: &UserId) -> Result<Option<OwnedMxcUri>>;
|
||||
|
||||
/// Sets a new `avatar_url` or removes it if `avatar_url` is `None`.
|
||||
fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<OwnedMxcUri>) -> Result<()>;
|
||||
fn set_avatar_url(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
avatar_url: Option<OwnedMxcUri>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Get the `blurhash` of a user.
|
||||
fn blurhash(&self, user_id: &UserId) -> Result<Option<String>>;
|
||||
|
||||
/// Sets a new `avatar_url` or removes it if `avatar_url` is `None`.
|
||||
fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()>;
|
||||
fn set_blurhash(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
blurhash: Option<String>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Adds a new device to a user.
|
||||
fn create_device(
|
||||
|
|
@ -64,7 +88,11 @@ pub(crate) trait Data: Send + Sync {
|
|||
) -> Result<()>;
|
||||
|
||||
/// Removes a device from a user.
|
||||
fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>;
|
||||
fn remove_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Returns an iterator over all device ids of this user.
|
||||
fn all_device_ids<'a>(
|
||||
|
|
@ -73,7 +101,12 @@ pub(crate) trait Data: Send + Sync {
|
|||
) -> Box<dyn Iterator<Item = Result<OwnedDeviceId>> + 'a>;
|
||||
|
||||
/// Replaces the access token of one device.
|
||||
fn set_token(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()>;
|
||||
fn set_token(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
token: &str,
|
||||
) -> Result<()>;
|
||||
|
||||
fn add_one_time_key(
|
||||
&self,
|
||||
|
|
@ -165,7 +198,10 @@ pub(crate) trait Data: Send + Sync {
|
|||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>>;
|
||||
|
||||
fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<Raw<CrossSigningKey>>>;
|
||||
fn get_user_signing_key(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>>;
|
||||
|
||||
fn add_to_device_event(
|
||||
&self,
|
||||
|
|
@ -197,8 +233,11 @@ pub(crate) trait Data: Send + Sync {
|
|||
) -> Result<()>;
|
||||
|
||||
/// Get device metadata.
|
||||
fn get_device_metadata(&self, user_id: &UserId, device_id: &DeviceId)
|
||||
-> Result<Option<Device>>;
|
||||
fn get_device_metadata(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Result<Option<Device>>;
|
||||
|
||||
fn get_devicelist_version(&self, user_id: &UserId) -> Result<Option<u64>>;
|
||||
|
||||
|
|
@ -208,7 +247,15 @@ pub(crate) trait Data: Send + Sync {
|
|||
) -> Box<dyn Iterator<Item = Result<Device>> + 'a>;
|
||||
|
||||
/// Creates a new sync filter. Returns the filter id.
|
||||
fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> Result<String>;
|
||||
fn create_filter(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
filter: &FilterDefinition,
|
||||
) -> Result<String>;
|
||||
|
||||
fn get_filter(&self, user_id: &UserId, filter_id: &str) -> Result<Option<FilterDefinition>>;
|
||||
fn get_filter(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
filter_id: &str,
|
||||
) -> Result<Option<FilterDefinition>>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue