0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-10 05:58:56 +02:00

ircd:Ⓜ️:room: Reorient join bootstrap definitions.

ircd:Ⓜ️:room::bootstrap: Improve interface / various.
This commit is contained in:
Jason Volk 2019-07-23 15:37:32 -07:00
parent 32e77c64a8
commit df11d9bec6
7 changed files with 785 additions and 702 deletions

View file

@ -0,0 +1,24 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#pragma once
#define HAVE_IRCD_M_ROOM_BOOTSTRAP_H
struct ircd::m::room::bootstrap
{
// restrap: synchronous; send_join
bootstrap(const event &, const string_view &host);
// restrap: asynchronous; launch ctx; send_join
bootstrap(const event::id &, const string_view &host);
// synchronous make_join, eval; asynchronous send_join
bootstrap(event::id::buf &, const room::id &, const m::id::user &, const string_view &host);
};

View file

@ -104,6 +104,7 @@ struct ircd::m::room
struct aliases;
struct stats;
struct server_acl;
struct bootstrap;
using id = m::id::room;
using alias = m::id::room_alias;
@ -178,10 +179,6 @@ struct ircd::m::room
static event::idx index(const id &, std::nothrow_t);
static event::idx index(const id &);
static void bootstrap(const event &, const string_view &host);
static void bootstrap(const event::id &, const string_view &host);
static event::id::buf bootstrap(const id &, const id::user &, const string_view &host);
static size_t purge(const room &); // cuidado!
};
@ -198,6 +195,7 @@ struct ircd::m::room
#include "aliases.h"
#include "stats.h"
#include "server_acl.h"
#include "bootstrap.h"
inline ircd::m::room::operator
const ircd::m::room::id &()

View file

@ -220,6 +220,7 @@ ircd::m::module_names
"m_room_message",
"m_room_power_levels",
"m_room_server_acl",
"m_room_bootstrap",
"m_presence",
"m_receipt",
"m_typing",

View file

@ -119,6 +119,7 @@ m_room_aliases_la_SOURCES = m_room_aliases.cc
m_room_message_la_SOURCES = m_room_message.cc
m_room_power_levels_la_SOURCES = m_room_power_levels.cc
m_room_server_acl_la_SOURCES = m_room_server_acl.cc
m_room_bootstrap_la_SOURCES = m_room_bootstrap.cc
m_init_bootstrap_la_SOURCES = m_init_bootstrap.cc
m_listen_la_SOURCES = m_listen.cc
@ -157,6 +158,7 @@ m_module_LTLIBRARIES = \
m_room_message.la \
m_room_power_levels.la \
m_room_server_acl.la \
m_room_bootstrap.la \
m_init_bootstrap.la \
m_listen.la \
###

View file

@ -13,12 +13,6 @@
using namespace ircd::m;
using namespace ircd;
ircd::log::log
join_log
{
"matrix.join"
};
resource::response
post__join(client &client,
const resource::request &request,
@ -57,8 +51,13 @@ ircd::m::join(const room &room,
{
if(!exists(room))
{
const auto &room_id(room.room_id);
return m::room::bootstrap(room_id, user_id, room_id.host()); //TODO: host
m::event::id::buf ret;
m::room::bootstrap
{
ret, room.room_id, user_id, room.room_id.host() //TODO: host
};
return ret;
}
json::iov event;
@ -122,7 +121,15 @@ ircd::m::join(const m::room::alias &room_alias,
};
if(!exists(room_id))
return m::room::bootstrap(room_id, user_id, room_alias.host());
{
m::event::id::buf ret;
m::room::bootstrap
{
ret, room_id, user_id, room_alias.host()
};
return ret;
}
const m::room room
{
@ -131,691 +138,3 @@ ircd::m::join(const m::room::alias &room_alias,
return m::join(room, user_id);
}
//
// bootstrap
//
static event::id::buf
bootstrap_make_join(const string_view &host,
const m::room::id &,
const m::user::id &);
static std::tuple<json::object, unique_buffer<mutable_buffer>>
bootstrap_send_join(const string_view &host,
const m::room::id &,
const m::event::id &,
const json::object &event);
static void
bootstrap_fetch_keys(const json::array &events);
static void
bootstrap_eval_lazy_chain(const json::array &auth_chain);
static void
bootstrap_eval_auth_chain(const json::array &auth_chain);
static void
bootstrap_eval_state(const json::array &state);
static void
bootstrap_backfill(const string_view &host,
const m::room::id &,
const m::event::id &);
conf::item<seconds>
make_join_timeout
{
{ "name", "ircd.client.rooms.join.make_join.timeout" },
{ "default", 15L },
};
conf::item<seconds>
send_join_timeout
{
{ "name", "ircd.client.rooms.join.send_join.timeout" },
{ "default", 90L /* spinappse */ },
};
conf::item<seconds>
backfill_timeout
{
{ "name", "ircd.client.rooms.join.backfill.timeout" },
{ "default", 15L },
};
conf::item<size_t>
backfill_limit
{
{ "name", "ircd.client.rooms.join.backfill.limit" },
{ "default", 64L },
{ "description",
R"(
The number of events to request on initial backfill. Specapse may limit
this to 50, but it also may not. Either way, a good choice is enough to
fill a client's timeline quickly with a little headroom.
)"}
};
conf::item<bool>
backfill_first
{
{ "name", "ircd.client.rooms.join.backfill.first" },
{ "default", true },
{ "description",
R"(
During the room join bootstrap process, this controls whether backfilling
recent timeline events occurs before processing the room state. If true,
user experience may be improved because their client's timeline is
immediately populated with recent messages. Otherwise, the backfill will be
delayed until after all state events have been processed first. Setting
this to false is safer, as some clients may be confused by timeline events
which are missing related state events. Note that fundamental state events
for the room are still processed first regardless of this setting. Also
known as the Hackfill optimization.
)"}
};
conf::item<bool>
lazychain_enable
{
{ "name", "ircd.client.rooms.join.lazychain.enable" },
{ "default", false },
{ "description",
R"(
During the room join bootstrap process, this controls whether the
auth_chain in the response is only selectively processed. This is a
safe optimization that allows the bootstrap to progress to the next
phase. The skipped events are eventually processed during the state
evaluation phase.
)"}
};
event::id::buf
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap(const m::room::id &room_id,
const m::user::id &user_id,
const string_view &host)
{
log::debug
{
join_log, "join bootstrap starting in %s for %s to '%s'",
string_view{room_id},
string_view{user_id},
host
};
const auto event_id
{
bootstrap_make_join(host, room_id, user_id)
};
assert(event_id);
bootstrap(event_id, host); // asynchronous; returns quickly
return event_id;
}
void
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap(const m::event::id &event_id,
const string_view &host)
try
{
const m::event::fetch event{event_id};
assert(event.source);
context
{
"roomjoin", 128_KiB,
context::POST | context::DETACH,
[host(std::string(host)), event(std::string(event.source))]
{
bootstrap(m::event{event}, host);
}
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "join bootstrap for %s to %s :%s",
string_view{event_id},
string(host),
e.what()
};
}
void
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap(const m::event &event,
const string_view &host)
try
{
const m::event::id &event_id
{
event.event_id
};
const m::room::id &room_id
{
at<"room_id"_>(event)
};
const m::user::id &user_id
{
at<"sender"_>(event)
};
const m::room room
{
room_id, event_id
};
log::info
{
join_log, "join bootstrap sending in %s for %s at %s to '%s'",
string_view{room_id},
string_view{user_id},
string_view{event_id},
host
};
assert(event.source);
const auto &[response, buf]
{
bootstrap_send_join(host, room_id, event_id, event.source)
};
const json::array &auth_chain
{
response["auth_chain"]
};
const json::array &state
{
response["state"]
};
log::info
{
join_log, "join bootstrap joined to %s for %s at %s to '%s' state:%zu auth_chain:%zu",
string_view{room_id},
string_view{user_id},
string_view{event_id},
host,
state.size(),
auth_chain.size(),
};
if(lazychain_enable)
bootstrap_eval_lazy_chain(auth_chain);
else
bootstrap_eval_auth_chain(auth_chain);
if(backfill_first)
{
bootstrap_backfill(host, room_id, event_id);
bootstrap_eval_state(state);
} else {
bootstrap_eval_state(state);
bootstrap_backfill(host, room_id, event_id);
}
// After we just received and processed all of this state with only a
// recent backfill our system doesn't know if state events which are
// unreferenced are simply referenced by events we just don't have. They
// will all be added to the room::head and each future event we transmit
// to the room will drain that list little by little. But the cost of all
// these references is too high. We take the easy route here and simply
// clear the head of every event except our own join event.
const size_t num_reset
{
m::room::head::reset(room)
};
log::info
{
join_log, "join bootstrap joined to %s for %s at %s reset:%zu complete",
string_view{room_id},
string_view{user_id},
string_view{event_id},
num_reset,
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "join bootstrap for %s to %s :%s",
string_view{event.event_id},
string(host),
e.what()
};
}
void
bootstrap_backfill(const string_view &host,
const m::room::id &room_id,
const m::event::id &event_id)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::backfill::opts opts{host};
opts.dynamic = true;
opts.event_id = event_id;
opts.limit = size_t(backfill_limit);
m::v1::backfill request
{
room_id, buf, std::move(opts)
};
request.wait(seconds(backfill_timeout));
const auto code
{
request.get()
};
const json::object &response
{
request.in.content
};
const json::array &pdus
{
response["pdus"]
};
log::info
{
join_log, "join bootstrap processing backfill for %s from %s at %s events:%zu",
string_view{room_id},
host,
string_view{event_id},
pdus.size(),
};
m::vm::opts vmopts;
vmopts.nothrows = -1;
vmopts.fetch_state_check = false;
vmopts.fetch_prev_check = false;
vmopts.infolog_accept = false;
m::vm::eval
{
pdus, vmopts
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "join bootstrap %s backfill @ %s from %s :%s",
string_view{room_id},
string_view{event_id},
string(host),
e.what(),
};
}
void
bootstrap_eval_state(const json::array &state)
try
{
m::vm::opts opts;
opts.nothrows = -1;
opts.fetch_prev_check = false;
opts.fetch_state_check = false;
opts.infolog_accept = false;
m::vm::eval
{
state, opts
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "join bootstrap eval state :%s", e.what(),
};
}
void
bootstrap_eval_auth_chain(const json::array &auth_chain)
try
{
bootstrap_fetch_keys(auth_chain);
m::vm::opts opts;
opts.infolog_accept = true;
opts.fetch = false;
m::vm::eval
{
auth_chain, opts
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "join bootstrap eval auth_chain :%s", e.what(),
};
throw;
}
void
bootstrap_eval_lazy_chain(const json::array &auth_chain)
{
m::vm::opts opts;
opts.infolog_accept = true;
opts.fetch = false;
// Parse and sort the auth_chain first so we don't have to keep scanning
// the JSON to do the various operations that follow.
std::vector<m::event> events(begin(auth_chain), end(auth_chain));
std::sort(begin(events), end(events));
// When we selectively evaluate the auth_chain below we may need to feed
// the vm certain member events first to avoid complications; this
// subroutine will find them.
const auto find_member{[&events]
(const m::user::id &user_id, const int64_t &depth)
{
const auto it(std::find_if(rbegin(events), rend(events), [&user_id, &depth]
(const m::event &event)
{
return json::get<"depth"_>(event) < depth &&
json::get<"type"_>(event) == "m.room.member" &&
json::get<"state_key"_>(event) == user_id;
}));
if(unlikely(it == rend(events)))
throw m::NOT_FOUND
{
"No m.room.member event for %s found in auth chain.",
string_view{user_id}
};
return *it;
}};
for(const auto &event : events)
{
// Skip all events which aren't power events. We don't need them
// here yet. They can wait until state evaluation later.
if(!m::event::auth::is_power_event(event))
continue;
// Find the member event for the sender of this power event so the
// system is aware of their identity first; this isn't done for the
// create event because the vm expects that first regardless.
if(json::get<"type"_>(event) != "m.room.create")
{
const auto &member_event
{
find_member(at<"sender"_>(event), at<"depth"_>(event))
};
m::vm::eval
{
member_event, opts
};
}
m::vm::eval
{
event, opts
};
}
}
void
bootstrap_fetch_keys(const json::array &events)
try
{
std::vector<m::v1::key::server_key> queries;
queries.reserve(events.size());
for(const json::object &event : events)
for(const auto &[server_name, signatures] : json::object(event["signatures"]))
for(const auto &[key_id, signature] : json::object(signatures))
queries.emplace_back(unquote(event.at("origin")), key_id);
std::sort(begin(queries), end(queries));
queries.erase(std::unique(begin(queries), end(queries)), end(queries));
log::info
{
join_log, "Fetching %zu keys for %zu events...",
queries.size(),
events.size(),
};
const size_t fetched
{
m::keys::fetch(queries)
};
log::info
{
join_log, "Fetched %zu of %zu keys for %zu events",
fetched,
queries.size(),
events.size(),
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "Error when fetching keys for %zu events :%s",
events.size(),
};
}
std::tuple<json::object, unique_buffer<mutable_buffer>>
bootstrap_send_join(const string_view &host,
const m::room::id &room_id,
const m::event::id &event_id,
const json::object &event)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::send_join::opts opts{host};
opts.dynamic = true;
m::v1::send_join send_join
{
room_id, event_id, event, buf, std::move(opts)
};
send_join.wait(seconds(send_join_timeout));
const auto send_join_code
{
send_join.get()
};
const json::array &send_join_response
{
send_join
};
const uint more_send_join_code
{
send_join_response.at<uint>(0)
};
const json::object &send_join_response_data
{
send_join_response[1]
};
assert(!!send_join.in.dynamic);
return
{
send_join_response_data,
std::move(send_join.in.dynamic)
};
}
catch(const std::exception &e)
{
log::error
{
join_log, "Bootstrap %s @ %s send_join to %s :%s",
string_view{room_id},
string_view{event_id},
string(host),
e.what(),
};
throw;
}
event::id::buf
bootstrap_make_join(const string_view &host,
const m::room::id &room_id,
const m::user::id &user_id)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::make_join::opts opts{host};
m::v1::make_join request
{
room_id, user_id, buf, std::move(opts)
};
request.wait(seconds(make_join_timeout));
const auto code
{
request.get()
};
const json::object &response
{
request.in.content
};
const json::string &room_version
{
response.get("room_version", "1")
};
const json::object &proto
{
response.at("event")
};
const json::array &auth_events
{
proto.get("auth_events")
};
const json::array &prev_events
{
proto.get("prev_events")
};
json::iov event;
json::iov content;
const json::iov::push push[]
{
{ event, { "type", "m.room.member" }},
{ event, { "sender", user_id }},
{ event, { "state_key", user_id }},
{ content, { "membership", "join" }},
{ event, { "prev_events", prev_events }},
{ event, { "auth_events", auth_events }},
{ event, { "prev_state", "[]" }},
{ event, { "depth", proto.get<long>("depth") }},
{ event, { "room_id", room_id }},
};
const m::user user{user_id};
const m::user::profile profile{user};
char displayname_buf[256];
const string_view displayname
{
profile.get(displayname_buf, "displayname")
};
char avatar_url_buf[256];
const string_view avatar_url
{
profile.get(avatar_url_buf, "avatar_url")
};
const json::iov::add _displayname
{
content, !empty(displayname),
{
"displayname", [&displayname]() -> json::value
{
return displayname;
}
}
};
const json::iov::add _avatar_url
{
content, !empty(avatar_url),
{
"avatar_url", [&avatar_url]() -> json::value
{
return avatar_url;
}
}
};
m::vm::copts vmopts;
vmopts.infolog_accept = true;
vmopts.fetch = false;
vmopts.eval = false;
vmopts.user_id = user_id;
vmopts.room_version = room_version;
vm::eval eval
{
event, content, vmopts
};
const m::event::id::buf &ret
{
eval
};
if(unlikely(!ret))
throw m::UNAVAILABLE
{
"Unknown error"
};
return ret;
}
catch(const std::exception &e)
{
log::error
{
join_log, "Bootstrap %s for %s make_join to %s :%s",
string_view{room_id},
string_view{user_id},
string(host),
e.what(),
};
throw;
}

View file

@ -9881,7 +9881,11 @@ console_cmd__room__restrap(opt &out, const string_view &line)
param.at("host")
};
m::room::bootstrap(event_id, host);
m::room::bootstrap
{
event_id, host
};
return true;
}

735
modules/m_room_bootstrap.cc Normal file
View file

@ -0,0 +1,735 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
namespace ircd::m::bootstrap
{
using send_join1_response = std::tuple<json::object, unique_buffer<mutable_buffer>>;
static event::id::buf make_join(const string_view &host, const room::id &, const user::id &);
static send_join1_response send_join(const string_view &host, const room::id &, const event::id &, const json::object &event);
static void fetch_keys(const json::array &events);
static void eval_lazy_chain(const json::array &auth_chain);
static void eval_auth_chain(const json::array &auth_chain);
static void eval_state(const json::array &state);
static void backfill(const string_view &host, const room::id &, const event::id &);
extern conf::item<seconds> make_join_timeout;
extern conf::item<seconds> send_join_timeout;
extern conf::item<seconds> backfill_timeout;
extern conf::item<size_t> backfill_limit;
extern conf::item<bool> backfill_first;
extern conf::item<bool> lazychain_enable;
extern log::log log;
}
ircd::mapi::header
IRCD_MODULE
{
"Matrix room bootstrap."
};
decltype(ircd::m::bootstrap::log)
ircd::m::bootstrap::log
{
"m.bootstrap"
};
decltype(ircd::m::bootstrap::lazychain_enable)
ircd::m::bootstrap::lazychain_enable
{
{ "name", "ircd.client.rooms.join.lazychain.enable" },
{ "default", false },
{ "description",
R"(
During the room join bootstrap process, this controls whether the
auth_chain in the response is only selectively processed. This is a
safe optimization that allows the bootstrap to progress to the next
phase. The skipped events are eventually processed during the state
evaluation phase.
)"}
};
decltype(ircd::m::bootstrap::backfill_first)
ircd::m::bootstrap::backfill_first
{
{ "name", "ircd.client.rooms.join.backfill.first" },
{ "default", true },
{ "description",
R"(
During the room join bootstrap process, this controls whether backfilling
recent timeline events occurs before processing the room state. If true,
user experience may be improved because their client's timeline is
immediately populated with recent messages. Otherwise, the backfill will be
delayed until after all state events have been processed first. Setting
this to false is safer, as some clients may be confused by timeline events
which are missing related state events. Note that fundamental state events
for the room are still processed first regardless of this setting. Also
known as the Hackfill optimization.
)"}
};
decltype(ircd::m::bootstrap::backfill_limit)
ircd::m::bootstrap::backfill_limit
{
{ "name", "ircd.client.rooms.join.backfill.limit" },
{ "default", 64L },
{ "description",
R"(
The number of events to request on initial backfill. Specapse may limit
this to 50, but it also may not. Either way, a good choice is enough to
fill a client's timeline quickly with a little headroom.
)"}
};
decltype(ircd::m::bootstrap::backfill_timeout)
ircd::m::bootstrap::backfill_timeout
{
{ "name", "ircd.client.rooms.join.backfill.timeout" },
{ "default", 15L },
};
decltype(ircd::m::bootstrap::send_join_timeout)
ircd::m::bootstrap::send_join_timeout
{
{ "name", "ircd.client.rooms.join.send_join.timeout" },
{ "default", 90L /* spinappse */ },
};
decltype(ircd::m::bootstrap::make_join_timeout)
ircd::m::bootstrap::make_join_timeout
{
{ "name", "ircd.client.rooms.join.make_join.timeout" },
{ "default", 15L },
};
//
// m::room::bootstrap
//
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap::bootstrap(m::event::id::buf &event_id_buf,
const m::room::id &room_id,
const m::user::id &user_id,
const string_view &host)
{
log::info
{
log, "Starting in %s for %s to '%s'",
string_view{room_id},
string_view{user_id},
host
};
// synchronous; yields ctx.
event_id_buf = m::bootstrap::make_join(host, room_id, user_id);
assert(event_id_buf);
// asynchronous; returns quickly
room::bootstrap
{
event_id_buf, host
};
}
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap::bootstrap(const m::event::id &event_id,
const string_view &host)
try
{
struct pkg
{
std::string event;
std::string event_id;
std::string host;
};
static const context::flags flags
{
context::POST | context::DETACH
};
static const auto stack_sz
{
128_KiB
};
const m::event::fetch event
{
event_id
};
assert(event.valid);
assert(event.source);
context
{
"bootstrap",
stack_sz,
flags,
[p(pkg{std::string(event.source), event.event_id, host})]
{
assert(!empty(p.event));
assert(!empty(p.event_id));
const m::event event
{
p.event, p.event_id
};
assert(!empty(p.host));
room::bootstrap
{
event, p.host
};
}
};
}
catch(const std::exception &e)
{
log::error
{
log, "Failed to bootstrap for %s to %s :%s",
string_view{event_id},
host,
e.what(),
};
}
IRCD_MODULE_EXPORT
ircd::m::room::bootstrap::bootstrap(const m::event &event,
const string_view &host)
try
{
const m::event::id &event_id
{
event.event_id
};
const m::room::id &room_id
{
at<"room_id"_>(event)
};
const m::user::id &user_id
{
at<"sender"_>(event)
};
const m::room room
{
room_id, event_id
};
log::info
{
log, "join bootstrap sending in %s for %s at %s to '%s'",
string_view{room_id},
string_view{user_id},
string_view{event_id},
host
};
assert(event.source);
const auto &[response, buf]
{
m::bootstrap::send_join(host, room_id, event_id, event.source)
};
const json::array &auth_chain
{
response["auth_chain"]
};
const json::array &state
{
response["state"]
};
log::info
{
log, "join bootstrap joined to %s for %s at %s to '%s' state:%zu auth_chain:%zu",
string_view{room_id},
string_view{user_id},
string_view{event_id},
host,
state.size(),
auth_chain.size(),
};
if(m::bootstrap::lazychain_enable)
m::bootstrap::eval_lazy_chain(auth_chain);
else
m::bootstrap::eval_auth_chain(auth_chain);
if(m::bootstrap::backfill_first)
{
m::bootstrap::backfill(host, room_id, event_id);
m::bootstrap::eval_state(state);
} else {
m::bootstrap::eval_state(state);
m::bootstrap::backfill(host, room_id, event_id);
}
// After we just received and processed all of this state with only a
// recent backfill our system doesn't know if state events which are
// unreferenced are simply referenced by events we just don't have. They
// will all be added to the room::head and each future event we transmit
// to the room will drain that list little by little. But the cost of all
// these references is too high. We take the easy route here and simply
// clear the head of every event except our own join event.
const size_t num_reset
{
m::room::head::reset(room)
};
log::info
{
log, "join bootstrap joined to %s for %s at %s reset:%zu complete",
string_view{room_id},
string_view{user_id},
string_view{event_id},
num_reset,
};
}
catch(const std::exception &e)
{
log::error
{
log, "join bootstrap for %s to %s :%s",
string_view{event.event_id},
string(host),
e.what()
};
}
void
ircd::m::bootstrap::backfill(const string_view &host,
const m::room::id &room_id,
const m::event::id &event_id)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::backfill::opts opts{host};
opts.dynamic = true;
opts.event_id = event_id;
opts.limit = size_t(backfill_limit);
m::v1::backfill request
{
room_id, buf, std::move(opts)
};
request.wait(seconds(backfill_timeout));
const auto code
{
request.get()
};
const json::object &response
{
request.in.content
};
const json::array &pdus
{
response["pdus"]
};
log::info
{
log, "join bootstrap processing backfill for %s from %s at %s events:%zu",
string_view{room_id},
host,
string_view{event_id},
pdus.size(),
};
m::vm::opts vmopts;
vmopts.nothrows = -1;
vmopts.fetch_state_check = false;
vmopts.fetch_prev_check = false;
vmopts.infolog_accept = false;
m::vm::eval
{
pdus, vmopts
};
}
catch(const std::exception &e)
{
log::error
{
log, "join bootstrap %s backfill @ %s from %s :%s",
string_view{room_id},
string_view{event_id},
string(host),
e.what(),
};
}
void
ircd::m::bootstrap::eval_state(const json::array &state)
try
{
m::vm::opts opts;
opts.nothrows = -1;
opts.fetch_prev_check = false;
opts.fetch_state_check = false;
opts.infolog_accept = false;
m::vm::eval
{
state, opts
};
}
catch(const std::exception &e)
{
log::error
{
log, "join bootstrap eval state :%s", e.what(),
};
}
void
ircd::m::bootstrap::eval_auth_chain(const json::array &auth_chain)
try
{
fetch_keys(auth_chain);
m::vm::opts opts;
opts.infolog_accept = true;
opts.fetch = false;
m::vm::eval
{
auth_chain, opts
};
}
catch(const std::exception &e)
{
log::error
{
log, "join bootstrap eval auth_chain :%s", e.what(),
};
throw;
}
void
ircd::m::bootstrap::eval_lazy_chain(const json::array &auth_chain)
{
m::vm::opts opts;
opts.infolog_accept = true;
opts.fetch = false;
// Parse and sort the auth_chain first so we don't have to keep scanning
// the JSON to do the various operations that follow.
std::vector<m::event> events(begin(auth_chain), end(auth_chain));
std::sort(begin(events), end(events));
// When we selectively evaluate the auth_chain below we may need to feed
// the vm certain member events first to avoid complications; this
// subroutine will find them.
const auto find_member{[&events]
(const m::user::id &user_id, const int64_t &depth)
{
const auto it(std::find_if(rbegin(events), rend(events), [&user_id, &depth]
(const m::event &event)
{
return json::get<"depth"_>(event) < depth &&
json::get<"type"_>(event) == "m.room.member" &&
json::get<"state_key"_>(event) == user_id;
}));
if(unlikely(it == rend(events)))
throw m::NOT_FOUND
{
"No m.room.member event for %s found in auth chain.",
string_view{user_id}
};
return *it;
}};
for(const auto &event : events)
{
// Skip all events which aren't power events. We don't need them
// here yet. They can wait until state evaluation later.
if(!m::event::auth::is_power_event(event))
continue;
// Find the member event for the sender of this power event so the
// system is aware of their identity first; this isn't done for the
// create event because the vm expects that first regardless.
if(json::get<"type"_>(event) != "m.room.create")
{
const auto &member_event
{
find_member(at<"sender"_>(event), at<"depth"_>(event))
};
m::vm::eval
{
member_event, opts
};
}
m::vm::eval
{
event, opts
};
}
}
void
ircd::m::bootstrap::fetch_keys(const json::array &events)
try
{
std::vector<m::v1::key::server_key> queries;
queries.reserve(events.size());
for(const json::object &event : events)
for(const auto &[server_name, signatures] : json::object(event["signatures"]))
for(const auto &[key_id, signature] : json::object(signatures))
queries.emplace_back(unquote(event.at("origin")), key_id);
std::sort(begin(queries), end(queries));
queries.erase(std::unique(begin(queries), end(queries)), end(queries));
log::info
{
log, "Fetching %zu keys for %zu events...",
queries.size(),
events.size(),
};
const size_t fetched
{
m::keys::fetch(queries)
};
log::info
{
log, "Fetched %zu of %zu keys for %zu events",
fetched,
queries.size(),
events.size(),
};
}
catch(const std::exception &e)
{
log::error
{
log, "Error when fetching keys for %zu events :%s",
events.size(),
};
}
ircd::m::bootstrap::send_join1_response
ircd::m::bootstrap::send_join(const string_view &host,
const m::room::id &room_id,
const m::event::id &event_id,
const json::object &event)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::send_join::opts opts{host};
opts.dynamic = true;
m::v1::send_join send_join
{
room_id, event_id, event, buf, std::move(opts)
};
send_join.wait(seconds(send_join_timeout));
const auto send_join_code
{
send_join.get()
};
const json::array &send_join_response
{
send_join
};
const uint more_send_join_code
{
send_join_response.at<uint>(0)
};
const json::object &send_join_response_data
{
send_join_response[1]
};
assert(!!send_join.in.dynamic);
return
{
send_join_response_data,
std::move(send_join.in.dynamic)
};
}
catch(const std::exception &e)
{
log::error
{
log, "Bootstrap %s @ %s send_join to %s :%s",
string_view{room_id},
string_view{event_id},
string(host),
e.what(),
};
throw;
}
ircd::m::event::id::buf
ircd::m::bootstrap::make_join(const string_view &host,
const m::room::id &room_id,
const m::user::id &user_id)
try
{
const unique_buffer<mutable_buffer> buf
{
16_KiB // headers in and out
};
m::v1::make_join::opts opts{host};
m::v1::make_join request
{
room_id, user_id, buf, std::move(opts)
};
request.wait(seconds(make_join_timeout));
const auto code
{
request.get()
};
const json::object &response
{
request.in.content
};
const json::string &room_version
{
response.get("room_version", "1")
};
const json::object &proto
{
response.at("event")
};
const json::array &auth_events
{
proto.get("auth_events")
};
const json::array &prev_events
{
proto.get("prev_events")
};
json::iov event;
json::iov content;
const json::iov::push push[]
{
{ event, { "type", "m.room.member" }},
{ event, { "sender", user_id }},
{ event, { "state_key", user_id }},
{ content, { "membership", "join" }},
{ event, { "prev_events", prev_events }},
{ event, { "auth_events", auth_events }},
{ event, { "prev_state", "[]" }},
{ event, { "depth", proto.get<long>("depth") }},
{ event, { "room_id", room_id }},
};
const m::user user{user_id};
const m::user::profile profile{user};
char displayname_buf[256];
const string_view displayname
{
profile.get(displayname_buf, "displayname")
};
char avatar_url_buf[256];
const string_view avatar_url
{
profile.get(avatar_url_buf, "avatar_url")
};
const json::iov::add _displayname
{
content, !empty(displayname),
{
"displayname", [&displayname]() -> json::value
{
return displayname;
}
}
};
const json::iov::add _avatar_url
{
content, !empty(avatar_url),
{
"avatar_url", [&avatar_url]() -> json::value
{
return avatar_url;
}
}
};
m::vm::copts vmopts;
vmopts.infolog_accept = true;
vmopts.fetch = false;
vmopts.eval = false;
vmopts.user_id = user_id;
vmopts.room_version = room_version;
vm::eval eval
{
event, content, vmopts
};
const m::event::id::buf &ret
{
eval
};
if(unlikely(!ret))
throw m::UNAVAILABLE
{
"Unknown error"
};
return ret;
}
catch(const std::exception &e)
{
log::error
{
log, "Bootstrap %s for %s make_join to %s :%s",
string_view{room_id},
string_view{user_id},
string(host),
e.what(),
};
throw;
}