mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Migrate olm sessions to be stored in one database instead of thousands
This commit is contained in:
parent
724e38e673
commit
d45dc6c77b
3 changed files with 91 additions and 27 deletions
105
src/Cache.cpp
105
src/Cache.cpp
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
//! Should be changed when a breaking change occurs in the cache format.
|
//! Should be changed when a breaking change occurs in the cache format.
|
||||||
//! This will reset client's data.
|
//! This will reset client's data.
|
||||||
static const std::string CURRENT_CACHE_FORMAT_VERSION{"2023.03.12"};
|
static const std::string CURRENT_CACHE_FORMAT_VERSION{"2023.10.22"};
|
||||||
|
|
||||||
//! Keys used for the DB
|
//! Keys used for the DB
|
||||||
static const std::string_view NEXT_BATCH_KEY("next_batch");
|
static const std::string_view NEXT_BATCH_KEY("next_batch");
|
||||||
|
@ -91,6 +91,8 @@ static constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
|
||||||
static constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
static constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
||||||
//! MegolmSessionIndex -> session data about which devices have access to this
|
//! MegolmSessionIndex -> session data about which devices have access to this
|
||||||
static constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db");
|
static constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db");
|
||||||
|
//! Curve25519 key to session_id and json encoded olm session, separated by null. Dupsorted.
|
||||||
|
static constexpr auto OLM_SESSIONS_DB("olm_sessions.v3");
|
||||||
|
|
||||||
//! flag to be set, when the db should be compacted on startup
|
//! flag to be set, when the db should be compacted on startup
|
||||||
bool needsCompact = false;
|
bool needsCompact = false;
|
||||||
|
@ -98,6 +100,21 @@ bool needsCompact = false;
|
||||||
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
||||||
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
combineOlmSessionKeyFromCurveAndSessionId(std::string_view curve25519, std::string_view session_id)
|
||||||
|
{
|
||||||
|
std::string combined(curve25519.size() + 1 + session_id.size(), '\0');
|
||||||
|
combined.replace(0, curve25519.size(), curve25519);
|
||||||
|
combined.replace(curve25519.size() + 1, session_id.size(), session_id);
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
static std::pair<std::string_view, std::string_view>
|
||||||
|
splitCurve25519AndOlmSessionId(std::string_view input)
|
||||||
|
{
|
||||||
|
auto separator = input.find('\0');
|
||||||
|
return std::pair(input.substr(0, separator), input.substr(separator + 1));
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::unique_ptr<Cache> instance_ = nullptr;
|
std::unique_ptr<Cache> instance_ = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -412,6 +429,8 @@ Cache::setup()
|
||||||
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||||
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
||||||
|
|
||||||
|
olmSessionDb_ = lmdb::dbi::open(txn, OLM_SESSIONS_DB, MDB_CREATE);
|
||||||
|
|
||||||
// What rooms are encrypted
|
// What rooms are encrypted
|
||||||
encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
|
encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
|
||||||
eventExpiryBgJob_ = lmdb::dbi::open(txn, EVENT_EXPIRATION_BG_JOB_DB, MDB_CREATE);
|
eventExpiryBgJob_ = lmdb::dbi::open(txn, EVENT_EXPIRATION_BG_JOB_DB, MDB_CREATE);
|
||||||
|
@ -1075,8 +1094,6 @@ Cache::saveOlmSessions(std::vector<std::pair<std::string, mtx::crypto::OlmSessio
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
for (const auto &[curve25519, session] : sessions) {
|
for (const auto &[curve25519, session] : sessions) {
|
||||||
auto db = getOlmSessionsDb(txn, curve25519);
|
|
||||||
|
|
||||||
const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_);
|
const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_);
|
||||||
const auto session_id = mtx::crypto::session_id(session.get());
|
const auto session_id = mtx::crypto::session_id(session.get());
|
||||||
|
|
||||||
|
@ -1084,7 +1101,9 @@ Cache::saveOlmSessions(std::vector<std::pair<std::string, mtx::crypto::OlmSessio
|
||||||
stored_session.pickled_session = pickled;
|
stored_session.pickled_session = pickled;
|
||||||
stored_session.last_message_ts = timestamp;
|
stored_session.last_message_ts = timestamp;
|
||||||
|
|
||||||
db.put(txn, session_id, nlohmann::json(stored_session).dump());
|
olmSessionDb_.put(txn,
|
||||||
|
combineOlmSessionKeyFromCurveAndSessionId(curve25519, session_id),
|
||||||
|
nlohmann::json(stored_session).dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
@ -1098,7 +1117,6 @@ Cache::saveOlmSession(const std::string &curve25519,
|
||||||
using namespace mtx::crypto;
|
using namespace mtx::crypto;
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
auto db = getOlmSessionsDb(txn, curve25519);
|
|
||||||
|
|
||||||
const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_);
|
const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_);
|
||||||
const auto session_id = mtx::crypto::session_id(session.get());
|
const auto session_id = mtx::crypto::session_id(session.get());
|
||||||
|
@ -1107,7 +1125,9 @@ Cache::saveOlmSession(const std::string &curve25519,
|
||||||
stored_session.pickled_session = pickled;
|
stored_session.pickled_session = pickled;
|
||||||
stored_session.last_message_ts = timestamp;
|
stored_session.last_message_ts = timestamp;
|
||||||
|
|
||||||
db.put(txn, session_id, nlohmann::json(stored_session).dump());
|
olmSessionDb_.put(txn,
|
||||||
|
combineOlmSessionKeyFromCurveAndSessionId(curve25519, session_id),
|
||||||
|
nlohmann::json(stored_session).dump());
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
@ -1119,10 +1139,10 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto txn = ro_txn(env_);
|
auto txn = ro_txn(env_);
|
||||||
auto db = getOlmSessionsDb(txn, curve25519);
|
|
||||||
|
|
||||||
std::string_view pickled;
|
std::string_view pickled;
|
||||||
bool found = db.get(txn, session_id, pickled);
|
bool found = olmSessionDb_.get(
|
||||||
|
txn, combineOlmSessionKeyFromCurveAndSessionId(curve25519, session_id), pickled);
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
auto data = nlohmann::json::parse(pickled).get<StoredOlmSession>();
|
auto data = nlohmann::json::parse(pickled).get<StoredOlmSession>();
|
||||||
|
@ -1141,14 +1161,20 @@ Cache::getLatestOlmSession(const std::string &curve25519)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto txn = ro_txn(env_);
|
auto txn = ro_txn(env_);
|
||||||
auto db = getOlmSessionsDb(txn, curve25519);
|
|
||||||
|
|
||||||
std::string_view session_id, pickled_session;
|
std::string_view key = curve25519, pickled_session;
|
||||||
|
|
||||||
std::optional<StoredOlmSession> currentNewest;
|
std::optional<StoredOlmSession> currentNewest;
|
||||||
|
|
||||||
auto cursor = lmdb::cursor::open(txn, db);
|
auto cursor = lmdb::cursor::open(txn, olmSessionDb_);
|
||||||
while (cursor.get(session_id, pickled_session, MDB_NEXT)) {
|
bool first = true;
|
||||||
|
while (cursor.get(key, pickled_session, first ? MDB_SET_RANGE : MDB_NEXT)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
auto storedCurve = splitCurve25519AndOlmSessionId(key).first;
|
||||||
|
if (storedCurve != curve25519)
|
||||||
|
break;
|
||||||
|
|
||||||
auto data = nlohmann::json::parse(pickled_session).get<StoredOlmSession>();
|
auto data = nlohmann::json::parse(pickled_session).get<StoredOlmSession>();
|
||||||
if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
|
if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts)
|
||||||
currentNewest = data;
|
currentNewest = data;
|
||||||
|
@ -1170,14 +1196,21 @@ Cache::getOlmSessions(const std::string &curve25519)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto txn = ro_txn(env_);
|
auto txn = ro_txn(env_);
|
||||||
auto db = getOlmSessionsDb(txn, curve25519);
|
|
||||||
|
|
||||||
std::string_view session_id, unused;
|
std::string_view key = curve25519, value;
|
||||||
std::vector<std::string> res;
|
std::vector<std::string> res;
|
||||||
|
|
||||||
auto cursor = lmdb::cursor::open(txn, db);
|
auto cursor = lmdb::cursor::open(txn, olmSessionDb_);
|
||||||
while (cursor.get(session_id, unused, MDB_NEXT))
|
|
||||||
|
bool first = true;
|
||||||
|
while (cursor.get(key, value, first ? MDB_SET_RANGE : MDB_NEXT)) {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
auto [storedCurve, session_id] = splitCurve25519AndOlmSessionId(key);
|
||||||
|
if (storedCurve != curve25519)
|
||||||
|
break;
|
||||||
res.emplace_back(session_id);
|
res.emplace_back(session_id);
|
||||||
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -1687,6 +1720,46 @@ Cache::runMigrations()
|
||||||
nhlog::db()->info("Successfully updated states key database format.");
|
nhlog::db()->info("Successfully updated states key database format.");
|
||||||
return true;
|
return true;
|
||||||
}},
|
}},
|
||||||
|
{"2023.10.22",
|
||||||
|
[this]() {
|
||||||
|
// migrate olm sessions to a single db
|
||||||
|
try {
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr);
|
||||||
|
auto mainDb = lmdb::dbi::open(txn);
|
||||||
|
auto dbNames = lmdb::cursor::open(txn, mainDb);
|
||||||
|
|
||||||
|
std::string_view dbName;
|
||||||
|
while (dbNames.get(dbName, MDB_NEXT)) {
|
||||||
|
if (!dbName.starts_with("olm_sessions.v2/"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto curveKey = dbName;
|
||||||
|
curveKey.remove_prefix(std::string_view("olm_sessions.v2/").size());
|
||||||
|
|
||||||
|
auto oldDb = lmdb::dbi::open(txn, std::string(dbName).c_str());
|
||||||
|
auto olmCursor = lmdb::cursor::open(txn, oldDb);
|
||||||
|
|
||||||
|
std::string_view session_id, json;
|
||||||
|
while (olmCursor.get(session_id, json, MDB_NEXT)) {
|
||||||
|
olmSessionDb_.put(
|
||||||
|
txn,
|
||||||
|
combineOlmSessionKeyFromCurveAndSessionId(curveKey, session_id),
|
||||||
|
json);
|
||||||
|
}
|
||||||
|
|
||||||
|
oldDb.drop(txn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
nhlog::db()->critical("Failed to convert olm sessions database in migration! {}",
|
||||||
|
e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nhlog::db()->info("Successfully updated olm sessions database format.");
|
||||||
|
return true;
|
||||||
|
}},
|
||||||
};
|
};
|
||||||
|
|
||||||
nhlog::db()->info("Running migrations, this may take a while!");
|
nhlog::db()->info("Running migrations, this may take a while!");
|
||||||
|
|
|
@ -674,16 +674,6 @@ private:
|
||||||
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
|
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Retrieves or creates the database that stores the open OLM sessions between our device
|
|
||||||
//! and the given curve25519 key which represents another device.
|
|
||||||
//!
|
|
||||||
//! Each entry is a map from the session_id to the pickled representation of the session.
|
|
||||||
lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
|
|
||||||
{
|
|
||||||
return lmdb::dbi::open(
|
|
||||||
txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
|
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
|
||||||
{
|
{
|
||||||
if (!event.content.display_name.empty())
|
if (!event.content.display_name.empty())
|
||||||
|
@ -713,6 +703,7 @@ private:
|
||||||
lmdb::dbi inboundMegolmSessionDb_;
|
lmdb::dbi inboundMegolmSessionDb_;
|
||||||
lmdb::dbi outboundMegolmSessionDb_;
|
lmdb::dbi outboundMegolmSessionDb_;
|
||||||
lmdb::dbi megolmSessionDataDb_;
|
lmdb::dbi megolmSessionDataDb_;
|
||||||
|
lmdb::dbi olmSessionDb_;
|
||||||
|
|
||||||
lmdb::dbi encryptedRooms_;
|
lmdb::dbi encryptedRooms_;
|
||||||
|
|
||||||
|
|
|
@ -719,7 +719,7 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
|
||||||
nhlog::crypto()->debug("Updated olm session: {}",
|
nhlog::crypto()->debug("Updated olm session: {}",
|
||||||
mtx::crypto::session_id(session->get()));
|
mtx::crypto::session_id(session->get()));
|
||||||
cache::saveOlmSession(
|
cache::saveOlmSession(
|
||||||
id, std::move(session.value()), QDateTime::currentMSecsSinceEpoch());
|
sender_key, std::move(session.value()), QDateTime::currentMSecsSinceEpoch());
|
||||||
} catch (const mtx::crypto::olm_exception &e) {
|
} catch (const mtx::crypto::olm_exception &e) {
|
||||||
nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}",
|
nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}",
|
||||||
msg.type,
|
msg.type,
|
||||||
|
|
Loading…
Reference in a new issue