0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-25 16:22:35 +01:00
construct/ircd/m_room.cc

4207 lines
77 KiB
C++

// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 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.
size_t
ircd::m::room::purge(const room &room)
{
size_t ret(0);
db::txn txn
{
*m::dbs::events
};
room.for_each([&txn, &ret]
(const m::event::idx &idx)
{
const m::event::fetch event
{
idx
};
m::dbs::write_opts opts;
opts.op = db::op::DELETE;
opts.event_idx = idx;
m::dbs::write(txn, event, opts);
++ret;
});
txn();
return ret;
}
ircd::m::room::state::rebuild::rebuild(const room::id &room_id)
{
const m::event::id::buf event_id
{
m::head(room_id)
};
const m::room::state::history history
{
room_id, event_id
};
const m::room::state present_state
{
room_id
};
m::dbs::write_opts opts;
opts.appendix.reset();
opts.appendix.set(dbs::appendix::ROOM_STATE);
opts.appendix.set(dbs::appendix::ROOM_JOINED);
db::txn txn
{
*m::dbs::events
};
ssize_t deleted(0);
present_state.for_each([&opts, &txn, &deleted]
(const auto &type, const auto &state_key, const auto &event_idx)
{
const m::event::fetch &event
{
event_idx, std::nothrow
};
if(!event.valid)
return true;
auto _opts(opts);
_opts.op = db::op::DELETE;
_opts.event_idx = event_idx;
dbs::write(txn, event, _opts);
++deleted;
return true;
});
ssize_t added(0);
history.for_each([&opts, &txn, &added, &room_id]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
const m::event::fetch &event
{
event_idx, std::nothrow
};
if(!event.valid)
return true;
const auto &[pass, fail]
{
auth::check_present(event)
};
if(!pass)
{
log::dwarning
{
log, "%s fails for present state in %s :%s",
string_view{event.event_id},
string_view{room_id},
what(fail),
};
return true;
}
auto _opts(opts);
_opts.op = db::op::SET;
_opts.event_idx = event_idx;
dbs::write(txn, event, _opts);
++added;
return true;
});
log::info
{
log, "Present state of %s @ %s rebuild complete with %zu size:%s del:%zd add:%zd (%zd)",
string_view{room_id},
string_view{event_id},
txn.size(),
pretty(iec(txn.bytes())),
deleted,
added,
(added - deleted),
};
txn();
}
bool
ircd::m::room::state::is(const event::idx &event_idx)
{
bool ret{false};
m::get(event_idx, "state_key", [&ret]
(const string_view &state_key)
{
ret = true;
});
return ret;
}
bool
ircd::m::room::state::is(std::nothrow_t,
const event::idx &event_idx)
{
bool ret{false};
m::get(std::nothrow, event_idx, "state_key", [&ret]
(const string_view &state_key)
{
ret = true;
});
return ret;
}
size_t
ircd::m::room::state::purge_replaced(const room::id &room_id)
{
db::txn txn
{
*m::dbs::events
};
size_t ret(0);
m::room::events it
{
room_id, uint64_t(0)
};
if(!it)
return ret;
for(; it; ++it)
{
const m::event::idx &event_idx(it.event_idx());
if(!m::get(std::nothrow, event_idx, "state_key", [](const auto &) {}))
continue;
if(!m::event::refs(event_idx).count(m::dbs::ref::NEXT_STATE))
continue;
// TODO: erase event
}
return ret;
}
bool
ircd::m::room::state::present(const event::idx &event_idx)
{
static const event::fetch::opts fopts
{
event::keys::include { "room_id", "type", "state_key" },
};
const m::event::fetch event
{
event_idx, fopts
};
const m::room room
{
at<"room_id"_>(event)
};
const m::room::state state
{
room
};
const auto state_idx
{
state.get(std::nothrow, at<"type"_>(event), at<"state_key"_>(event))
};
assert(event_idx);
return state_idx == event_idx;
}
size_t
ircd::m::room::state::prefetch(const state &state,
const string_view &type,
const event::idx_range &range)
{
const m::event::fetch::opts &fopts
{
state.fopts?
*state.fopts:
m::event::fetch::default_opts
};
size_t ret{0};
state.for_each(type, m::event::closure_idx{[&ret, &fopts, &range]
(const m::event::idx &event_idx)
{
if(event_idx < range.first)
return;
if(range.second && event_idx > range.second)
return;
ret += m::prefetch(event_idx, fopts);
}});
ctx::yield();
return ret;
}
ircd::m::event::idx
ircd::m::room::state::prev(const event::idx &event_idx)
{
event::idx ret{0};
prev(event_idx, [&ret]
(const event::idx &event_idx)
{
if(event_idx > ret)
ret = event_idx;
return true;
});
return ret;
}
ircd::m::event::idx
ircd::m::room::state::next(const event::idx &event_idx)
{
event::idx ret{0};
next(event_idx, [&ret]
(const event::idx &event_idx)
{
if(event_idx > ret)
ret = event_idx;
return true;
});
return ret;
}
bool
ircd::m::room::state::next(const event::idx &event_idx,
const event::closure_idx_bool &closure)
{
const m::event::refs refs
{
event_idx
};
return refs.for_each(dbs::ref::NEXT_STATE, [&closure]
(const event::idx &event_idx, const dbs::ref &ref)
{
assert(ref == dbs::ref::NEXT_STATE);
return closure(event_idx);
});
}
bool
ircd::m::room::state::prev(const event::idx &event_idx,
const event::closure_idx_bool &closure)
{
const m::event::refs refs
{
event_idx
};
return refs.for_each(dbs::ref::PREV_STATE, [&closure]
(const event::idx &event_idx, const dbs::ref &ref)
{
assert(ref == dbs::ref::PREV_STATE);
return closure(event_idx);
});
}
ircd::m::room
ircd::m::create(const id::room &room_id,
const id::user &creator,
const string_view &preset)
{
return create(createroom
{
{ "room_id", room_id },
{ "creator", creator },
{ "preset", preset },
});
}
ircd::m::room
ircd::m::create(const createroom &c,
json::stack::array *const &errors)
{
using prototype = room (const createroom &, json::stack::array *const &);
static mods::import<prototype> call
{
"m_room_create", "ircd::m::create"
};
return call(c, errors);
}
ircd::m::event::id::buf
ircd::m::join(const id::room_alias &room_alias,
const id::user &user_id)
{
using prototype = event::id::buf (const id::room_alias &, const id::user &);
static mods::import<prototype> function
{
"m_room_join", "ircd::m::join"
};
return function(room_alias, user_id);
}
ircd::m::event::id::buf
ircd::m::join(const room &room,
const id::user &user_id)
{
using prototype = event::id::buf (const m::room &, const id::user &);
static mods::import<prototype> function
{
"m_room_join", "ircd::m::join"
};
return function(room, user_id);
}
ircd::m::event::id::buf
ircd::m::leave(const room &room,
const id::user &user_id)
{
using prototype = event::id::buf (const m::room &, const id::user &);
static mods::import<prototype> function
{
"m_room_leave", "ircd::m::leave"
};
return function(room, user_id);
}
ircd::m::event::id::buf
ircd::m::invite(const room &room,
const id::user &target,
const id::user &sender)
{
json::iov content;
return invite(room, target, sender, content);
}
ircd::m::event::id::buf
ircd::m::invite(const room &room,
const id::user &target,
const id::user &sender,
json::iov &content)
{
using prototype = event::id::buf (const m::room &, const id::user &, const id::user &, json::iov &);
static mods::import<prototype> call
{
"client_rooms", "ircd::m::invite"
};
return call(room, target, sender, content);
}
ircd::m::event::id::buf
ircd::m::redact(const room &room,
const id::user &sender,
const id::event &event_id,
const string_view &reason)
{
using prototype = event::id::buf (const m::room &, const id::user &, const id::event &, const string_view &);
static mods::import<prototype> function
{
"client_rooms", "redact__"
};
return function(room, sender, event_id, reason);
}
ircd::m::event::id::buf
ircd::m::notice(const room &room,
const string_view &body)
{
return message(room, me.user_id, body, "m.notice");
}
ircd::m::event::id::buf
ircd::m::notice(const room &room,
const m::id::user &sender,
const string_view &body)
{
return message(room, sender, body, "m.notice");
}
ircd::m::event::id::buf
ircd::m::msghtml(const room &room,
const m::id::user &sender,
const string_view &html,
const string_view &alt,
const string_view &msgtype)
{
return message(room, sender,
{
{ "msgtype", msgtype },
{ "format", "org.matrix.custom.html" },
{ "body", { alt?: html, json::STRING } },
{ "formatted_body", { html, json::STRING } },
});
}
ircd::m::event::id::buf
ircd::m::message(const room &room,
const m::id::user &sender,
const string_view &body,
const string_view &msgtype)
{
return message(room, sender,
{
{ "body", { body, json::STRING } },
{ "msgtype", { msgtype, json::STRING } },
});
}
ircd::m::event::id::buf
ircd::m::message(const room &room,
const m::id::user &sender,
const json::members &contents)
{
return send(room, sender, "m.room.message", contents);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
const m::id::user &sender,
const string_view &type,
const string_view &state_key,
const json::members &contents)
{
const size_t contents_count
{
std::min(contents.size(), json::object::max_sorted_members)
};
json::iov _content;
json::iov::push content[contents_count]; // 48B each
return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
const m::id::user &sender,
const string_view &type,
const string_view &state_key,
const json::object &contents)
{
const size_t contents_count
{
std::min(contents.size(), json::object::max_sorted_members)
};
json::iov _content;
json::iov::push content[contents_count]; // 48B each
return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop
ircd::m::event::id::buf
ircd::m::send(const room &room,
const m::id::user &sender,
const string_view &type,
const string_view &state_key,
const json::iov &content)
{
json::iov event;
const json::iov::push push[]
{
{ event, { "sender", sender }},
{ event, { "type", type }},
{ event, { "state_key", state_key }},
};
return commit(room, event, content);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
const m::id::user &sender,
const string_view &type,
const json::members &contents)
{
const size_t contents_count
{
std::min(contents.size(), json::object::max_sorted_members)
};
json::iov _content;
json::iov::push content[contents_count]; // 48B each
return send(room, sender, type, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
const m::id::user &sender,
const string_view &type,
const json::object &contents)
{
const size_t contents_count
{
std::min(contents.size(), json::object::max_sorted_members)
};
json::iov _content;
json::iov::push content[contents_count]; // 48B each
return send(room, sender, type, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop
ircd::m::event::id::buf
ircd::m::send(const room &room,
const id::user &sender,
const string_view &type,
const json::iov &content)
{
json::iov event;
const json::iov::push push[]
{
{ event, { "sender", sender }},
{ event, { "type", type }},
};
return commit(room, event, content);
}
ircd::m::event::id::buf
ircd::m::commit(const room &room,
json::iov &event,
const json::iov &contents)
{
// Set the room_id on the iov
json::iov::push room_id
{
event, { "room_id", room.room_id }
};
vm::copts opts
{
room.copts?
*room.copts:
vm::default_copts
};
// Some functionality on this server may create an event on behalf
// of remote users. It's safe for us to mask this here, but eval'ing
// this event in any replay later will require special casing.
opts.non_conform |= event::conforms::MISMATCH_ORIGIN_SENDER;
// Don't need this here
opts.verify = false;
return vm::eval
{
event, contents, opts
};
}
ircd::m::id::room::buf
ircd::m::room_id(const id::room_alias &room_alias)
{
char buf[m::id::MAX_SIZE + 1];
static_assert(sizeof(buf) <= 256);
return room_id(buf, room_alias);
}
ircd::m::id::room::buf
ircd::m::room_id(const string_view &room_id_or_alias)
{
char buf[m::id::MAX_SIZE + 1];
static_assert(sizeof(buf) <= 256);
return room_id(buf, room_id_or_alias);
}
ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
const string_view &room_id_or_alias)
{
switch(m::sigil(room_id_or_alias))
{
case id::ROOM:
return id::room{out, room_id_or_alias};
case id::USER:
{
const m::user::room user_room(room_id_or_alias);
return string_view{data(out), copy(out, user_room.room_id)};
}
case id::NODE:
{
const m::node node(lstrip(room_id_or_alias, ':'));
return node.room_id(out);
}
default:
return room_id(out, id::room_alias{room_id_or_alias});
}
}
ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
const id::room_alias &room_alias)
{
room::id ret;
room::aliases::cache::get(room_alias, [&out, &ret]
(const room::id &room_id)
{
ret = string_view { data(out), copy(out, room_id) };
});
return ret;
}
bool
ircd::m::exists(const id::room_alias &room_alias,
const bool &remote_query)
{
if(room::aliases::cache::has(room_alias))
return true;
if(!remote_query)
return false;
return room::aliases::cache::get(std::nothrow, room_alias, [](const room::id &room_id) {});
}
int64_t
ircd::m::depth(const id::room &room_id)
{
return std::get<int64_t>(top(room_id));
}
int64_t
ircd::m::depth(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return -1;
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<0>(part);
}
ircd::m::event::idx
ircd::m::head_idx(const id::room &room_id)
{
return std::get<event::idx>(top(room_id));
}
ircd::m::event::idx
ircd::m::head_idx(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return 0;
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<1>(part);
}
ircd::m::event::id::buf
ircd::m::head(const id::room &room_id)
{
return std::get<event::id::buf>(top(room_id));
}
ircd::m::event::id::buf
ircd::m::head(std::nothrow_t,
const id::room &room_id)
{
return std::get<event::id::buf>(top(std::nothrow, room_id));
}
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(const id::room &room_id)
{
const auto ret
{
top(std::nothrow, room_id)
};
if(std::get<int64_t>(ret) == -1)
throw m::NOT_FOUND
{
"No head for room %s", string_view{room_id}
};
return ret;
}
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(std::nothrow_t,
const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
if(!it)
return
{
event::id::buf{}, -1, 0
};
const auto part
{
dbs::room_events_key(it->first)
};
const int64_t &depth
{
int64_t(std::get<0>(part))
};
const event::idx &event_idx
{
std::get<1>(part)
};
std::tuple<event::id::buf, int64_t, event::idx> ret
{
event::id::buf{}, depth, event_idx
};
m::event_id(event_idx, std::nothrow, [&ret]
(const event::id &event_id)
{
std::get<event::id::buf>(ret) = event_id;
});
return ret;
}
ircd::m::id::user::buf
ircd::m::any_user(const room &room,
const string_view &host,
const string_view &membership)
{
user::id::buf ret;
const room::members members{room};
members.for_each(membership, [&host, &ret]
(const auto &user_id, const auto &event_idx)
{
if(host && user_id.host() != host)
return true;
ret = user_id;
return false;
});
return ret;
}
ircd::string_view
ircd::m::membership(const mutable_buffer &out,
const room &room,
const user::id &user_id)
{
const room::state state
{
room
};
const auto event_idx
{
state.get(std::nothrow, "m.room.member", user_id)
};
return room::members::membership(out, event_idx);
}
/// Receive the join_rule of the room into buffer of sufficient size.
/// The protocol does not specify a join_rule string longer than 7
/// characters but do be considerate of the future. This function
/// properly defaults the string as per the protocol spec.
ircd::string_view
ircd::m::join_rule(const mutable_buffer &out,
const room &room)
{
static const string_view default_join_rule
{
"invite"
};
string_view ret
{
default_join_rule
};
const event::keys::include keys
{
"content"
};
const m::event::fetch::opts fopts
{
keys, room.fopts? room.fopts->gopts : db::gopts{}
};
const room::state state
{
room, &fopts
};
state.get(std::nothrow, "m.room.join_rules", "", [&ret, &out]
(const m::event &event)
{
const auto &content
{
json::get<"content"_>(event)
};
const json::string &rule
{
content.get("join_rule", default_join_rule)
};
ret = string_view
{
data(out), copy(out, rule)
};
});
return ret;
}
ircd::string_view
ircd::m::version(const mutable_buffer &buf,
const room &room)
{
const auto ret
{
version(buf, room, std::nothrow)
};
if(!ret)
throw m::NOT_FOUND
{
"Failed to find room %s to query its version",
string_view{room.room_id}
};
return ret;
}
ircd::string_view
ircd::m::version(const mutable_buffer &buf,
const room &room,
std::nothrow_t)
{
const auto event_idx
{
room.get(std::nothrow, "m.room.create", "")
};
string_view ret
{
strlcpy{buf, "1"_sv}
};
m::get(std::nothrow, event_idx, "content", [&buf, &ret]
(const json::object &content)
{
const json::string &version
{
content.get("room_version", "1")
};
ret = strlcpy
{
buf, version
};
});
return ret;
}
ircd::string_view
ircd::m::type(const mutable_buffer &buf,
const room &room)
{
string_view ret;
const auto event_idx
{
room.get(std::nothrow, "m.room.create", "")
};
m::get(std::nothrow, event_idx, "content", [&buf, &ret]
(const json::object &content)
{
const json::string &type
{
content.get("type")
};
ret = strlcpy
{
buf, type
};
});
return ret;
}
ircd::m::id::user::buf
ircd::m::creator(const id::room &room_id)
{
// Query the sender field of the event to get the creator. This is for
// future compatibility if the content.creator field gets eliminated.
static const event::fetch::opts fopts
{
event::keys::include {"sender"}
};
const room::state state
{
room_id, &fopts
};
id::user::buf ret;
state.get("m.room.create", "", [&ret]
(const m::event &event)
{
ret = user::id
{
json::get<"sender"_>(event)
};
});
return ret;
}
//
// boolean suite
//
/// The only joined members are from our origin (local only). This indicates
/// we won't have any other federation servers to query for room data, nor do
/// we need to broadcast events to the federation. This is not an authority
/// about a room's type or ability to federate. Returned value changes to false
/// when another origin joins.
bool
ircd::m::local_only(const room &room)
{
const room::origins origins
{
room
};
return origins.empty() || origins.only(my_host());
}
bool
ircd::m::visible(const room &room,
const string_view &mxid,
const event *const &event)
{
if(event)
return m::visible(*event, mxid);
const m::event event_
{
json::members
{
{ "event_id", room.event_id },
{ "room_id", room.room_id },
}
};
return m::visible(event_, mxid);
}
/// Test of the join_rule of the room is the argument.
bool
ircd::m::join_rule(const room &room,
const string_view &rule)
{
char buf[32];
return join_rule(buf, room) == rule;
}
bool
ircd::m::membership(const room &room,
const user::id &user_id,
const string_view &membership)
{
const room::state state
{
room
};
const auto event_idx
{
state.get(std::nothrow, "m.room.member", user_id)
};
return room::members::membership(event_idx, membership);
}
bool
ircd::m::creator(const room::id &room_id,
const user::id &user_id)
{
const auto creator_user_id
{
creator(room_id)
};
return creator_user_id == user_id;
}
bool
ircd::m::federated(const id::room &room_id)
{
static const m::event::fetch::opts fopts
{
event::keys::include { "content" },
};
const m::room::state state
{
room_id, &fopts
};
bool ret;
state.get("m.room.create", "", [&ret]
(const m::event &event)
{
ret = json::get<"content"_>(event).get("m.federate", true);
});
return ret;
}
/// Determine if this is an internal room. The following must be satisfied:
///
/// - The room was created by this origin.
/// - The creator was the server itself, not any other user.
bool
ircd::m::internal(const id::room &room_id)
{
const m::room room
{
room_id
};
if(!my(room))
return false;
if(!exists(room))
return false;
if(!creator(room, m::me))
return false;
return true;
}
bool
ircd::m::exists(const id::room &room_id)
{
const auto it
{
dbs::room_events.begin(room_id)
};
return bool(it);
}
bool
ircd::m::exists(const room &room)
{
return exists(room.room_id);
}
//
// util
//
bool
ircd::m::operator==(const room &a, const room &b)
{
return !(a != b);
}
bool
ircd::m::operator!=(const room &a_, const room &b_)
{
const string_view &a{a_.room_id}, &b{b_.room_id};
return a != b;
}
bool
ircd::m::operator!(const room &a)
{
return !a.room_id;
}
bool
ircd::m::my(const room &room)
{
return my(room.room_id);
}
//
// room
//
/// A room index is just the event::idx of its create event.
ircd::m::event::idx
ircd::m::room::index(const room::id &room_id)
{
const auto ret
{
index(room_id, std::nothrow)
};
if(!ret)
throw m::NOT_FOUND
{
"No index for room %s", string_view{room_id}
};
return ret;
}
ircd::m::event::idx
ircd::m::room::index(const room::id &room_id,
std::nothrow_t)
{
uint64_t depth{0};
room::events it
{
room_id, depth
};
return it? it.event_idx() : 0;
}
//
// room::room
//
size_t
ircd::m::room::count()
const
{
size_t ret(0);
for_each(event::closure_idx_bool{[&ret]
(const event::idx &event_idx)
{
++ret;
return true;
}});
return ret;
}
size_t
ircd::m::room::count(const string_view &type)
const
{
size_t ret(0);
for_each(type, event::closure_idx_bool{[&ret]
(const event::idx &event_idx)
{
++ret;
return true;
}});
return ret;
}
size_t
ircd::m::room::count(const string_view &type,
const string_view &state_key)
const
{
size_t ret(0);
for_each(type, event::closure_idx_bool{[&state_key, &ret]
(const event::idx &event_idx)
{
ret += query(std::nothrow, event_idx, "state_key", [&state_key]
(const string_view &_state_key) -> bool
{
return state_key == _state_key;
});
return true;
}});
return ret;
}
bool
ircd::m::room::has(const string_view &type)
const
{
return get(std::nothrow, type, event::closure{});
}
void
ircd::m::room::get(const string_view &type,
const event::closure &closure)
const
{
if(!get(std::nothrow, type, closure))
throw m::NOT_FOUND
{
"No events of type '%s' found in '%s'",
type,
room_id
};
}
bool
ircd::m::room::get(std::nothrow_t,
const string_view &type,
const event::closure &closure)
const
{
bool ret{false};
for_each(type, event::closure_bool{[&ret, &closure]
(const event &event)
{
if(closure)
closure(event);
ret = true;
return false;
}});
return ret;
}
ircd::m::event::idx
ircd::m::room::get(const string_view &type)
const
{
const event::idx ret
{
get(std::nothrow, type)
};
if(unlikely(!ret))
throw m::NOT_FOUND
{
"No events of type '%s' found in '%s'",
type,
room_id
};
return ret;
}
ircd::m::event::idx
ircd::m::room::get(std::nothrow_t,
const string_view &type)
const
{
event::idx ret{0};
for_each(type, event::closure_idx_bool{[&ret]
(const event::idx &event_idx)
{
ret = event_idx;
return false;
}});
return ret;
}
ircd::m::event::idx
ircd::m::room::get(const string_view &type,
const string_view &state_key)
const
{
return state(*this).get(type, state_key);
}
ircd::m::event::idx
ircd::m::room::get(std::nothrow_t,
const string_view &type,
const string_view &state_key)
const
{
return state(*this).get(std::nothrow, type, state_key);
}
void
ircd::m::room::get(const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
const state state{*this};
state.get(type, state_key, closure);
}
bool
ircd::m::room::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
const state state{*this};
return state.get(std::nothrow, type, state_key, closure);
}
bool
ircd::m::room::has(const string_view &type,
const string_view &state_key)
const
{
const state state{*this};
return state.has(type, state_key);
}
void
ircd::m::room::for_each(const event::closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const event::id::closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::id::closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const event::closure_idx &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::for_each(const event::closure_idx_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::for_each(const string_view &type,
const event::closure &closure)
const
{
for_each(type, event::closure_bool{[&closure]
(const event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(type, event::closure_idx_bool{[&closure, &event]
(const event::idx &event_idx)
{
if(!seek(event, event_idx, std::nothrow))
return true;
return closure(event);
}});
}
void
ircd::m::room::for_each(const string_view &type,
const event::id::closure &closure)
const
{
for_each(type, event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::id::closure_bool &closure)
const
{
return for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
m::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &event_id)
{
ret = closure(event_id);
});
return ret;
}});
}
void
ircd::m::room::for_each(const string_view &type,
const event::closure_idx &closure)
const
{
for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
closure(idx);
return true;
}});
}
bool
ircd::m::room::for_each(const string_view &type,
const event::closure_idx_bool &closure)
const
{
static constexpr auto idx
{
json::indexof<event, "type"_>()
};
auto &column
{
dbs::event_column.at(idx)
};
events it{*this};
for(; it; --it)
{
const auto &event_idx
{
it.event_idx()
};
bool match
{
empty(type) // allow empty type to always match and bypass query
};
if(!match)
column(byte_view<string_view>(event_idx), std::nothrow, [&match, &type]
(const string_view &value)
{
match = value == type;
});
if(match)
if(!closure(event_idx))
return false;
}
return true;
}
//
// room::events
//
ircd::m::room::events::events(const m::room &room,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
assert(room.room_id);
if(room.event_id)
seek(room.event_id);
else
seek();
}
ircd::m::room::events::events(const m::room &room,
const event::id &event_id,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
assert(room.room_id);
seek(event_id);
}
ircd::m::room::events::events(const m::room &room,
const uint64_t &depth,
const event::fetch::opts *const &fopts)
:room{room}
,_event
{
fopts?
*fopts:
room.fopts?
*room.fopts:
event::fetch::default_opts
}
{
assert(room.room_id);
// As a special convenience for the ctor only, if the depth=0 and
// nothing is found another attempt is made for depth=1 for synapse
// rooms which start at depth=1.
if(!seek(depth) && depth == 0)
seek(1);
}
const ircd::m::event &
ircd::m::room::events::operator*()
{
return fetch(std::nothrow);
};
bool
ircd::m::room::events::seek(const event::id &event_id)
{
const event::idx &event_idx
{
m::index(event_id, std::nothrow)
};
return event_idx?
seek_idx(event_idx):
false;
}
bool
ircd::m::room::events::seek(const uint64_t &depth)
{
char buf[dbs::ROOM_EVENTS_KEY_MAX_SIZE];
const string_view seek_key
{
depth != uint64_t(-1)?
dbs::room_events_key(buf, room.room_id, depth):
room.room_id
};
this->it = dbs::room_events.begin(seek_key);
return bool(*this);
}
bool
ircd::m::room::events::seek_idx(const event::idx &event_idx)
try
{
uint64_t depth(0);
if(event_idx)
m::get(event_idx, "depth", mutable_buffer
{
reinterpret_cast<char *>(&depth), sizeof(depth)
});
char buf[dbs::ROOM_EVENTS_KEY_MAX_SIZE];
const auto &seek_key
{
dbs::room_events_key(buf, room.room_id, depth, event_idx)
};
this->it = dbs::room_events.begin(seek_key);
if(!bool(*this))
return false;
// Check if this event_idx is actually in this room
if(event_idx && event_idx != this->event_idx())
return false;
return true;
}
catch(const db::not_found &e)
{
return false;
}
ircd::m::room::events::operator
ircd::m::event::idx()
const
{
return event_idx();
}
ircd::m::event::id::buf
ircd::m::room::events::event_id()
const
{
return m::event_id(this->event_idx(), std::nothrow);
}
uint64_t
ircd::m::room::events::depth()
const
{
assert(bool(*this));
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<0>(part);
}
ircd::m::event::idx
ircd::m::room::events::event_idx()
const
{
assert(bool(*this));
const auto part
{
dbs::room_events_key(it->first)
};
return std::get<1>(part);
}
const ircd::m::event &
ircd::m::room::events::fetch()
{
m::seek(_event, event_idx());
return _event;
}
const ircd::m::event &
ircd::m::room::events::fetch(std::nothrow_t)
{
m::seek(_event, event_idx(), std::nothrow);
return _event;
}
//
// room::state
//
decltype(ircd::m::room::state::enable_history)
ircd::m::room::state::enable_history
{
{ "name", "ircd.m.room.state.enable_history" },
{ "default", true },
};
decltype(ircd::m::room::state::readahead_size)
ircd::m::room::state::readahead_size
{
{ "name", "ircd.m.room.state.readahead_size" },
{ "default", 0L },
};
//
// room::state::state
//
ircd::m::room::state::state(const m::room &room,
const event::fetch::opts *const &fopts)
:room_id
{
room.room_id
}
,event_id
{
room.event_id?
event::id::buf{room.event_id}:
event::id::buf{}
}
,fopts
{
fopts?
fopts:
room.fopts
}
{
}
size_t
ircd::m::room::state::prefetch(const event::idx &start,
const event::idx &stop)
const
{
return prefetch(string_view{}, start, stop);
}
size_t
ircd::m::room::state::prefetch(const string_view &type,
const event::idx &start,
const event::idx &stop)
const
{
return prefetch(*this, type, event::idx_range{start, stop});
}
ircd::m::event::idx
ircd::m::room::state::get(const string_view &type,
const string_view &state_key)
const
{
event::idx ret;
get(type, state_key, event::closure_idx{[&ret]
(const event::idx &event_idx)
{
ret = event_idx;
}});
return ret;
}
ircd::m::event::idx
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key)
const
{
event::idx ret{0};
get(std::nothrow, type, state_key, event::closure_idx{[&ret]
(const event::idx &event_idx)
{
ret = event_idx;
}});
return ret;
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
get(type, state_key, event::closure_idx{[this, &closure]
(const event::idx &event_idx)
{
const event::fetch event
{
event_idx, fopts? *fopts : event::fetch::default_opts
};
closure(event);
}});
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::id::closure &closure)
const
{
get(type, state_key, event::closure_idx{[&]
(const event::idx &idx)
{
if(!m::event_id(idx, std::nothrow, closure))
throw m::NOT_FOUND
{
"(%s,%s) in %s idx:%lu event_id :not found",
type,
state_key,
string_view{room_id},
idx,
};
}});
}
void
ircd::m::room::state::get(const string_view &type,
const string_view &state_key,
const event::closure_idx &closure)
const try
{
if(!present())
{
const history history
{
room_id, event_id
};
closure(history.get(type, state_key));
return;
}
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
column(dbs::room_state_key(key, room_id, type, state_key), [&closure]
(const string_view &value)
{
closure(byte_view<event::idx>(value));
});
}
catch(const db::not_found &e)
{
throw m::NOT_FOUND
{
"(%s,%s) in %s :%s",
type,
state_key,
string_view{room_id},
e.what()
};
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure &closure)
const
{
return get(std::nothrow, type, state_key, event::closure_idx{[this, &closure]
(const event::idx &event_idx)
{
const event::fetch event
{
event_idx, std::nothrow, fopts? *fopts : event::fetch::default_opts
};
closure(event);
}});
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::id::closure &closure)
const
{
return get(std::nothrow, type, state_key, event::closure_idx{[&closure]
(const event::idx &idx)
{
m::event_id(idx, std::nothrow, closure);
}});
}
bool
ircd::m::room::state::get(std::nothrow_t,
const string_view &type,
const string_view &state_key,
const event::closure_idx &closure)
const
{
if(!present())
{
const history history
{
room_id, event_id
};
const auto event_idx
{
history.get(std::nothrow, type, state_key)
};
if(event_idx)
{
closure(event_idx);
return true;
}
else return false;
}
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
return column(dbs::room_state_key(key, room_id, type, state_key), std::nothrow, [&closure]
(const string_view &value)
{
closure(byte_view<event::idx>(value));
});
}
bool
ircd::m::room::state::has(const event::idx &event_idx)
const
{
static const event::fetch::opts fopts
{
event::keys::include { "type", "state_key" },
};
const m::event::fetch event
{
event_idx, std::nothrow, fopts
};
if(!event.valid)
return false;
const auto state_idx
{
get(std::nothrow, at<"type"_>(event), at<"state_key"_>(event))
};
assert(event_idx);
return event_idx == state_idx;
}
bool
ircd::m::room::state::has(const string_view &type)
const
{
return for_each(type, event::id::closure_bool{[](const m::event::id &)
{
return true;
}});
}
bool
ircd::m::room::state::has(const string_view &type,
const string_view &state_key)
const
{
if(!present())
{
const history history
{
room_id, event_id
};
return history.has(type, state_key);
}
auto &column{dbs::room_state};
char key[dbs::ROOM_STATE_KEY_MAX_SIZE];
return db::has(column, dbs::room_state_key(key, room_id, type, state_key));
}
size_t
ircd::m::room::state::count()
const
{
if(!present())
return count(string_view{});
const db::gopts &opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
size_t ret(0);
auto &column{dbs::room_state};
for(auto it{column.begin(room_id, opts)}; bool(it); ++it)
++ret;
return ret;
}
size_t
ircd::m::room::state::count(const string_view &type)
const
{
if(!present())
return count(type);
const db::gopts &opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
size_t ret(0);
auto &column{dbs::room_state};
for(auto it{column.begin(room_id, opts)}; bool(it); ++it)
{
const auto key(dbs::room_state_key(it->first));
ret += std::get<0>(key) == type;
}
return ret;
}
void
ircd::m::room::state::for_each(const event::closure &closure)
const
{
for_each(event::closure_bool{[&closure]
(const m::event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(event::closure_idx_bool{[&event, &closure]
(const event::idx &event_idx)
{
if(seek(event, event_idx, std::nothrow))
if(!closure(event))
return false;
return true;
}});
}
void
ircd::m::room::state::for_each(const event::id::closure &closure)
const
{
for_each(event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::id::closure_bool &closure)
const
{
return for_each(event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
m::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &id)
{
ret = closure(id);
});
return ret;
}});
}
void
ircd::m::room::state::for_each(const event::closure_idx &closure)
const
{
for_each(event::closure_idx_bool{[&closure]
(const event::idx &event_idx)
{
closure(event_idx);
return true;
}});
}
bool
ircd::m::room::state::for_each(const event::closure_idx_bool &closure)
const
{
return for_each(closure_bool{[&closure]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
return closure(event_idx);
}});
}
bool
ircd::m::room::state::for_each(const closure_bool &closure)
const
{
if(!present())
{
const history history
{
room_id, event_id
};
return history.for_each([&closure]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
return closure(type, state_key, event_idx);
});
}
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
if(!opts.readahead)
opts.readahead = size_t(readahead_size);
auto &column{dbs::room_state};
for(auto it{column.begin(room_id, opts)}; bool(it); ++it)
{
const byte_view<event::idx> idx(it->second);
const auto key(dbs::room_state_key(it->first));
if(!closure(std::get<0>(key), std::get<1>(key), idx))
return false;
}
return true;
}
bool
ircd::m::room::state::for_each(const type_prefix &prefix,
const closure_bool &closure)
const
{
bool ret(true), cont(true);
for_each(closure_bool{[&prefix, &closure, &ret, &cont]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
if(!startswith(type, string_view(prefix)))
return cont;
cont = false;
ret = closure(type, state_key, event_idx);
return ret;
}});
return ret;
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::closure &closure)
const
{
for_each(type, event::closure_bool{[&closure]
(const m::event &event)
{
closure(event);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::closure_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::id::closure &closure)
const
{
for_each(type, event::id::closure_bool{[&closure]
(const event::id &event_id)
{
closure(event_id);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::id::closure_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
void
ircd::m::room::state::for_each(const string_view &type,
const event::closure_idx &closure)
const
{
for_each(type, event::closure_idx_bool{[&closure]
(const event::idx &event_idx)
{
closure(event_idx);
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const event::closure_idx_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
bool
ircd::m::room::state::for_each(const string_view &type,
const closure_bool &closure)
const
{
return type?
for_each(type, string_view{}, closure):
for_each(closure);
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::closure_bool &closure)
const
{
event::fetch event
{
fopts? *fopts : event::fetch::default_opts
};
return for_each(type, state_key_lb, event::closure_idx_bool{[&event, &closure]
(const event::idx &event_idx)
{
if(seek(event, event_idx, std::nothrow))
if(!closure(event))
return false;
return true;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::id::closure_bool &closure)
const
{
return for_each(type, state_key_lb, event::closure_idx_bool{[&closure]
(const event::idx &idx)
{
bool ret{true};
m::event_id(idx, std::nothrow, [&ret, &closure]
(const event::id &id)
{
ret = closure(id);
});
return ret;
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const event::closure_idx_bool &closure)
const
{
return for_each(type, state_key_lb, closure_bool{[&closure]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
return closure(event_idx);
}});
}
bool
ircd::m::room::state::for_each(const string_view &type,
const string_view &state_key_lb,
const closure_bool &closure)
const
{
if(!present())
{
const history history
{
room_id, event_id
};
return history.for_each(type, state_key_lb, [&closure]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
return closure(type, state_key, event_idx);
});
}
char keybuf[dbs::ROOM_STATE_KEY_MAX_SIZE];
const auto &key
{
dbs::room_state_key(keybuf, room_id, type, state_key_lb)
};
db::gopts opts
{
this->fopts? this->fopts->gopts : db::gopts{}
};
if(!opts.readahead)
opts.readahead = size_t(readahead_size);
auto &column{dbs::room_state};
for(auto it{column.begin(key, opts)}; bool(it); ++it)
{
const auto key
{
dbs::room_state_key(it->first)
};
if(std::get<0>(key) != type)
break;
const byte_view<event::idx> idx(it->second);
if(!closure(std::get<0>(key), std::get<1>(key), idx))
return false;
}
return true;
}
/// Figure out if this instance of room::state is presenting the current
/// "present" state of the room or the state of the room at some previous
/// event. This is an important distinction because the present state of
/// the room should provide optimal performance for the functions of this
/// interface by using the present state table. Prior states will use the
/// state btree.
bool
ircd::m::room::state::present()
const
{
// When no event_id is passed to the state constructor that immediately
// indicates the present state of the room is sought.
if(!event_id)
return true;
// When the global configuration disables history, always consider the
// present state. (disabling may yield unexpected incorrect results by
// returning the present state without error).
if(!enable_history)
return true;
// Check the cached value from a previous false result of this function
// before doing any real work/IO below. If this function ever returned
// false it will never return true after.
if(_not_present)
return false;
const auto head_id
{
m::head(std::nothrow, room_id)
};
// If the event_id passed is exactly the latest event we can obviously
// consider this the present state.
if(!head_id || head_id == event_id)
return true;
// This result is cacheable because once it's no longer the present
// it will never be again. Panta chorei kai ouden menei. Note that this
// is a const member function; the cache variable is an appropriate case
// for the 'mutable' keyword.
_not_present = true;
return false;
}
//
// room::state::history
//
ircd::m::room::state::history::history(const m::room &room)
:history
{
room, -1
}
{
}
ircd::m::room::state::history::history(const m::room::id &room_id,
const m::event::id &event_id)
:history
{
m::room
{
room_id, event_id
}
}
{
}
ircd::m::room::state::history::history(const m::room &room,
const int64_t &bound)
:space
{
room
}
,bound
{
bound < 0 && room.event_id?
m::get<int64_t>(m::index(room.event_id), "depth"):
bound
}
{
}
ircd::m::event::idx
ircd::m::room::state::history::get(const string_view &type,
const string_view &state_key)
const
{
const auto ret
{
get(std::nothrow, type, state_key)
};
if(unlikely(!ret))
throw m::NOT_FOUND
{
"(%s,%s) in %s @%ld$%s",
type,
state_key,
string_view{space.room.room_id},
bound,
string_view{space.room.event_id},
};
return ret;
}
ircd::m::event::idx
ircd::m::room::state::history::get(std::nothrow_t,
const string_view &type,
const string_view &state_key)
const
{
event::idx ret{0};
assert(type && defined(state_key));
for_each(type, state_key, [&ret]
(const auto &, const auto &, const auto &, const auto &event_idx)
{
ret = event_idx;
return false;
});
return ret;
}
bool
ircd::m::room::state::history::has(const string_view &type)
const
{
return has(type, string_view{});
}
bool
ircd::m::room::state::history::has(const string_view &type,
const string_view &state_key)
const
{
return !for_each(type, state_key, []
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
return false;
});
}
size_t
ircd::m::room::state::history::count(const string_view &type)
const
{
return count(type, string_view{});
}
size_t
ircd::m::room::state::history::count(const string_view &type,
const string_view &state_key)
const
{
size_t ret(0);
for_each(type, state_key, [&ret]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
++ret;
return true;
});
return ret;
}
bool
ircd::m::room::state::history::for_each(const closure &closure)
const
{
return for_each(string_view{}, string_view{}, closure);
}
bool
ircd::m::room::state::history::for_each(const string_view &type,
const closure &closure)
const
{
return for_each(type, string_view{}, closure);
}
bool
ircd::m::room::state::history::for_each(const string_view &type,
const string_view &state_key,
const closure &closure)
const
{
char type_buf[m::event::TYPE_MAX_SIZE];
char state_key_buf[m::event::STATE_KEY_MAX_SIZE];
string_view last_type;
string_view last_state_key;
return space.for_each(type, state_key, [&]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
if(bound > -1 && depth >= bound)
return true;
if(type == last_type && state_key == last_state_key)
return true;
if(!closure(type, state_key, depth, event_idx))
return false;
if(type != last_type)
last_type = { type_buf, copy(type_buf, type) };
if(state_key != last_state_key)
last_state_key = { state_key_buf, copy(state_key_buf, state_key) };
return true;
});
}
//
// room::state::space
//
ircd::m::room::state::space::space(const m::room &room)
:room
{
room
}
{
}
bool
ircd::m::room::state::space::has(const string_view &type)
const
{
return has(type, string_view{});
}
bool
ircd::m::room::state::space::has(const string_view &type,
const string_view &state_key)
const
{
return has(type, state_key, -1);
}
bool
ircd::m::room::state::space::has(const string_view &type,
const string_view &state_key,
const int64_t &depth)
const
{
return !for_each(type, state_key, depth, []
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
return false;
});
}
size_t
ircd::m::room::state::space::count()
const
{
return count(string_view{});
}
size_t
ircd::m::room::state::space::count(const string_view &type)
const
{
return count(type, string_view{});
}
size_t
ircd::m::room::state::space::count(const string_view &type,
const string_view &state_key)
const
{
return count(type, state_key, -1L);
}
size_t
ircd::m::room::state::space::count(const string_view &type,
const string_view &state_key,
const int64_t &depth)
const
{
size_t ret(0);
for_each(type, state_key, depth, [&ret]
(const auto &type, const auto &state_key, const auto &depth, const auto &event_idx)
{
++ret;
return true;
});
return ret;
}
bool
ircd::m::room::state::space::for_each(const closure &closure)
const
{
return for_each(string_view{}, string_view{}, -1L, closure);
}
bool
ircd::m::room::state::space::for_each(const string_view &type,
const closure &closure)
const
{
return for_each(type, string_view{}, -1L, closure);
}
bool
ircd::m::room::state::space::for_each(const string_view &type,
const string_view &state_key,
const closure &closure)
const
{
return for_each(type, state_key, -1L, closure);
}
bool
ircd::m::room::state::space::for_each(const string_view &type,
const string_view &state_key,
const int64_t &depth,
const closure &closure)
const
{
const int64_t &_depth
{
type? depth : 0L
};
char buf[dbs::ROOM_STATE_SPACE_KEY_MAX_SIZE];
const string_view &key
{
dbs::room_state_space_key(buf, room.room_id, type, state_key, _depth, 0UL)
};
auto it
{
dbs::room_state_space.begin(key)
};
for(; it; ++it)
{
const auto &[_type, _state_key, _depth, _event_idx]
{
dbs::room_state_space_key(it->first)
};
if(type && type != _type)
break;
if(state_key && state_key != _state_key)
break;
if(depth >= 0 && depth != _depth)
break;
if(!closure(_type, _state_key, _depth, _event_idx))
return false;
}
return true;
}
//
// room::state::space::rebuild
//
ircd::m::room::state::space::rebuild::rebuild(const room::id &room_id)
{
db::txn txn
{
*m::dbs::events
};
m::room::events it
{
room_id, uint64_t(0)
};
if(!it)
return;
size_t state_count(0), messages_count(0), state_deleted(0);
for(; it; ++it, ++messages_count) try
{
const m::event::idx &event_idx
{
it.event_idx()
};
if(!state::is(std::nothrow, event_idx))
continue;
++state_count;
const m::event &event{*it};
const auto &[pass_static, reason_static]
{
room::auth::check_static(event)
};
if(!pass_static)
log::dwarning
{
log, "%s in %s erased from state space (static) :%s",
string_view{event.event_id},
string_view{room_id},
what(reason_static),
};
const auto &[pass_relative, reason_relative]
{
pass_static?
room::auth::check_relative(event):
room::auth::passfail{false, {}},
};
if(pass_static && !pass_relative)
log::dwarning
{
log, "%s in %s erased from state space (relative) :%s",
string_view{event.event_id},
string_view{room_id},
what(reason_relative),
};
dbs::write_opts opts;
opts.event_idx = event_idx;
opts.appendix.reset();
opts.appendix.set(dbs::appendix::ROOM_STATE_SPACE);
opts.op = pass_static && pass_relative? db::op::SET : db::op::DELETE;
state_deleted += opts.op == db::op::DELETE;
dbs::write(txn, event, opts);
}
catch(const ctx::interrupted &e)
{
log::dwarning
{
log, "room::state::space::rebuild :%s",
e.what()
};
throw;
}
catch(const std::exception &e)
{
log::error
{
log, "room::state::space::rebuild :%s",
e.what()
};
}
log::info
{
log, "room::state::space::rebuild %s complete msgs:%zu state:%zu del:%zu transaction elems:%zu size:%s",
string_view{room_id},
messages_count,
state_count,
state_deleted,
txn.size(),
pretty(iec(txn.bytes()))
};
txn();
}
//
// room::members
//
bool
ircd::m::room::members::empty()
const
{
return empty(string_view{});
}
bool
ircd::m::room::members::empty(const string_view &membership)
const
{
return for_each(membership, closure{[](const auto &)
{
return false;
}});
}
size_t
ircd::m::room::members::count()
const
{
return count(string_view{});
}
size_t
ircd::m::room::members::count(const string_view &membership)
const
{
size_t ret{0};
this->for_each(membership, room::members::closure{[&ret]
(const id::user &)
{
++ret;
return true;
}});
return ret;
}
bool
ircd::m::room::members::for_each(const closure &closure)
const
{
return for_each(string_view{}, closure);
}
bool
ircd::m::room::members::for_each(const closure_idx &closure)
const
{
return for_each(string_view{}, closure);
}
bool
ircd::m::room::members::for_each(const string_view &membership,
const closure_idx &closure)
const
{
const m::room::state state
{
room
};
const bool present
{
state.present()
};
// joined members optimization. Only possible when seeking
// membership="join" on the present state of the room.
if(membership == "join" && present)
return this->for_each(membership, [this, &closure, &state]
(const id::user &member)
{
const auto event_idx
{
state.get(std::nothrow, "m.room.member", member)
};
if(likely(event_idx))
return closure(member, event_idx);
log::error
{
log, "Failed to find member event_idx:%zu for %s in present state of %s",
event_idx,
string_view{member},
string_view{room.room_id},
};
return true;
});
return state.for_each("m.room.member", [this, &membership, &closure]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
return !membership || this->membership(event_idx, membership)?
closure(state_key, event_idx):
true;
});
}
bool
ircd::m::room::members::for_each(const string_view &membership,
const closure &closure)
const
{
const m::room::state state
{
room
};
const bool present
{
state.present()
};
// joined members optimization. Only possible when seeking
// membership="join" on the present state of the room.
if(membership == "join" && present)
return room::origins::_for_each(room, [this, &closure]
(const string_view &key)
{
const string_view &member
{
std::get<1>(dbs::room_joined_key(key))
};
return closure(member);
});
return this->for_each(membership, [&closure]
(const auto &user_id, const auto &event_idx)
{
return closure(user_id);
});
}
bool
ircd::m::room::members::membership(const event::idx &event_idx,
const string_view &membership)
{
return m::query(std::nothrow, event_idx, "content", [&membership]
(const json::object &content)
{
const json::string &content_membership
{
content["membership"]
};
return content_membership && content_membership == membership;
});
}
ircd::string_view
ircd::m::room::members::membership(const mutable_buffer &out,
const event::idx &event_idx)
{
return m::query(std::nothrow, event_idx, "content", [&out]
(const json::object &content) -> string_view
{
const json::string &content_membership
{
content["membership"]
};
return strlcpy
{
out, content_membership
};
});
}
//
// room::origins
//
ircd::string_view
ircd::m::room::origins::random(const mutable_buffer &buf,
const closure_bool &proffer)
const
{
string_view ret;
const auto closure{[&buf, &proffer, &ret]
(const string_view &origin)
{
ret = { data(buf), copy(buf, origin) };
}};
random(closure, proffer);
return ret;
}
bool
ircd::m::room::origins::random(const closure &view,
const closure_bool &proffer)
const
{
return random(*this, view, proffer);
}
bool
ircd::m::room::origins::random(const origins &origins,
const closure &view,
const closure_bool &proffer)
{
bool ret{false};
const size_t max
{
origins.count()
};
if(unlikely(!max))
return ret;
auto select
{
ssize_t(rand::integer(0, max - 1))
};
const closure_bool closure{[&proffer, &view, &select]
(const string_view &origin)
{
if(select-- > 0)
return true;
// Test if this random selection is "ok" e.g. the callback allows the
// user to test a blacklist for this origin. Skip to next if not.
if(proffer && !proffer(origin))
{
++select;
return true;
}
view(origin);
return false;
}};
const auto iteration{[&origins, &closure, &ret]
{
ret = !origins.for_each(closure);
}};
// Attempt select on first iteration
iteration();
// If nothing was OK between the random int and the end of the iteration
// then start again and pick the first OK.
if(!ret && select >= 0)
iteration();
return ret;
}
bool
ircd::m::room::origins::empty()
const
{
return for_each(closure_bool{[]
(const string_view &)
{
// return false to break and return false.
return false;
}});
}
size_t
ircd::m::room::origins::count()
const
{
size_t ret{0};
for_each([&ret](const string_view &)
{
++ret;
});
return ret;
}
size_t
ircd::m::room::origins::count_error()
const
{
size_t ret{0};
for_each([&ret](const string_view &server)
{
ret += !ircd::empty(server::errmsg(server));
});
return ret;
}
size_t
ircd::m::room::origins::count_online()
const
{
ssize_t ret
{
0 - ssize_t(count_error())
};
for_each([&ret](const string_view &hostport)
{
ret += bool(server::exists(hostport));
});
assert(ret >= 0L);
return std::max(ret, 0L);
}
/// Tests if argument is the only origin in the room.
/// If a zero or more than one origins exist, returns false. If the only origin
/// in the room is the argument origin, returns true.
bool
ircd::m::room::origins::only(const string_view &origin)
const
{
ushort ret{2};
for_each(closure_bool{[&ret, &origin]
(const string_view &origin_) -> bool
{
if(origin == origin_)
ret = 1;
else
ret = 0;
return ret;
}});
return ret == 1;
}
bool
ircd::m::room::origins::has(const string_view &origin)
const
{
db::domain &index
{
dbs::room_joined
};
char querybuf[dbs::ROOM_JOINED_KEY_MAX_SIZE];
const auto query
{
dbs::room_joined_key(querybuf, room.room_id, origin)
};
auto it
{
index.begin(query)
};
if(!it)
return false;
const string_view &key
{
lstrip(it->first, "\0"_sv)
};
const string_view &key_origin
{
std::get<0>(dbs::room_joined_key(key))
};
return key_origin == origin;
}
void
ircd::m::room::origins::for_each(const closure &view)
const
{
for_each(closure_bool{[&view]
(const string_view &origin)
{
view(origin);
return true;
}});
}
bool
ircd::m::room::origins::for_each(const closure_bool &view)
const
{
string_view last;
char lastbuf[rfc1035::NAME_BUFSIZE];
return _for_each(*this, [&last, &lastbuf, &view]
(const string_view &key)
{
const string_view &origin
{
std::get<0>(dbs::room_joined_key(key))
};
if(origin == last)
return true;
if(!view(origin))
return false;
last = { lastbuf, copy(lastbuf, origin) };
return true;
});
}
bool
ircd::m::room::origins::_for_each(const origins &origins,
const closure_bool &view)
{
db::domain &index
{
dbs::room_joined
};
auto it
{
index.begin(origins.room.room_id)
};
for(; bool(it); ++it)
{
const string_view &key
{
lstrip(it->first, "\0"_sv)
};
if(!view(key))
return false;
}
return true;
}
//
// room::aliases
//
size_t
ircd::m::room::aliases::count()
const
{
return count(string_view{});
}
size_t
ircd::m::room::aliases::count(const string_view &server)
const
{
size_t ret(0);
for_each(server, [&ret](const auto &a)
{
++ret;
return true;
});
return ret;
}
bool
ircd::m::room::aliases::has(const alias &alias)
const
{
return !for_each(alias.host(), [&alias]
(const id::room_alias &a)
{
assert(a.host() == alias.host());
return a == alias? false : true; // false to break on found
});
}
bool
ircd::m::room::aliases::for_each(const closure_bool &closure)
const
{
const room::state state
{
room
};
return state.for_each("m.room.aliases", [this, &closure]
(const string_view &type, const string_view &state_key, const event::idx &)
{
return for_each(state_key, closure);
});
}
bool
ircd::m::room::aliases::for_each(const string_view &server,
const closure_bool &closure)
const
{
if(!server)
return for_each(closure);
return for_each(room, server, closure);
}
bool
ircd::m::room::aliases::for_each(const m::room &room,
const string_view &server,
const closure_bool &closure)
{
using prototype = bool (const m::room &, const string_view &, const closure_bool &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::for_each"
};
return call(room, server, closure);
}
//
// room::aliases::cache
//
bool
ircd::m::room::aliases::cache::del(const alias &a)
{
using prototype = bool (const alias &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::del"
};
return call(a);
}
bool
ircd::m::room::aliases::cache::set(const alias &a,
const id &i)
{
using prototype = bool (const alias &, const id &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::set"
};
return call(a, i);
}
bool
ircd::m::room::aliases::cache::fetch(std::nothrow_t,
const alias &a,
const net::hostport &hp)
try
{
fetch(a, hp);
return true;
}
catch(const std::exception &e)
{
thread_local char buf[384];
log::error
{
log, "Failed to fetch room_id for %s from %s :%s",
string_view{a},
string(buf, hp),
e.what(),
};
return false;
}
void
ircd::m::room::aliases::cache::fetch(const alias &a,
const net::hostport &hp)
{
using prototype = void (const alias &, const net::hostport &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::fetch"
};
return call(a, hp);
}
ircd::m::room::id::buf
ircd::m::room::aliases::cache::get(const alias &a)
{
id::buf ret;
get(a, [&ret]
(const id &room_id)
{
ret = room_id;
});
return ret;
}
ircd::m::room::id::buf
ircd::m::room::aliases::cache::get(std::nothrow_t,
const alias &a)
{
id::buf ret;
get(std::nothrow, a, [&ret]
(const id &room_id)
{
ret = room_id;
});
return ret;
}
void
ircd::m::room::aliases::cache::get(const alias &a,
const id::closure &c)
{
if(!get(std::nothrow, a, c))
throw m::NOT_FOUND
{
"Cannot find room_id for %s",
string_view{a}
};
}
bool
ircd::m::room::aliases::cache::get(std::nothrow_t,
const alias &a,
const id::closure &c)
{
using prototype = bool (std::nothrow_t, const alias &, const id::closure &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::get"
};
return call(std::nothrow, a, c);
}
bool
ircd::m::room::aliases::cache::has(const alias &a)
{
using prototype = bool (const alias &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::has"
};
return call(a);
}
bool
ircd::m::room::aliases::cache::for_each(const closure_bool &c)
{
return for_each(string_view{}, c);
}
bool
ircd::m::room::aliases::cache::for_each(const string_view &s,
const closure_bool &c)
{
using prototype = bool (const string_view &, const closure_bool &);
static mods::import<prototype> call
{
"m_room_aliases", "ircd::m::room::aliases::cache::for_each"
};
return call(s, c);
}
//
// room::power
//
decltype(ircd::m::room::power::default_creator_level)
ircd::m::room::power::default_creator_level
{
100
};
decltype(ircd::m::room::power::default_power_level)
ircd::m::room::power::default_power_level
{
50
};
decltype(ircd::m::room::power::default_event_level)
ircd::m::room::power::default_event_level
{
0
};
decltype(ircd::m::room::power::default_user_level)
ircd::m::room::power::default_user_level
{
0
};
ircd::json::object
ircd::m::room::power::default_content(const mutable_buffer &buf,
const m::user::id &creator)
{
return compose_content(buf, [&creator]
(const string_view &key, json::stack::object &object)
{
if(key != "users")
return;
assert(default_creator_level == 100);
json::stack::member
{
object, creator, json::value(default_creator_level)
};
});
}
ircd::json::object
ircd::m::room::power::compose_content(const mutable_buffer &buf,
const compose_closure &closure)
{
json::stack out{buf};
json::stack::object content{out};
assert(default_power_level == 50);
json::stack::member
{
content, "ban", json::value(default_power_level)
};
{
json::stack::object events
{
content, "events"
};
closure("events", events);
}
assert(default_event_level == 0);
json::stack::member
{
content, "events_default", json::value(default_event_level)
};
json::stack::member
{
content, "invite", json::value(default_power_level)
};
json::stack::member
{
content, "kick", json::value(default_power_level)
};
{
json::stack::object notifications
{
content, "notifications"
};
json::stack::member
{
notifications, "room", json::value(default_power_level)
};
closure("notifications", notifications);
}
json::stack::member
{
content, "redact", json::value(default_power_level)
};
json::stack::member
{
content, "state_default", json::value(default_power_level)
};
{
json::stack::object users
{
content, "users"
};
closure("users", users);
}
assert(default_user_level == 0);
json::stack::member
{
content, "users_default", json::value(default_user_level)
};
content.~object();
return json::object{out.completed()};
}
//
// room::power::power
//
ircd::m::room::power::power(const m::room &room)
:power
{
room, room.get(std::nothrow, "m.room.power_levels", "")
}
{
}
ircd::m::room::power::power(const m::room &room,
const event::idx &power_event_idx)
:room
{
room
}
,power_event_idx
{
power_event_idx
}
{
}
ircd::m::room::power::power(const m::event &power_event,
const m::event &create_event)
:power
{
power_event, m::user::id(unquote(json::get<"content"_>(create_event).get("creator")))
}
{
}
ircd::m::room::power::power(const m::event &power_event,
const m::user::id &room_creator_id)
:power
{
json::get<"content"_>(power_event), room_creator_id
}
{
}
ircd::m::room::power::power(const json::object &power_event_content,
const m::user::id &room_creator_id)
:power_event_content
{
power_event_content
}
,room_creator_id
{
room_creator_id
}
{
}
/// "all who attain great power and riches make use of either force or fraud"
///
/// Returns bool for "allow" or "deny"
///
/// Provide the user invoking the power. The return value indicates whether
/// they have the power.
///
/// Provide the property/event_type. There are two usages here: 1. This is a
/// string corresponding to one of the spec top-level properties like "ban"
/// and "redact". In this case, the type and state_key parameters to this
/// function are not used. 2. This string is empty or "events" in which case
/// the type parameter is used to fetch the power threshold for that type.
/// For state events of a type, the state_key must be provided for inspection
/// here as well.
bool
ircd::m::room::power::operator()(const m::user::id &user_id,
const string_view &prop,
const string_view &type,
const string_view &state_key)
const
{
const auto &user_level
{
level_user(user_id)
};
const auto &required_level
{
empty(prop) || prop == "events"?
level_event(type, state_key):
level(prop)
};
return user_level >= required_level;
}
int64_t
ircd::m::room::power::level_user(const m::user::id &user_id)
const try
{
int64_t ret
{
default_user_level
};
const auto closure{[&user_id, &ret]
(const json::object &content)
{
const auto users_default
{
content.get<int64_t>("users_default", default_user_level)
};
const json::object &users
{
content.get("users")
};
ret = users.get<int64_t>(user_id, users_default);
}};
const bool has_power_levels_event
{
view(closure)
};
if(!has_power_levels_event)
{
if(room_creator_id && user_id == room_creator_id)
ret = default_creator_level;
if(room.room_id && creator(room, user_id))
ret = default_creator_level;
}
return ret;
}
catch(const json::error &e)
{
return default_user_level;
}
int64_t
ircd::m::room::power::level_event(const string_view &type)
const try
{
int64_t ret
{
default_event_level
};
const auto closure{[&type, &ret]
(const json::object &content)
{
const auto &events_default
{
content.get<int64_t>("events_default", default_event_level)
};
const json::object &events
{
content.get("events")
};
ret = events.get<int64_t>(type, events_default);
}};
const bool has_power_levels_event
{
view(closure)
};
return ret;
}
catch(const json::error &e)
{
return default_event_level;
}
int64_t
ircd::m::room::power::level_event(const string_view &type,
const string_view &state_key)
const try
{
if(!defined(state_key))
return level_event(type);
int64_t ret
{
default_power_level
};
const auto closure{[&type, &ret]
(const json::object &content)
{
const auto &state_default
{
content.get<int64_t>("state_default", default_power_level)
};
const json::object &events
{
content.get("events")
};
ret = events.get<int64_t>(type, state_default);
}};
const bool has_power_levels_event
{
view(closure)
};
return ret;
}
catch(const json::error &e)
{
return default_power_level;
}
int64_t
ircd::m::room::power::level(const string_view &prop)
const try
{
int64_t ret
{
default_power_level
};
view([&prop, &ret]
(const json::object &content)
{
ret = content.at<int64_t>(prop);
});
return ret;
}
catch(const json::error &e)
{
return default_power_level;
}
size_t
ircd::m::room::power::count_levels()
const
{
size_t ret{0};
for_each([&ret]
(const string_view &, const int64_t &)
{
++ret;
});
return ret;
}
size_t
ircd::m::room::power::count_collections()
const
{
size_t ret{0};
view([&ret]
(const json::object &content)
{
for(const auto &member : content)
ret += json::type(member.second) == json::OBJECT;
});
return ret;
}
size_t
ircd::m::room::power::count(const string_view &prop)
const
{
size_t ret{0};
for_each(prop, [&ret]
(const string_view &, const int64_t &)
{
++ret;
});
return ret;
}
bool
ircd::m::room::power::has_event(const string_view &type)
const try
{
bool ret{false};
view([&type, &ret]
(const json::object &content)
{
const json::object &events
{
content.at("events")
};
const string_view &level
{
unquote(events.at(type))
};
ret = json::type(level) == json::NUMBER;
});
return ret;
}
catch(const json::error &)
{
return false;
}
bool
ircd::m::room::power::has_user(const m::user::id &user_id)
const try
{
bool ret{false};
view([&user_id, &ret]
(const json::object &content)
{
const json::object &users
{
content.at("users")
};
const string_view &level
{
unquote(users.at(user_id))
};
ret = json::type(level) == json::NUMBER;
});
return ret;
}
catch(const json::error &)
{
return false;
}
bool
ircd::m::room::power::has_collection(const string_view &prop)
const
{
bool ret{false};
view([&prop, &ret]
(const json::object &content)
{
const auto &value{content.get(prop)};
if(value && json::type(value) == json::OBJECT)
ret = true;
});
return ret;
}
bool
ircd::m::room::power::has_level(const string_view &prop)
const
{
bool ret{false};
view([&prop, &ret]
(const json::object &content)
{
const auto &value(unquote(content.get(prop)));
if(value && json::type(value) == json::NUMBER)
ret = true;
});
return ret;
}
void
ircd::m::room::power::for_each(const closure &closure)
const
{
for_each(string_view{}, closure);
}
bool
ircd::m::room::power::for_each(const closure_bool &closure)
const
{
return for_each(string_view{}, closure);
}
void
ircd::m::room::power::for_each(const string_view &prop,
const closure &closure)
const
{
for_each(prop, closure_bool{[&closure]
(const string_view &key, const int64_t &level)
{
closure(key, level);
return true;
}});
}
bool
ircd::m::room::power::for_each(const string_view &prop,
const closure_bool &closure)
const
{
bool ret{true};
view([&prop, &closure, &ret]
(const json::object &content)
{
const json::object &collection
{
// This little cmov gimmick sets collection to be the outer object
// itself if no property was given, allowing us to reuse this func
// for all iterations of key -> level mappings.
prop? json::object{content.get(prop)} : content
};
const string_view _collection{collection};
if(prop && (!_collection || json::type(_collection) != json::OBJECT))
return;
for(auto it(begin(collection)); it != end(collection) && ret; ++it)
{
const auto &member(*it);
if(json::type(unquote(member.second)) != json::NUMBER)
continue;
const auto &key
{
unquote(member.first)
};
const auto &val
{
lex_cast<int64_t>(member.second)
};
ret = closure(key, val);
}
});
return ret;
}
bool
ircd::m::room::power::view(const std::function<void (const json::object &)> &closure)
const
{
if(power_event_idx)
if(m::get(std::nothrow, power_event_idx, "content", closure))
return true;
closure(power_event_content);
return !empty(power_event_content);
}
//
// room::stats
//
size_t
__attribute__((noreturn))
ircd::m::room::stats::bytes_total(const m::room &room)
{
throw m::UNSUPPORTED
{
"Not yet implemented."
};
}
size_t
__attribute__((noreturn))
ircd::m::room::stats::bytes_total_compressed(const m::room &room)
{
throw m::UNSUPPORTED
{
"Not yet implemented."
};
}
size_t
ircd::m::room::stats::bytes_json(const m::room &room)
{
size_t ret(0);
for(m::room::events it(room); it; --it)
{
const m::event::idx &event_idx
{
it.event_idx()
};
const byte_view<string_view> key
{
event_idx
};
static const db::gopts gopts
{
db::get::NO_CACHE
};
ret += db::bytes_value(m::dbs::event_json, key, gopts);
}
return ret;
}
size_t
__attribute__((noreturn))
ircd::m::room::stats::bytes_json_compressed(const m::room &room)
{
throw m::UNSUPPORTED
{
"Not yet implemented."
};
}