From 54db9c89ede00bdcc48a64397cb45073b4fc6625 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 20 Sep 2020 23:04:14 +0200 Subject: [PATCH] Simplify outbound session setup Don't send inbound session to self and claim and send all keys at once. --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Cache.cpp | 38 ++--- src/ChatPage.cpp | 11 +- src/Olm.cpp | 6 +- src/timeline/.TimelineModel.cpp.swn | Bin 0 -> 237568 bytes src/timeline/EventStore.cpp | 138 +++++++++--------- src/timeline/TimelineModel.cpp | 214 +++++++++++++--------------- src/timeline/TimelineModel.h | 12 +- 9 files changed, 209 insertions(+), 214 deletions(-) create mode 100644 src/timeline/.TimelineModel.cpp.swn diff --git a/CMakeLists.txt b/CMakeLists.txt index 46d83f67..47f08657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -342,7 +342,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 0665c8baf4af0ce192adb8ca97761b63b681d569 + GIT_TAG f84611f129b46746a4b586acaba54fc31a303bc6 ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index c71a5771..da1b5a37 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "0665c8baf4af0ce192adb8ca97761b63b681d569", + "commit": "f84611f129b46746a4b586acaba54fc31a303bc6", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Cache.cpp b/src/Cache.cpp index 5302218a..07d01819 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -139,24 +139,26 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect(this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional 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::updateUserCacheFlag, + this, + [this](const std::string &user_id) { + std::optional 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); } void diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 31ba38d7..704543b5 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -606,11 +606,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect( this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); - connect(this, - &ChatPage::tryDelayedSyncCb, - this, - [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, - Qt::QueuedConnection); + connect( + this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); connect(this, &ChatPage::newSyncResponse, diff --git a/src/Olm.cpp b/src/Olm.cpp index 9e997801..4f0c5893 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -581,9 +581,11 @@ send_megolm_key_to_device(const std::string &user_id, ->create_room_key_event(UserId(user_id), pks.ed25519, payload) .dump(); + mtx::requests::ClaimKeys claim_keys; + claim_keys.one_time_keys[user_id][device_id] = mtx::crypto::SIGNED_CURVE25519; + http::client()->claim_keys( - user_id, - {device_id}, + claim_keys, [room_key, user_id, device_id, pks](const mtx::responses::ClaimKeys &res, mtx::http::RequestErr err) { if (err) { diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn new file mode 100644 index 0000000000000000000000000000000000000000..9e96570264bc9cbab5e9b87ccf0e1880e0db3eea GIT binary patch literal 237568 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

`E(A&g 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 F{|AftumJ!7 literal 0 HcmV?d00001 diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 66a6d799..af1f7b23 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -32,38 +32,40 @@ EventStore::EventStore(std::string room_id, QObject *) this->last = range->last; } - connect(this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); + connect( + this, + &EventStore::eventFetched, + this, + [this](std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); - connect(this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first) - fetchMore(); - else { - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - } - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + emit beginInsertRows(toExternalIdx(newFirst), + toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + } + }, + Qt::QueuedConnection); connect(this, &EventStore::processPending, this, [this]() { if (!current_txn.empty()) { @@ -128,46 +130,48 @@ EventStore::EventStore(std::string room_id, QObject *) event->data); }); - connect(this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); - connect(this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); + connect( + this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_, event_id); + } + }); - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 8f0e470e..ddd238b9 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -204,11 +204,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { - connect(this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, &TimelineModel::newMessageToSend, @@ -217,17 +218,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj Qt::QueuedConnection); connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect(&events, - &EventStore::dataChanged, - this, - [this](int from, int to) { - nhlog::ui()->debug("data changed {} to {}", - events.size() - to - 1, - events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }, - Qt::QueuedConnection); + connect( + &events, + &EventStore::dataChanged, + this, + [this](int from, int to) { + nhlog::ui()->debug( + "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), + index(events.size() - from - 1, 0)); + }, + Qt::QueuedConnection); connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { int first = events.size() - to; @@ -916,10 +917,20 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: OutboundGroupSessionData session_data; session_data.session_id = session_id; session_data.session_key = session_key; - session_data.message_index = 0; // TODO Update me + session_data.message_index = 0; cache::saveOutboundMegolmSession( room_id, session_data, std::move(outbound_session)); + { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = session_id; + index.sender_key = olm::client()->identity_keys().curve25519; + auto megolm_session = + olm::client()->init_inbound_group_session(session_key); + cache::saveInboundMegolmSession(index, std::move(megolm_session)); + } + const auto members = cache::roomMembers(room_id); nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id); @@ -961,19 +972,23 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: return; } + mtx::requests::ClaimKeys claim_keys; + + // Mapping from user id to a device_id with valid identity keys to the + // generated room_key event used for sharing the megolm session. + std::map> room_key_msgs; + std::map> deviceKeys; + for (const auto &user : res.device_keys) { - // Mapping from a device_id with valid identity keys to the - // generated room_key event used for sharing the megolm session. - std::map room_key_msgs; - std::map deviceKeys; - - room_key_msgs.clear(); - deviceKeys.clear(); - for (const auto &dev : user.second) { const auto user_id = ::UserId(dev.second.user_id); const auto device_id = DeviceId(dev.second.device_id); + if (user_id.get() == + http::client()->user_id().to_string() && + device_id.get() == http::client()->device_id()) + continue; + const auto device_keys = dev.second.keys; const auto curveKey = "curve25519:" + device_id.get(); const auto edKey = "ed25519:" + device_id.get(); @@ -1015,42 +1030,25 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: user_id, pks.ed25519, megolm_payload) .dump(); - room_key_msgs.emplace(device_id, room_key); - deviceKeys.emplace(device_id, pks); + room_key_msgs[user_id].emplace(device_id, room_key); + deviceKeys[user_id].emplace(device_id, pks); + claim_keys.one_time_keys[user.first][device_id] = + mtx::crypto::SIGNED_CURVE25519; + + nhlog::net()->info("{}", device_id.get()); + nhlog::net()->info(" curve25519 {}", pks.curve25519); + nhlog::net()->info(" ed25519 {}", pks.ed25519); } - - std::vector valid_devices; - valid_devices.reserve(room_key_msgs.size()); - for (auto const &d : room_key_msgs) { - valid_devices.push_back(d.first); - - nhlog::net()->info("{}", d.first); - nhlog::net()->info(" curve25519 {}", - deviceKeys.at(d.first).curve25519); - nhlog::net()->info(" ed25519 {}", - deviceKeys.at(d.first).ed25519); - } - - nhlog::net()->info( - "sending claim request for user {} with {} devices", - user.first, - valid_devices.size()); - - http::client()->claim_keys( - user.first, - valid_devices, - std::bind(&TimelineModel::handleClaimedKeys, - this, - keeper, - room_key_msgs, - deviceKeys, - user.first, - std::placeholders::_1, - std::placeholders::_2)); - - // TODO: Wait before sending the next batch of requests. - std::this_thread::sleep_for(std::chrono::milliseconds(500)); } + + http::client()->claim_keys(claim_keys, + std::bind(&TimelineModel::handleClaimedKeys, + this, + keeper, + room_key_msgs, + deviceKeys, + std::placeholders::_1, + std::placeholders::_2)); }); // TODO: Let the user know about the errors. @@ -1068,12 +1066,12 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: } void -TimelineModel::handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_keys, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err) +TimelineModel::handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("claim keys error: {} {} {}", @@ -1083,65 +1081,53 @@ TimelineModel::handleClaimedKeys(std::shared_ptr keeper, return; } - nhlog::net()->debug("claimed keys for {}", user_id); - - if (res.one_time_keys.size() == 0) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - if (res.one_time_keys.find(user_id) == res.one_time_keys.end()) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - auto retrieved_devices = res.one_time_keys.at(user_id); - // Payload with all the to_device message to be sent. - json body; - body["messages"][user_id] = json::object(); + nlohmann::json body; - for (const auto &rd : retrieved_devices) { - const auto device_id = rd.first; - nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); - - // TODO: Verify signatures - auto otk = rd.second.begin()->at("key"); - - if (pks.find(device_id) == pks.end()) { - nhlog::net()->critical("couldn't find public key for device: {}", - device_id); - continue; + for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { + nhlog::net()->debug("claimed keys for {}", user_id); + if (retrieved_devices.size() == 0) { + nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); + return; } - auto id_key = pks.at(device_id).curve25519; - auto s = olm::client()->create_outbound_session(id_key, otk); + for (const auto &rd : retrieved_devices) { + const auto device_id = rd.first; - if (room_keys.find(device_id) == room_keys.end()) { - nhlog::net()->critical("couldn't find m.room_key for device: {}", - device_id); - continue; + nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + + // TODO: Verify signatures + auto otk = rd.second.begin()->at("key"); + + auto id_key = pks.at(user_id).at(device_id).curve25519; + auto s = olm::client()->create_outbound_session(id_key, otk); + + auto device_msg = olm::client()->create_olm_encrypted_content( + s.get(), + room_keys.at(user_id).at(device_id), + pks.at(user_id).at(device_id).curve25519); + + try { + cache::saveOlmSession(id_key, std::move(s)); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", + e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical( + "failed to pickle outbound olm session: {}", e.what()); + } + + body["messages"][user_id][device_id] = device_msg; } - auto device_msg = olm::client()->create_olm_encrypted_content( - s.get(), room_keys.at(device_id), pks.at(device_id).curve25519); - - try { - cache::saveOlmSession(id_key, std::move(s)); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to pickle outbound olm session: {}", - e.what()); - } - - body["messages"][user_id][device_id] = device_msg; + nhlog::net()->info("send_to_device: {}", user_id); } - nhlog::net()->info("send_to_device: {}", user_id); - http::client()->send_to_device( - "m.room.encrypted", body, [keeper](mtx::http::RequestErr err) { + mtx::events::to_string(mtx::events::EventType::RoomEncrypted), + http::client()->generate_txn_id(), + body, + [keeper](mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to send " "send_to_device " diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 390fa1ed..61d00df9 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -285,12 +285,12 @@ signals: private: template void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType); - void handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_key, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err); + void handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err); void readEvent(const std::string &id); void setPaginationInProgress(const bool paginationInProgress);