diff --git a/src/Cache.cpp b/src/Cache.cpp
index 667506c5..8b47c357 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(RoomMember)
Q_DECLARE_METATYPE(mtx::responses::Timeline)
Q_DECLARE_METATYPE(RoomSearchResult)
Q_DECLARE_METATYPE(RoomInfo)
+Q_DECLARE_METATYPE(mtx::responses::QueryKeys)
namespace {
std::unique_ptr<Cache> instance_ = nullptr;
@@ -155,26 +156,7 @@ Cache::Cache(const QString &userId, QObject *parent)
, localUserId_{userId}
{
setup();
- connect(
- this,
- &Cache::updateUserCacheFlag,
- this,
- [this](const std::string &user_id) {
- std::optional<UserCache> cache_ = getUserCache(user_id);
- if (cache_.has_value()) {
- cache_.value().isUpdated = false;
- setUserCache(user_id, cache_.value());
- } else {
- setUserCache(user_id, UserCache{});
- }
- },
- Qt::QueuedConnection);
- connect(
- this,
- &Cache::deleteLeftUsers,
- this,
- [this](const std::string &user_id) { deleteUserCache(user_id); },
- Qt::QueuedConnection);
+ connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection);
}
void
@@ -1017,6 +999,8 @@ Cache::saveState(const mtx::responses::Sync &res)
using namespace mtx::events;
auto user_id = this->localUserId_.toStdString();
+ auto currentBatchToken = nextBatchToken();
+
auto txn = lmdb::txn::begin(env_);
setNextBatchToken(txn, res.next_batch);
@@ -1034,6 +1018,8 @@ Cache::saveState(const mtx::responses::Sync &res)
ev);
}
+ auto userKeyCacheDb = getUserKeysDb(txn);
+
// Save joined rooms
for (const auto &room : res.rooms.join) {
auto statesdb = getStatesDb(txn, room.first);
@@ -1107,7 +1093,8 @@ Cache::saveState(const mtx::responses::Sync &res)
savePresence(txn, res.presence);
- updateUserCache(res.device_lists);
+ markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
+ deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left);
removeLeftRooms(txn, res.rooms.leave);
@@ -3098,126 +3085,246 @@ Cache::statusMessage(const std::string &user_id)
}
void
-to_json(json &j, const UserCache &info)
+to_json(json &j, const UserKeyCache &info)
{
- j["keys"] = info.keys;
- j["isUpdated"] = info.isUpdated;
+ j["device_keys"] = info.device_keys;
+ j["master_keys"] = info.master_keys;
+ j["user_signing_keys"] = info.user_signing_keys;
+ j["self_signing_keys"] = info.self_signing_keys;
+ j["updated_at"] = info.updated_at;
+ j["last_changed"] = info.last_changed;
}
void
-from_json(const json &j, UserCache &info)
+from_json(const json &j, UserKeyCache &info)
{
- info.keys = j.at("keys").get<mtx::responses::QueryKeys>();
- info.isUpdated = j.at("isUpdated").get<bool>();
+ info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
+ info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
+ info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
+ info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{});
+ info.updated_at = j.value("updated_at", "");
+ info.last_changed = j.value("last_changed", "");
}
-std::optional<UserCache>
-Cache::getUserCache(const std::string &user_id)
+std::optional<UserKeyCache>
+Cache::userKeys(const std::string &user_id)
{
- lmdb::val verifiedVal;
+ lmdb::val keys;
- auto txn = lmdb::txn::begin(env_);
- auto db = getUserCacheDb(txn);
- auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
-
- txn.commit();
+ try {
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto db = getUserKeysDb(txn);
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), keys);
- UserCache verified_state;
- if (res) {
- verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
- return verified_state;
- } else {
+ if (res) {
+ return json::parse(std::string_view(keys.data(), keys.size()))
+ .get<UserKeyCache>();
+ } else {
+ return {};
+ }
+ } catch (std::exception &) {
return {};
}
}
-//! be careful when using make sure is_user_verified is not changed
-int
-Cache::setUserCache(const std::string &user_id, const UserCache &body)
+void
+Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
{
auto txn = lmdb::txn::begin(env_);
- auto db = getUserCacheDb(txn);
+ auto db = getUserKeysDb(txn);
- auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
+ std::map<std::string, UserKeyCache> updates;
- txn.commit();
+ for (const auto &[user, keys] : keyQuery.device_keys)
+ updates[user].device_keys = keys;
+ for (const auto &[user, keys] : keyQuery.master_keys)
+ updates[user].master_keys = keys;
+ for (const auto &[user, keys] : keyQuery.user_signing_keys)
+ updates[user].user_signing_keys = keys;
+ for (const auto &[user, keys] : keyQuery.self_signing_keys)
+ updates[user].self_signing_keys = keys;
- return res;
+ for (auto &[user, update] : updates) {
+ lmdb::val oldKeys;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
+
+ if (res) {
+ auto last_changed =
+ json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
+ .get<UserKeyCache>()
+ .last_changed;
+ // skip if we are tracking this and expect it to be up to date with the last
+ // sync token
+ if (!last_changed.empty() && last_changed != sync_token)
+ continue;
+ }
+ lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(update).dump()));
+ }
+
+ txn.commit();
}
void
-Cache::updateUserCache(const mtx::responses::DeviceLists body)
+Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids)
{
- for (std::string user_id : body.changed) {
- emit updateUserCacheFlag(user_id);
- }
-
- for (std::string user_id : body.left) {
- emit deleteLeftUsers(user_id);
- }
+ for (const auto &user_id : user_ids)
+ lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
}
-int
-Cache::deleteUserCache(const std::string &user_id)
+void
+Cache::markUserKeysOutOfDate(lmdb::txn &txn,
+ lmdb::dbi &db,
+ const std::vector<std::string> &user_ids,
+ const std::string &sync_token)
{
- auto txn = lmdb::txn::begin(env_);
- auto db = getUserCacheDb(txn);
- auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
+ mtx::requests::QueryKeys query;
+ query.token = sync_token;
- txn.commit();
+ for (const auto &user : user_ids) {
+ lmdb::val oldKeys;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
- return res;
+ if (!res)
+ continue;
+
+ auto cacheEntry =
+ json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get<UserKeyCache>();
+ cacheEntry.last_changed = sync_token;
+ lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(cacheEntry).dump()));
+
+ query.device_keys[user] = {};
+ }
+
+ if (!query.device_keys.empty())
+ http::client()->query_keys(query,
+ [this, sync_token](const mtx::responses::QueryKeys &keys,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn(
+ "failed to query device keys: {} {}",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit userKeysUpdate(sync_token, keys);
+ });
}
void
-to_json(json &j, const DeviceVerifiedCache &info)
+to_json(json &j, const VerificationCache &info)
{
- j["is_user_verified"] = info.is_user_verified;
- j["cross_verified"] = info.cross_verified;
- j["device_verified"] = info.device_verified;
- j["device_blocked"] = info.device_blocked;
+ j["verified_master_key"] = info.verified_master_key;
+ j["cross_verified"] = info.cross_verified;
+ j["device_verified"] = info.device_verified;
+ j["device_blocked"] = info.device_blocked;
}
void
-from_json(const json &j, DeviceVerifiedCache &info)
+from_json(const json &j, VerificationCache &info)
{
- info.is_user_verified = j.at("is_user_verified");
- info.cross_verified = j.at("cross_verified").get<std::vector<std::string>>();
- info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
- info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
+ info.verified_master_key = j.at("verified_master_key");
+ info.cross_verified = j.at("cross_verified").get<std::vector<std::string>>();
+ info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
+ info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
}
-std::optional<DeviceVerifiedCache>
-Cache::getVerifiedCache(const std::string &user_id)
+std::optional<VerificationCache>
+Cache::verificationStatus(const std::string &user_id)
{
lmdb::val verifiedVal;
auto txn = lmdb::txn::begin(env_);
- auto db = getDeviceVerifiedDb(txn);
- auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
+ auto db = getVerificationDb(txn);
- txn.commit();
-
- DeviceVerifiedCache verified_state;
- if (res) {
- verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
- return verified_state;
- } else {
+ try {
+ VerificationCache verified_state;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
+ if (res) {
+ verified_state =
+ json::parse(std::string_view(verifiedVal.data(), verifiedVal.size()));
+ return verified_state;
+ } else {
+ return {};
+ }
+ } catch (std::exception &) {
return {};
}
}
-int
-Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
+void
+Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
{
+ lmdb::val val;
+
auto txn = lmdb::txn::begin(env_);
- auto db = getDeviceVerifiedDb(txn);
+ auto db = getVerificationDb(txn);
- auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
+ try {
+ VerificationCache verified_state;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val);
+ if (res) {
+ verified_state = json::parse(std::string_view(val.data(), val.size()));
+ }
- txn.commit();
+ for (const auto &device : verified_state.device_verified)
+ if (device == key)
+ return;
- return res;
+ verified_state.device_verified.push_back(key);
+ lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
+ txn.commit();
+ } catch (std::exception &) {
+ }
+}
+
+void
+Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
+{
+ lmdb::val val;
+
+ auto txn = lmdb::txn::begin(env_);
+ auto db = getVerificationDb(txn);
+
+ try {
+ VerificationCache verified_state;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val);
+ if (res) {
+ verified_state = json::parse(std::string_view(val.data(), val.size()));
+ }
+
+ verified_state.device_verified.erase(
+ std::remove(verified_state.device_verified.begin(),
+ verified_state.device_verified.end(),
+ key),
+ verified_state.device_verified.end());
+
+ lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
+ txn.commit();
+ } catch (std::exception &) {
+ }
+}
+
+void
+Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key)
+{
+ lmdb::val val;
+
+ auto txn = lmdb::txn::begin(env_);
+ auto db = getVerificationDb(txn);
+
+ try {
+ VerificationCache verified_state;
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val);
+ if (res) {
+ verified_state = json::parse(std::string_view(val.data(), val.size()));
+ }
+
+ verified_state.verified_master_key = key;
+ lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
+ txn.commit();
+ } catch (std::exception &) {
+ }
}
void
@@ -3401,47 +3508,49 @@ statusMessage(const std::string &user_id)
{
return instance_->statusMessage(user_id);
}
-std::optional<UserCache>
-getUserCache(const std::string &user_id)
-{
- return instance_->getUserCache(user_id);
-}
+//! Load saved data for the display names & avatars.
void
-updateUserCache(const mtx::responses::DeviceLists body)
+populateMembers()
{
- instance_->updateUserCache(body);
+ instance_->populateMembers();
}
-int
-setUserCache(const std::string &user_id, const UserCache &body)
+// user cache stores user keys
+std::optional<UserKeyCache>
+userKeys(const std::string &user_id)
+{
+ return instance_->userKeys(user_id);
+}
+void
+updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
{
- return instance_->setUserCache(user_id, body);
+ instance_->updateUserKeys(sync_token, keyQuery);
}
-int
-deleteUserCache(const std::string &user_id)
+// device & user verification cache
+std::optional<VerificationCache>
+verificationStatus(const std::string &user_id)
{
- return instance_->deleteUserCache(user_id);
+ return instance_->verificationStatus(user_id);
}
-std::optional<DeviceVerifiedCache>
-getVerifiedCache(const std::string &user_id)
+void
+markDeviceVerified(const std::string &user_id, const std::string &key)
{
- return instance_->getVerifiedCache(user_id);
+ instance_->markDeviceVerified(user_id, key);
}
-int
-setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
+void
+markDeviceUnverified(const std::string &user_id, const std::string &key)
{
- return instance_->setVerifiedCache(user_id, body);
+ instance_->markDeviceUnverified(user_id, key);
}
-//! Load saved data for the display names & avatars.
void
-populateMembers()
+markMasterKeyVerified(const std::string &user_id, const std::string &key)
{
- instance_->populateMembers();
+ instance_->markMasterKeyVerified(user_id, key);
}
std::vector<std::string>
|