/* * Copyright (C) 2016 Charybdis Development Team * Copyright (C) 2016 Jason Volk * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /////////////////////////////////////////////////////////////////////////////// // // m.h // namespace ircd::m { struct log::log log { "matrix", 'm' }; std::map modules; ircd::net::listener *listener; static void leave_ircd_room(); static void join_ircd_room(); static void bootstrap(); static void init_keys(const json::object &options); static void init_cert(const json::object &options); } const ircd::m::room::id::buf init_room_id { "init", ircd::my_host() }; const ircd::m::user::id::buf ircd_user_id { "ircd", ircd::my_host() //TODO: hostname }; ircd::m::user ircd::m::me { ircd_user_id }; const ircd::m::room::id::buf ircd_room_id { "ircd", ircd::my_host() }; ircd::m::room ircd::m::my_room { ircd_room_id }; const ircd::m::room::id::buf control_room_id { "control", ircd::my_host() }; ircd::m::room ircd::m::control { control_room_id }; bool ircd::m::self::host(const string_view &s) { return s == host(); } ircd::string_view ircd::m::self::host() { return "zemos.net"; //me.user_id.host(); } ircd::m::init::init() try { const json::strung options{json::members { { "name", "Chat Matrix" }, { "host", "0.0.0.0" }, { "port", 8448 }, { "ssl_certificate_file", "/home/jason/.synapse/zemos.net.crt" }, { "ssl_certificate_chain_file", "/home/jason/.synapse/zemos.net.crt" }, { "ssl_tmp_dh_file", "/home/jason/.synapse/cdc.z.tls.dh" }, { "ssl_private_key_file_pem", "/home/jason/.synapse/cdc.z.tls.key" }, { "secret_key_file", "/home/jason/charybdis.sk" }, }}; init_keys(options); init_cert(options); const string_view prefixes[] { "m_", "client_", "key_", "federation_", "media_" }; for(const auto &name : mods::available()) if(startswith_any(name, std::begin(prefixes), std::end(prefixes))) modules.emplace(name, name); if(db::sequence(*event::events) == 0) bootstrap(); modules.emplace("root.so"s, "root.so"s); //TODO: conf obviously listener = new ircd::net::listener{options}; join_ircd_room(); } catch(const m::error &e) { log.critical("%s %s", e.what(), e.content); throw; } ircd::m::init::~init() noexcept try { leave_ircd_room(); delete listener; modules.clear(); } catch(const m::error &e) { log.critical("%s %s", e.what(), e.content); ircd::terminate(); } void ircd::m::join_ircd_room() try { join(my_room, me.user_id); } catch(const m::ALREADY_MEMBER &e) { log.warning("IRCd did not shut down correctly..."); } void ircd::m::leave_ircd_room() { leave(my_room, me.user_id); } namespace ircd::m { static void bootstrap_keys(); } void ircd::m::bootstrap() { assert(event::events); assert(db::sequence(*event::events) == 0); ircd::log::notice ( "This appears to be your first time running IRCd because the events " "database is empty. I will be bootstrapping it with initial events now..." ); create(my_room, me.user_id); create(control, me.user_id); create(user::accounts, me.user_id); create(user::sessions, me.user_id); create(filter::filters, me.user_id); join(user::accounts, me.user_id); bootstrap_keys(); message(control, me.user_id, "Welcome to the control room."); message(control, me.user_id, "I am the daemon. You can talk to me in this room by highlighting me."); } // // dbs // namespace ircd::m::dbs { std::map modules; std::map> databases; void init_modules(); void init_databases(); } ircd::m::dbs::init::init() { init_modules(); init_databases(); ircd::m::event::events = databases.at("events").get(); } ircd::m::dbs::init::~init() noexcept { ircd::m::event::events = nullptr; databases.clear(); modules.clear(); } void ircd::m::dbs::init_databases() { for(const auto &pair : modules) { const auto &name(pair.first); const auto dbname(mods::unpostfixed(name)); const std::string shortname(lstrip(dbname, "db_")); const std::string symname(shortname + "_database"s); databases.emplace(shortname, import_shared { dbname, symname }); } } void ircd::m::dbs::init_modules() { for(const auto &name : mods::available()) if(startswith(name, "db_")) modules.emplace(name, name); } /////////////////////////////////////////////////////////////////////////////// // // m/keys.h // const ircd::m::room::id::buf keys_room_id { "keys", ircd::my_host() }; /// The keys room is where the public key data for each server is stored as /// state indexed by the server name. ircd::m::room ircd::m::keys::room { keys_room_id }; ircd::ed25519::sk ircd::m::self::secret_key {}; ircd::ed25519::pk ircd::m::self::public_key {}; std::string ircd::m::self::public_key_b64 {}; std::string ircd::m::self::public_key_id {}; std::string ircd::m::self::tls_cert_der {}; std::string ircd::m::self::tls_cert_der_sha256_b64 {}; static void ircd::m::init_cert(const json::object &options) { const string_view &cert_file { unquote(options.at("ssl_certificate_file")) }; const auto cert_pem { fs::read(std::string(cert_file)) }; const unique_buffer der_buf { 8_KiB }; const auto cert_der { openssl::cert2d(der_buf, cert_pem) }; const fixed_buffer hash { sha256{cert_der} }; self::tls_cert_der_sha256_b64 = { b64encode_unpadded(hash) }; log.info("Certificate `%s' (PEM: %zu bytes; DER: %zu bytes) sha256b64: %s", cert_file, cert_pem.size(), size(cert_der), self::tls_cert_der_sha256_b64); } static void ircd::m::init_keys(const json::object &options) { const auto &sk_file { unquote(options.at("secret_key_file")) }; self::secret_key = ed25519::sk { std::string{sk_file}, &self::public_key }; self::public_key_b64 = b64encode_unpadded(self::public_key); const fixed_buffer hash { sha256{const_raw_buffer{self::public_key}} }; const auto public_key_hash_b64 { b64encode_unpadded(hash) }; self::public_key_id = fmt::snstringf(BUFSIZE, "ed25519:%s", public_key_hash_b64); log.info("Current key is '%s' and the public key is: %s", self::public_key_id, self::public_key_b64); } static void ircd::m::bootstrap_keys() { create(keys::room, me.user_id); const json::strung verify_keys { json::members {{ string_view{self::public_key_id}, { { "key", self::public_key_b64 } } }} }; keys my_key; json::get<"verify_keys"_>(my_key) = verify_keys; json::get<"server_name"_>(my_key) = my_host(); json::get<"old_verify_keys"_>(my_key) = "{}"; json::get<"valid_until_ts"_>(my_key) = ircd::time() + duration_cast(hours(2160)).count(); const json::members tlsfps { { "sha256", self::tls_cert_der_sha256_b64 } }; const json::value tlsfp[1] { { tlsfps } }; const auto tls_fingerprints{json::strung(json::value { tlsfp, 1 })}; json::get<"tls_fingerprints"_>(my_key) = tls_fingerprints; const auto presig { json::strung(my_key) }; const ed25519::sig sig { self::secret_key.sign(const_raw_buffer{presig}) }; static char signature[256]; const auto signatures{json::strung(json::members { { my_host(), { { string_view{self::public_key_id}, b64encode_unpadded(signature, sig) } }} })}; json::get<"signatures"_>(my_key) = signatures; keys::set(my_key); } void ircd::m::keys::get(const string_view &server_name, const string_view &key_id, const key_closure &closure) try { get(server_name, [&key_id, &closure](const keys &keys) { const json::object vks { at<"verify_keys"_>(keys) }; const json::object vkk { vks.at(key_id) }; // The key is not unquote() because some types of keys may be // more complex than just a string one day; think: RLWE. const string_view &key { vkk.at("key") }; closure(key); }); } catch(const json::not_found &e) { throw m::NOT_FOUND { "Failed to find key '%s' for '%s': %s", key_id, server_name, e.what() }; } void ircd::m::keys::get(const string_view &server_name, const keys_closure &closure) { assert(!server_name.empty()); if(get_local(server_name, closure)) return; if(server_name == my_host()) throw m::NOT_FOUND { "keys for '%s' (that's myself) not found", server_name }; log.debug("Keys for %s not cached; querying network...", server_name); char url[1024]; const auto url_len { fmt::snprintf(url, sizeof(url), "_matrix/key/v2/server/") }; //TODO: XXX const unique_buffer buffer { 8192 }; ircd::parse::buffer pb{mutable_buffer{buffer}}; m::request request{"GET", url, {}, {}}; m::session session{server_name}; const json::object response { session(pb, request) }; const m::keys &keys { response }; if(!verify(keys)) throw m::error { http::UNAUTHORIZED, "M_INVALID_SIGNATURE", "Failed to verify keys for '%s'", server_name }; log.debug("Verified keys from '%s'", server_name); set(keys); closure(keys); } void ircd::m::keys::get(const string_view &server_name, const string_view &key_id, const string_view &query_server, const keys_closure &closure) try { assert(!server_name.empty()); assert(!query_server.empty()); char key_id_buf[1024]; char server_name_buf[1024]; char url[1024]; const auto url_len { fmt::snprintf(url, sizeof(url), "_matrix/key/v2/query/%s/%s/", urlencode(server_name, server_name_buf), urlencode(key_id, key_id_buf)) }; //TODO: XXX const unique_buffer buffer { 8192 }; // Make request and receive response synchronously. // This ircd::ctx will block here fetching. ircd::parse::buffer pb{mutable_buffer{buffer}}; m::request request{"GET", url, {}, {}}; m::session session{server_name}; const json::object response { session(pb, request) }; const json::array &keys { response.at("server_keys") }; log::debug("Fetched %zu candidate keys seeking '%s' for '%s' from '%s' (%s)", keys.count(), empty(key_id)? "*" : key_id, server_name, query_server, string(net::remote(session.server))); bool ret{false}; for(auto it(begin(keys)); it != end(keys); ++it) { const m::keys &keys{*it}; const auto &_server_name { at<"server_name"_>(keys) }; if(!verify(keys)) throw m::error { http::UNAUTHORIZED, "M_INVALID_SIGNATURE", "Failed to verify keys for '%s' from '%s'", _server_name, query_server }; log.debug("Verified keys for '%s' from '%s'", _server_name, query_server); set(keys); const json::object vks{json::get<"verify_keys"_>(keys)}; if(_server_name == server_name) { closure(keys); ret = true; } } if(!ret) throw m::NOT_FOUND { "Failed to get any keys for '%s' from '%s' (got %zu total keys otherwise)", server_name, query_server, keys.count() }; } catch(const json::not_found &e) { throw m::NOT_FOUND { "Failed to find key '%s' for '%s' when querying '%s': %s", key_id, server_name, query_server, e.what() }; } bool ircd::m::keys::get_local(const string_view &server_name, const keys_closure &closure) { const m::vm::query query { { "room_id", keys::room.room_id }, { "type", "ircd.key" }, { "state_key", server_name }, }; const auto have { [&closure](const auto &event) { closure(json::get<"content"_>(event)); return true; } }; return m::vm::test(query, have); } void ircd::m::keys::set(const keys &keys) { const auto &state_key { unquote(at<"server_name"_>(keys)) }; const m::user::id::buf sender { "ircd", unquote(at<"server_name"_>(keys)) }; const json::strung content { keys }; json::iov event; json::iov::push members[] { { event, json::member { "type", "ircd.key" }}, { event, json::member { "state_key", state_key }}, { event, json::member { "sender", sender }}, { event, json::member { "content", content }} }; keys::room.send(event); } /// Verify this key data (with itself). bool ircd::m::keys::verify(const keys &keys) noexcept try { const auto &valid_until_ts { at<"valid_until_ts"_>(keys) }; if(valid_until_ts < ircd::time()) throw ircd::error("Key was valid until %s", timestr(valid_until_ts)); const json::object &verify_keys { at<"verify_keys"_>(keys) }; const string_view &key_id { begin(verify_keys)->first }; const json::object &key { begin(verify_keys)->second }; const ed25519::pk pk { [&key](auto &pk) { b64decode(pk, unquote(key.at("key"))); } }; const json::object &signatures { at<"signatures"_>(keys) }; const string_view &server_name { unquote(at<"server_name"_>(keys)) }; const json::object &server_signatures { signatures.at(server_name) }; const ed25519::sig sig{[&server_signatures, &key_id](auto &sig) { b64decode(sig, unquote(server_signatures.at(key_id))); }}; ///TODO: XXX m::keys copy{keys}; at<"signatures"_>(copy) = string_view{}; const json::strung preimage{copy}; return pk.verify(const_raw_buffer{preimage}, sig); } catch(const std::exception &e) { log.error("key verification for '%s' failed: %s", json::get<"server_name"_>(keys, ""_sv), e.what()); return false; } /////////////////////////////////////////////////////////////////////////////// // // m/filter.h // const ircd::m::room::id::buf filters_room_id { "filters", ircd::my_host() }; ircd::m::room ircd::m::filter::filters { filters_room_id }; ircd::m::filter::filter(const string_view &filter_id, const mutable_buffer &buf) { const m::vm::query query { { "room_id", filters.room_id }, { "type", "ircd.filter" }, { "state_key", filter_id }, }; size_t len{0}; m::vm::test(query, [&buf, &len] (const auto &event) { len = copy(buf, json::get<"content"_>(event)); return true; }); new (this) filter{json::object{buf}}; } size_t ircd::m::filter::size(const string_view &filter_id) { const m::vm::query query { { "room_id", filters.room_id }, { "type", "ircd.filter" }, { "state_key", filter_id }, }; size_t ret{0}; m::vm::test(query, [&ret] (const auto &event) { const string_view content { json::get<"content"_>(event) }; ret = content.size(); return true; }); return ret; } /////////////////////////////////////////////////////////////////////////////// // // m/room.h // // // room::state // ircd::m::room::state::state(const room::id &room_id, const event::id &event_id, const mutable_buffer &buf) { fetch tab { event_id, room_id, buf }; new (this) state{tab}; } ircd::m::room::state::state(fetch &tab) { io::acquire(tab); if(bool(tab.error)) std::rethrow_exception(tab.error); new (this) state{tab.pdus}; } ircd::m::room::state::state(const json::array &pdus) { for(const json::object &pdu : pdus) { const m::event &event{pdu}; json::set(*this, at<"type"_>(event), event); } } // // room // ircd::m::room::room(const id::alias &alias) :room_id{} {} ircd::m::room ircd::m::create(const id::room &room_id, const id::user &creator, const string_view &type) { return create(room_id, creator, init_room_id, type); } ircd::m::room ircd::m::create(const id::room &room_id, const id::user &creator, const id::room &parent, const string_view &type) { json::iov event; json::iov content; const json::iov::push push[] { { event, { "sender", creator }}, { content, { "creator", creator }}, }; const json::iov::add_if _parent { content, !parent.empty() && parent.local() != "init", { "parent", parent } }; const json::iov::add_if _type { content, !type.empty() && type != "room", { "type", type } }; room room { room_id }; room.create(event, content); return room; } void ircd::m::room::create(json::iov &event, json::iov &content) { const json::iov::defaults defaults[] { { event, { "sender", me.user_id }}, { content, { "creator", me.user_id }}, }; const json::strung _content { content }; json::iov::set set[] { { event, { "depth", 1 }}, { event, { "type", "m.room.create" }}, { event, { "state_key", "" }}, { event, { "content", _content }} }; send(event); } void ircd::m::join(const m::room::id &room_id, const m::id::user &user_id) { membership(room_id, user_id, "join"); } void ircd::m::leave(const m::room::id &room_id, const m::id::user &user_id) { membership(room_id, user_id, "leave"); } void ircd::m::membership(const m::id::room &room_id, const m::id::user &user_id, const string_view &membership) { json::iov event; json::iov content; json::iov::push push[] { { event, { "sender", user_id }}, { content, { "membership", membership }}, }; room room { room_id }; room.membership(event, content); } void ircd::m::message(const m::id::room &room_id, const m::id::user &user_id, const string_view &body, const string_view &msgtype) { json::iov event; json::iov content; json::iov::push push[] { { event, { "sender", user_id }}, { content, { "body", body }}, { content, { "msgtype", msgtype }}, }; room room { room_id }; room.message(event, content); } void ircd::m::room::membership(json::iov &event, json::iov &content) { const user::id &user_id { event.at("sender") }; const string_view &membership { content.at("membership") }; if(this->membership(user_id, membership)) throw m::ALREADY_MEMBER { "Member '%s' is already '%s'.", string_view{user_id}, membership }; const json::strung c //TODO: child iov { content }; const json::iov::set _event[] { { event, { "type", "m.room.member" }}, { event, { "state_key", user_id }}, { event, { "membership", membership }}, { event, { "content", string_view{c} }}, }; send(event); } void ircd::m::room::message(json::iov &event, json::iov &content) { const json::strung c //TODO: child iov { content }; const json::iov::set _event[] { { event, { "type", "m.room.message" }}, { event, { "content", string_view{c} }}, }; send(event); } bool ircd::m::room::membership(const m::id::user &user_id, const string_view &membership) const { const vm::query member_event { { "room_id", room_id }, { "type", "m.room.member" }, { "state_key", user_id }, }; if(!membership) return m::vm::test(member_event); const vm::query membership_test{[&membership] (const auto &event) { const json::object &content { json::at<"content"_>(event) }; const auto &existing_membership { unquote(content.at("membership")) }; return membership == existing_membership; }}; return m::vm::test(member_event && membership_test); } /// academic search std::vector ircd::m::room::barren(const int64_t &min_depth) const { return {}; } /// academic search uint64_t ircd::m::room::maxdepth() const { event::id::buf buf; return maxdepth(buf); } /// academic search uint64_t ircd::m::room::maxdepth(event::id::buf &buf) const { const vm::query query { { "room_id", room_id }, }; int64_t depth{0}; vm::for_each(query, [&buf, &depth] (const auto &event) { if(json::get<"depth"_>(event) > depth) { depth = json::get<"depth"_>(event); buf = json::get<"event_id"_>(event); } }); return depth; } ircd::m::event::id::buf ircd::m::room::send(const json::members &event) { size_t i(0); json::iov iov; json::iov::push members[event.size()]; for(const auto &member : event) new (members + i++) json::iov::push(iov, member); return send(iov); } ircd::m::event::id::buf ircd::m::room::send(json::iov &event) { const json::iov::set room_id { event, { "room_id", this->room_id } }; //std::cout << this->room_id << " at " << this->maxdepth() << std::endl; // TODO: XXX // commitment to room here @ exclusive acquisition of depth const json::iov::defaults depth { event, { "depth", int64_t(this->maxdepth()) + 1 } }; return m::vm::commit(event); } std::string ircd::m::pretty(const room::state &state) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 2048); const auto out{[&s] (const string_view &key, const auto &event) { if(!json::get<"event_id"_>(event)) return; s << std::setw(28) << std::right << key << " : " << at<"event_id"_>(event) << " " << json::get<"sender"_>(event) << " " << json::get<"depth"_>(event) << " " << pretty_oneline(event::prev{event}) << std::endl; }}; json::for_each(state, out); resizebuf(s, ret); return ret; } std::string ircd::m::pretty_oneline(const room::state &state) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 1024); const auto out{[&s] (const string_view &key, const auto &event) { if(!json::get<"event_id"_>(event)) return; s << key << " "; }}; json::for_each(state, out); resizebuf(s, ret); return ret; } /////////////////////////////////////////////////////////////////////////////// // // m/user.h // const ircd::m::room::id::buf accounts_room_id { "accounts", ircd::my_host() }; ircd::m::room ircd::m::user::accounts { accounts_room_id }; const ircd::m::room::id::buf sessions_room_id { "sessions", ircd::my_host() }; ircd::m::room ircd::m::user::sessions { sessions_room_id }; /// Register the user by joining them to the accounts room. /// /// The content of the join event may store keys including the registration /// options. Once this call completes the join was successful and the user is /// registered, otherwise throws. void ircd::m::user::activate(const json::members &contents) try { json::iov event; json::iov content; json::iov::push push[] { { event, { "sender", user_id }}, { content, { "membership", "join" }}, }; accounts.membership(event, content); control.membership(event, content); } catch(const m::ALREADY_MEMBER &e) { throw m::error { http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." }; } void ircd::m::user::deactivate(const json::members &contents) { json::iov event; json::iov content; json::iov::push push[] { { event, { "sender", user_id }}, { content, { "membership", "leave" }}, }; accounts.membership(event, content); } void ircd::m::user::password(const string_view &password) try { json::iov event; json::iov::push members[] { { event, { "type", "ircd.password" }}, { event, { "state_key", user_id }}, { event, { "sender", user_id }}, }; char b64[64]; uint8_t hash[32]; sha256{hash, const_buffer{password}}; const auto digest{b64encode_unpadded(b64, hash)}; json::iov::push content{event, { "content", json::members { { "sha256", digest } }, }}; accounts.send(event); } catch(const m::ALREADY_MEMBER &e) { throw m::error { http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." }; } bool ircd::m::user::is_password(const string_view &supplied_password) const { const vm::query member_event { { "room_id", accounts.room_id }, { "type", "ircd.password" }, { "state_key", user_id }, }; char b64[64]; uint8_t hash[32]; sha256{hash, const_buffer{supplied_password}}; const auto supplied_hash{b64encode_unpadded(b64, hash)}; const vm::query correct_password{[&supplied_hash] (const auto &event) { const json::object &content { json::at<"content"_>(event) }; const auto &correct_hash { unquote(content.at("sha256")) }; return supplied_hash == correct_hash; }}; const auto query { member_event && correct_password }; return m::vm::test(member_event && correct_password); } bool ircd::m::user::is_active() const { return accounts.membership(user_id); } /////////////////////////////////////////////////////////////////////////////// // // m/event.h // ircd::database * ircd::m::event::events {}; ircd::m::event::event(const id &id, const mutable_buffer &buf) { fetch tab { id, buf }; new (this) event{tab}; } ircd::m::event::event(fetch &tab) { io::acquire(tab); if(bool(tab.error)) std::rethrow_exception(tab.error); new (this) super_type{tab.pdu}; } ircd::m::event::temporality ircd::m::temporality(const event &event, const int64_t &rel) { const auto &depth { json::get<"depth"_>(event) }; return depth > rel? event::temporality::FUTURE: depth == rel? event::temporality::PRESENT: event::temporality::PAST; } ircd::m::event::lineage ircd::m::lineage(const event &event) { const json::array prev[] { json::get<"prev_events"_>(event), json::get<"auth_events"_>(event), json::get<"prev_state"_>(event), }; const auto count{std::accumulate(begin(prev), end(prev), size_t(0), [] (auto ret, const auto &array) { return ret += array.count(); })}; return count > 1? event::lineage::MERGE: count == 1? event::lineage::FORWARD: event::lineage::ROOT; } ircd::string_view ircd::m::reflect(const event::lineage &lineage) { switch(lineage) { case event::lineage::MERGE: return "MERGE"; case event::lineage::FORWARD: return "FORWARD"; case event::lineage::ROOT: return "ROOT"; } return "?????"; } ircd::string_view ircd::m::reflect(const event::temporality &temporality) { switch(temporality) { case event::temporality::FUTURE: return "FUTURE"; case event::temporality::PRESENT: return "PRESENT"; case event::temporality::PAST: return "PAST"; } return "?????"; } size_t ircd::m::degree(const event &event) { return degree(event::prev{event}); } size_t ircd::m::degree(const event::prev &prev) { size_t ret{0}; json::for_each(prev, [&ret] (const auto &, const json::array &prevs) { ret += prevs.count(); }); return ret; } size_t ircd::m::count(const event::prev &prev) { size_t ret{0}; m::for_each(prev, [&ret](const event::id &event_id) { ++ret; }); return ret; } void ircd::m::for_each(const event::prev &prev, const std::function &closure) { json::for_each(prev, [&closure] (const auto &key, const json::array &prevs) { for(const json::array &prev : prevs) { const event::id &id{unquote(prev[0])}; closure(id); } }); } std::string ircd::m::pretty(const event::prev &prev) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 2048); const auto out{[&s] (const string_view &key, auto&& val) { if(json::defined(val)) s << key << ": " << val << std::endl; }}; const auto &auth_events{json::get<"auth_events"_>(prev)}; for(const json::array auth_event : auth_events) out("auth_event", unquote(auth_event[0])); const auto &prev_states{json::get<"prev_state"_>(prev)}; for(const json::array prev_state : prev_states) out("prev_state", unquote(prev_state[0])); const auto &prev_events{json::get<"prev_events"_>(prev)}; for(const json::array prev_event : prev_events) out("prev_event", unquote(prev_event[0])); resizebuf(s, ret); return ret; } std::string ircd::m::pretty_oneline(const event::prev &prev) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 1024); const auto &auth_events{json::get<"auth_events"_>(prev)}; s << "A[ "; for(const json::array auth_event : auth_events) s << unquote(auth_event[0]) << " "; s << "] "; const auto &prev_states{json::get<"prev_state"_>(prev)}; s << "S[ "; for(const json::array prev_state : prev_states) s << unquote(prev_state[0]) << " "; s << "] "; const auto &prev_events{json::get<"prev_events"_>(prev)}; s << "E[ "; for(const json::array prev_event : prev_events) s << unquote(prev_event[0]) << " "; s << "] "; resizebuf(s, ret); return ret; } std::string ircd::m::pretty(const event &event) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 2048); const auto out{[&s] (const string_view &key, auto&& val) { if(json::defined(val)) s << std::setw(16) << std::right << key << ": " << val << std::endl; }}; const string_view top_keys[] { "origin", "event_id", "room_id", "sender", "type", "depth", "state_key", "membership", }; json::for_each(event, top_keys, out); const auto &hashes{json::get<"hashes"_>(event)}; for(const auto &hash : hashes) { s << std::setw(16) << std::right << "[hash]" << ": " << hash.first //<< " " //<< hash.second << std::endl; } const auto &signatures{json::get<"signatures"_>(event)}; for(const auto &signature : signatures) { s << std::setw(16) << std::right << "[signature]" << ": " << signature.first << " "; for(const auto &key : json::object{signature.second}) s << key.first << " "; s << std::endl; } const json::object &contents{json::get<"content"_>(event)}; if(!contents.empty()) { s << std::setw(16) << std::right << "[content]" << ": "; for(const auto &content : contents) s << content.first << ", "; s << std::endl; } const auto &auth_events{json::get<"auth_events"_>(event)}; for(const json::array auth_event : auth_events) out("[auth_event]", unquote(auth_event[0])); const auto &prev_states{json::get<"prev_state"_>(event)}; for(const json::array prev_state : prev_states) out("[prev_state]", unquote(prev_state[0])); const auto &prev_events{json::get<"prev_events"_>(event)}; for(const json::array prev_event : prev_events) out("[prev_event]", unquote(prev_event[0])); resizebuf(s, ret); return ret; } std::string ircd::m::pretty_oneline(const event &event) { std::string ret; std::stringstream s; pubsetbuf(s, ret, 1024); const auto out{[&s] (const string_view &key, auto&& val) { if(defined(val)) s << val << " "; else s << "* "; }}; const string_view top_keys[] { "origin", "event_id", "room_id", "sender", "depth", }; s << ':'; json::for_each(event, top_keys, out); const auto &auth_events{json::get<"auth_events"_>(event)}; s << "pa:" << auth_events.count() << " "; const auto &prev_states{json::get<"prev_state"_>(event)}; s << "ps:" << prev_states.count() << " "; const auto &prev_events{json::get<"prev_events"_>(event)}; s << "pe:" << prev_events.count() << " "; const auto &hashes{json::get<"hashes"_>(event)}; s << "[ "; for(const auto &hash : hashes) s << hash.first << " "; s << "] "; const auto &signatures{json::get<"signatures"_>(event)}; s << "[ "; for(const auto &signature : signatures) { s << signature.first << "[ "; for(const auto &key : json::object{signature.second}) s << key.first << " "; s << "] "; } s << "] "; out("type", json::get<"type"_>(event)); const auto &state_key { json::get<"state_key"_>(event) }; if(defined(state_key) && empty(state_key)) s << "\"\"" << " "; else if(defined(state_key)) s << state_key << " "; else s << "*" << " "; const json::object &contents{json::get<"content"_>(event)}; if(!contents.empty()) { s << "+" << string_view{contents}.size() << " bytes :"; for(const auto &content : contents) s << content.first << " "; } resizebuf(s, ret); return ret; }