// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 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. The // full license for this software is available in the LICENSE file. 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 &) noexcept { // 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 &) noexcept { ++ret; }); return ret; } size_t ircd::m::room::origins::count_error() const { size_t ret{0}; for_each([&ret](const string_view &server) { ret += fed::errant(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 &server) { ret += bool(fed::exists(server)); }); 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_) noexcept -> 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 { db::domain &index { dbs::room_joined }; auto it { index.begin(room.room_id) }; size_t repeat{0}; string_view last; char lastbuf[rfc1035::NAME_BUFSIZE]; for(; bool(it); ++it) { const auto &[origin, user_id] { dbs::room_joined_key(it->first) }; // This loop is about presenting unique origin strings to our user // through the callback. Since we're iterating all members in the room // we want to skip members from the same origin after the first member // from that origin. if(likely(origin != last)) { if(!view(origin)) return false; // Save the witnessed origin string in this buffer for the first // member of each origin; also reset the repeat ctr (see below). last = { lastbuf, copy(lastbuf, origin) }; repeat = 0; continue; }; // The threshold determines when to incur the cost of a logarithmic // seek on a new key. Under this threshold we iterate normally which // is a simple pointer-chase to the next record. If this threshold is // low, we would pay the logarithmic cost even if every server only had // one or two members joined to the room, etc. static const size_t repeat_threshold { 6 }; // Conditional branch that determines if we should generate a new key // to skip many members from the same origin. We do this via increment // of the last character of the current origin so the query key is just // past the end of all the member records from the last origin. if(repeat++ > repeat_threshold) { assert(!last.empty()); assert(last.size() < sizeof(lastbuf)); repeat = 0; lastbuf[last.size() - 1]++; char keybuf[dbs::ROOM_JOINED_KEY_MAX_SIZE]; if(!seek(it, dbs::room_joined_key(keybuf, room.room_id, last))) break; } } return true; }