mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 09:30:47 +03:00
Add support for displaying decrypted messages
This commit is contained in:
parent
b89257a34b
commit
626c680911
19 changed files with 869 additions and 99 deletions
|
@ -184,6 +184,7 @@ set(SRC_FILES
|
|||
src/MainWindow.cc
|
||||
src/MatrixClient.cc
|
||||
src/QuickSwitcher.cc
|
||||
src/Olm.cpp
|
||||
src/RegisterPage.cc
|
||||
src/RoomInfoListItem.cc
|
||||
src/RoomList.cc
|
||||
|
|
2
deps/CMakeLists.txt
vendored
2
deps/CMakeLists.txt
vendored
|
@ -40,7 +40,7 @@ set(MATRIX_STRUCTS_URL https://github.com/mujx/matrix-structs)
|
|||
set(MATRIX_STRUCTS_TAG eeb7373729a1618e2b3838407863342b88b8a0de)
|
||||
|
||||
set(MTXCLIENT_URL https://github.com/mujx/mtxclient)
|
||||
set(MTXCLIENT_TAG 57f56d1fe73989dbe041a7ac0a28bf2e3286bf98)
|
||||
set(MTXCLIENT_TAG 26aad7088b9532808ded9919d55f58711c0138e3)
|
||||
|
||||
set(OLM_URL https://git.matrix.org/git/olm.git)
|
||||
set(OLM_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae)
|
||||
|
|
131
include/Cache.h
131
include/Cache.h
|
@ -17,13 +17,16 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QImage>
|
||||
|
||||
#include <json.hpp>
|
||||
#include <lmdb++.h>
|
||||
#include <mtx/events/join_rules.hpp>
|
||||
#include <mtx/responses.hpp>
|
||||
#include <mtxclient/crypto/client.hpp>
|
||||
#include <mutex>
|
||||
|
||||
using mtx::events::state::JoinRule;
|
||||
|
||||
struct RoomMember
|
||||
|
@ -140,6 +143,83 @@ struct RoomSearchResult
|
|||
Q_DECLARE_METATYPE(RoomSearchResult)
|
||||
Q_DECLARE_METATYPE(RoomInfo)
|
||||
|
||||
// Extra information associated with an outbound megolm session.
|
||||
struct OutboundGroupSessionData
|
||||
{
|
||||
std::string session_id;
|
||||
std::string session_key;
|
||||
uint64_t message_index = 0;
|
||||
};
|
||||
|
||||
inline void
|
||||
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
|
||||
{
|
||||
obj["session_id"] = msg.session_id;
|
||||
obj["session_key"] = msg.session_key;
|
||||
obj["message_index"] = msg.message_index;
|
||||
}
|
||||
|
||||
inline void
|
||||
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
|
||||
{
|
||||
msg.session_id = obj.at("session_id");
|
||||
msg.session_key = obj.at("session_key");
|
||||
msg.message_index = obj.at("message_index");
|
||||
}
|
||||
|
||||
struct OutboundGroupSessionDataRef
|
||||
{
|
||||
OlmOutboundGroupSession *session;
|
||||
OutboundGroupSessionData data;
|
||||
};
|
||||
|
||||
struct DevicePublicKeys
|
||||
{
|
||||
std::string ed25519;
|
||||
std::string curve25519;
|
||||
};
|
||||
|
||||
inline void
|
||||
to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
|
||||
{
|
||||
obj["ed25519"] = msg.ed25519;
|
||||
obj["curve25519"] = msg.curve25519;
|
||||
}
|
||||
|
||||
inline void
|
||||
from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
|
||||
{
|
||||
msg.ed25519 = obj.at("ed25519");
|
||||
msg.curve25519 = obj.at("curve25519");
|
||||
}
|
||||
|
||||
//! Represents a unique megolm session identifier.
|
||||
struct MegolmSessionIndex
|
||||
{
|
||||
//! The room in which this session exists.
|
||||
std::string room_id;
|
||||
//! The session_id of the megolm session.
|
||||
std::string session_id;
|
||||
//! The curve25519 public key of the sender.
|
||||
std::string sender_key;
|
||||
|
||||
//! Representation to be used in a hash map.
|
||||
std::string to_hash() const { return room_id + session_id + sender_key; }
|
||||
};
|
||||
|
||||
struct OlmSessionStorage
|
||||
{
|
||||
std::map<std::string, mtx::crypto::OlmSessionPtr> outbound_sessions;
|
||||
std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
|
||||
std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
|
||||
std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
|
||||
|
||||
// Guards for accessing critical data.
|
||||
std::mutex outbound_mtx;
|
||||
std::mutex group_outbound_mtx;
|
||||
std::mutex group_inbound_mtx;
|
||||
};
|
||||
|
||||
class Cache : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -260,6 +340,48 @@ public:
|
|||
//! Check if we have sent a desktop notification for the given event id.
|
||||
bool isNotificationSent(const std::string &event_id);
|
||||
|
||||
//! Mark a room that uses e2e encryption.
|
||||
void setEncryptedRoom(const std::string &room_id);
|
||||
//! Save the public keys for a device.
|
||||
void saveDeviceKeys(const std::string &device_id);
|
||||
void getDeviceKeys(const std::string &device_id);
|
||||
|
||||
//! Save the device list for a user.
|
||||
void setDeviceList(const std::string &user_id, const std::vector<std::string> &devices);
|
||||
std::vector<std::string> getDeviceList(const std::string &user_id);
|
||||
|
||||
//
|
||||
// Outbound Megolm Sessions
|
||||
//
|
||||
void saveOutboundMegolmSession(const MegolmSessionIndex &index,
|
||||
const OutboundGroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr session);
|
||||
OutboundGroupSessionDataRef getOutboundMegolmSession(const MegolmSessionIndex &index);
|
||||
bool outboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
|
||||
|
||||
//
|
||||
// Inbound Megolm Sessions
|
||||
//
|
||||
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session);
|
||||
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
|
||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
|
||||
|
||||
//
|
||||
// Outbound Olm Sessions
|
||||
//
|
||||
void saveOutboundOlmSession(const std::string &curve25519,
|
||||
mtx::crypto::OlmSessionPtr session);
|
||||
OlmSession *getOutboundOlmSession(const std::string &curve25519);
|
||||
bool outboundOlmSessionsExists(const std::string &curve25519) noexcept;
|
||||
|
||||
void saveOlmAccount(const std::string &pickled);
|
||||
std::string restoreOlmAccount();
|
||||
|
||||
void restoreSessions();
|
||||
|
||||
OlmSessionStorage session_storage;
|
||||
|
||||
private:
|
||||
//! Save an invited room.
|
||||
void saveInvite(lmdb::txn &txn,
|
||||
|
@ -451,6 +573,13 @@ private:
|
|||
lmdb::dbi readReceiptsDb_;
|
||||
lmdb::dbi notificationsDb_;
|
||||
|
||||
lmdb::dbi devicesDb_;
|
||||
lmdb::dbi deviceKeysDb_;
|
||||
|
||||
lmdb::dbi inboundMegolmSessionDb_;
|
||||
lmdb::dbi outboundMegolmSessionDb_;
|
||||
lmdb::dbi outboundOlmSessionDb_;
|
||||
|
||||
QString localUserId_;
|
||||
QString cacheDirectory_;
|
||||
};
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
#include "Cache.h"
|
||||
#include "CommunitiesList.h"
|
||||
#include "Community.h"
|
||||
|
||||
#include <mtx.hpp>
|
||||
#include "MatrixClient.h"
|
||||
|
||||
class OverlayModal;
|
||||
class QuickSwitcher;
|
||||
|
@ -119,6 +118,7 @@ signals:
|
|||
void loggedOut();
|
||||
|
||||
void trySyncCb();
|
||||
void tryDelayedSyncCb();
|
||||
void tryInitialSyncCb();
|
||||
void leftRoom(const QString &room_id);
|
||||
|
||||
|
@ -146,8 +146,12 @@ private slots:
|
|||
private:
|
||||
static ChatPage *instance_;
|
||||
|
||||
//! Handler callback for initial sync. It doesn't run on the main thread so all
|
||||
//! communication with the GUI should be done through signals.
|
||||
void initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err);
|
||||
void tryInitialSync();
|
||||
void trySync();
|
||||
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
|
||||
|
||||
//! Check if the given room is currently open.
|
||||
bool isRoomActive(const QString &room_id)
|
||||
|
|
|
@ -15,4 +15,7 @@ net();
|
|||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
db();
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
crypto();
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ class MainWindow : public QMainWindow
|
|||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
static MainWindow *instance() { return instance_; };
|
||||
void saveCurrentWindowSize();
|
||||
|
|
|
@ -11,12 +11,15 @@ Q_DECLARE_METATYPE(mtx::responses::Notifications)
|
|||
Q_DECLARE_METATYPE(mtx::responses::Rooms)
|
||||
Q_DECLARE_METATYPE(mtx::responses::Sync)
|
||||
Q_DECLARE_METATYPE(std::string)
|
||||
Q_DECLARE_METATYPE(std::vector<std::string>);
|
||||
Q_DECLARE_METATYPE(std::vector<std::string>)
|
||||
|
||||
namespace http {
|
||||
namespace v2 {
|
||||
mtx::http::Client *
|
||||
client();
|
||||
|
||||
bool
|
||||
is_logged_in();
|
||||
}
|
||||
|
||||
//! Initialize the http module
|
||||
|
|
65
include/Olm.hpp
Normal file
65
include/Olm.hpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mtxclient/crypto/client.hpp>
|
||||
|
||||
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
|
||||
|
||||
namespace olm {
|
||||
|
||||
struct OlmCipherContent
|
||||
{
|
||||
std::string body;
|
||||
uint8_t type;
|
||||
};
|
||||
|
||||
inline void
|
||||
from_json(const nlohmann::json &obj, OlmCipherContent &msg)
|
||||
{
|
||||
msg.body = obj.at("body");
|
||||
msg.type = obj.at("type");
|
||||
}
|
||||
|
||||
struct OlmMessage
|
||||
{
|
||||
std::string sender_key;
|
||||
std::string sender;
|
||||
|
||||
using RecipientKey = std::string;
|
||||
std::map<RecipientKey, OlmCipherContent> ciphertext;
|
||||
};
|
||||
|
||||
inline void
|
||||
from_json(const nlohmann::json &obj, OlmMessage &msg)
|
||||
{
|
||||
if (obj.at("type") != "m.room.encrypted")
|
||||
throw std::invalid_argument("invalid type for olm message");
|
||||
|
||||
if (obj.at("content").at("algorithm") != OLM_ALGO)
|
||||
throw std::invalid_argument("invalid algorithm for olm message");
|
||||
|
||||
msg.sender = obj.at("sender");
|
||||
msg.sender_key = obj.at("content").at("sender_key");
|
||||
msg.ciphertext =
|
||||
obj.at("content").at("ciphertext").get<std::map<std::string, OlmCipherContent>>();
|
||||
}
|
||||
|
||||
mtx::crypto::OlmClient *
|
||||
client();
|
||||
|
||||
void
|
||||
handle_to_device_messages(const std::vector<nlohmann::json> &msgs);
|
||||
|
||||
void
|
||||
handle_olm_message(const OlmMessage &msg);
|
||||
|
||||
void
|
||||
handle_olm_normal_message(const std::string &sender,
|
||||
const std::string &sender_key,
|
||||
const OlmCipherContent &content);
|
||||
|
||||
void
|
||||
handle_pre_key_olm_message(const std::string &sender,
|
||||
const std::string &sender_key,
|
||||
const OlmCipherContent &content);
|
||||
} // namespace olm
|
|
@ -149,6 +149,9 @@ private:
|
|||
|
||||
QWidget *relativeWidget(TimelineItem *item, int dt) const;
|
||||
|
||||
TimelineEvent parseEncryptedEvent(
|
||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
|
||||
|
||||
//! Callback for all message sending.
|
||||
void sendRoomMessageHandler(const std::string &txn_id,
|
||||
const mtx::responses::EventId &res,
|
||||
|
|
250
src/Cache.cc
250
src/Cache.cc
|
@ -31,25 +31,42 @@
|
|||
|
||||
//! Should be changed when a breaking change occurs in the cache format.
|
||||
//! This will reset client's data.
|
||||
static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.05.11");
|
||||
static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.06.10");
|
||||
static const std::string SECRET("secret");
|
||||
|
||||
static const lmdb::val NEXT_BATCH_KEY("next_batch");
|
||||
static const lmdb::val OLM_ACCOUNT_KEY("olm_account");
|
||||
static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
|
||||
|
||||
//! Cache databases and their format.
|
||||
//!
|
||||
//! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc).
|
||||
//! Format: room_id -> RoomInfo
|
||||
static constexpr const char *ROOMS_DB = "rooms";
|
||||
static constexpr const char *INVITES_DB = "invites";
|
||||
constexpr auto ROOMS_DB("rooms");
|
||||
constexpr auto INVITES_DB("invites");
|
||||
//! Keeps already downloaded media for reuse.
|
||||
//! Format: matrix_url -> binary data.
|
||||
static constexpr const char *MEDIA_DB = "media";
|
||||
constexpr auto MEDIA_DB("media");
|
||||
//! Information that must be kept between sync requests.
|
||||
static constexpr const char *SYNC_STATE_DB = "sync_state";
|
||||
constexpr auto SYNC_STATE_DB("sync_state");
|
||||
//! Read receipts per room/event.
|
||||
static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
|
||||
static constexpr const char *NOTIFICATIONS_DB = "sent_notifications";
|
||||
constexpr auto READ_RECEIPTS_DB("read_receipts");
|
||||
constexpr auto NOTIFICATIONS_DB("sent_notifications");
|
||||
|
||||
//! Encryption related databases.
|
||||
|
||||
//! user_id -> list of devices
|
||||
constexpr auto DEVICES_DB("devices");
|
||||
//! device_id -> device keys
|
||||
constexpr auto DEVICE_KEYS_DB("device_keys");
|
||||
//! room_ids that have encryption enabled.
|
||||
// constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms");
|
||||
|
||||
//! MegolmSessionIndex -> pickled OlmInboundGroupSession
|
||||
constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
|
||||
//! MegolmSessionIndex -> pickled OlmOutboundGroupSession
|
||||
constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
||||
constexpr auto OUTBOUND_OLM_SESSIONS_DB("outbound_olm_sessions");
|
||||
|
||||
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
||||
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
||||
|
@ -79,7 +96,7 @@ client()
|
|||
{
|
||||
return instance_.get();
|
||||
}
|
||||
}
|
||||
} // namespace cache
|
||||
|
||||
Cache::Cache(const QString &userId, QObject *parent)
|
||||
: QObject{parent}
|
||||
|
@ -90,6 +107,11 @@ Cache::Cache(const QString &userId, QObject *parent)
|
|||
, mediaDb_{0}
|
||||
, readReceiptsDb_{0}
|
||||
, notificationsDb_{0}
|
||||
, devicesDb_{0}
|
||||
, deviceKeysDb_{0}
|
||||
, inboundMegolmSessionDb_{0}
|
||||
, outboundMegolmSessionDb_{0}
|
||||
, outboundOlmSessionDb_{0}
|
||||
, localUserId_{userId}
|
||||
{}
|
||||
|
||||
|
@ -149,9 +171,221 @@ Cache::setup()
|
|||
mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
|
||||
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
|
||||
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
|
||||
|
||||
// Device management
|
||||
devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE);
|
||||
deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE);
|
||||
|
||||
// Session management
|
||||
inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||
outboundOlmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_OLM_SESSIONS_DB, MDB_CREATE);
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
//
|
||||
// Device Management
|
||||
//
|
||||
|
||||
//
|
||||
// Session Management
|
||||
//
|
||||
|
||||
void
|
||||
Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto key = index.to_hash();
|
||||
const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET);
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
lmdb::dbi_put(txn, inboundMegolmSessionDb_, lmdb::val(key), lmdb::val(pickled));
|
||||
txn.commit();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
||||
session_storage.group_inbound_sessions[key] = std::move(session);
|
||||
}
|
||||
}
|
||||
|
||||
OlmInboundGroupSession *
|
||||
Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
||||
return session_storage.group_inbound_sessions[index.to_hash()].get();
|
||||
}
|
||||
|
||||
bool
|
||||
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
||||
return session_storage.group_inbound_sessions.find(index.to_hash()) !=
|
||||
session_storage.group_inbound_sessions.end();
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveOutboundMegolmSession(const MegolmSessionIndex &index,
|
||||
const OutboundGroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr session)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto key = index.to_hash();
|
||||
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
|
||||
|
||||
json j;
|
||||
j["data"] = data;
|
||||
j["session"] = pickled;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(key), lmdb::val(j.dump()));
|
||||
txn.commit();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
|
||||
session_storage.group_outbound_session_data[key] = data;
|
||||
session_storage.group_outbound_sessions[key] = std::move(session);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Cache::outboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
|
||||
{
|
||||
const auto key = index.to_hash();
|
||||
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
|
||||
return (session_storage.group_outbound_sessions.find(key) !=
|
||||
session_storage.group_outbound_sessions.end()) &&
|
||||
(session_storage.group_outbound_session_data.find(key) !=
|
||||
session_storage.group_outbound_session_data.end());
|
||||
}
|
||||
|
||||
OutboundGroupSessionDataRef
|
||||
Cache::getOutboundMegolmSession(const MegolmSessionIndex &index)
|
||||
{
|
||||
const auto key = index.to_hash();
|
||||
std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
|
||||
return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[key].get(),
|
||||
session_storage.group_outbound_session_data[key]};
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveOutboundOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto pickled = pickle<SessionObject>(session.get(), SECRET);
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(curve25519), lmdb::val(pickled));
|
||||
txn.commit();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
|
||||
session_storage.outbound_sessions[curve25519] = std::move(session);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Cache::outboundOlmSessionsExists(const std::string &curve25519) noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
|
||||
return session_storage.outbound_sessions.find(curve25519) !=
|
||||
session_storage.outbound_sessions.end();
|
||||
}
|
||||
|
||||
OlmSession *
|
||||
Cache::getOutboundOlmSession(const std::string &curve25519)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(session_storage.outbound_mtx);
|
||||
return session_storage.outbound_sessions.at(curve25519).get();
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveOlmAccount(const std::string &data)
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
lmdb::dbi_put(txn, syncStateDb_, OLM_ACCOUNT_KEY, lmdb::val(data));
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
void
|
||||
Cache::restoreSessions()
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
std::string key, value;
|
||||
|
||||
//
|
||||
// Inbound Megolm Sessions
|
||||
//
|
||||
{
|
||||
auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
|
||||
while (cursor.get(key, value, MDB_NEXT)) {
|
||||
auto session = unpickle<InboundSessionObject>(value, SECRET);
|
||||
session_storage.group_inbound_sessions[key] = std::move(session);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
//
|
||||
// Outbound Megolm Sessions
|
||||
//
|
||||
{
|
||||
auto cursor = lmdb::cursor::open(txn, outboundMegolmSessionDb_);
|
||||
while (cursor.get(key, value, MDB_NEXT)) {
|
||||
json obj;
|
||||
|
||||
try {
|
||||
obj = json::parse(value);
|
||||
|
||||
session_storage.group_outbound_session_data[key] =
|
||||
obj.at("data").get<OutboundGroupSessionData>();
|
||||
|
||||
auto session =
|
||||
unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
|
||||
session_storage.group_outbound_sessions[key] = std::move(session);
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
log::db()->warn("failed to parse outbound megolm session data: {}",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
//
|
||||
// Outbound Olm Sessions
|
||||
//
|
||||
{
|
||||
auto cursor = lmdb::cursor::open(txn, outboundOlmSessionDb_);
|
||||
while (cursor.get(key, value, MDB_NEXT)) {
|
||||
auto session = unpickle<SessionObject>(value, SECRET);
|
||||
session_storage.outbound_sessions[key] = std::move(session);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
|
||||
log::db()->info("sessions restored");
|
||||
}
|
||||
|
||||
std::string
|
||||
Cache::restoreOlmAccount()
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
lmdb::val pickled;
|
||||
lmdb::dbi_get(txn, syncStateDb_, OLM_ACCOUNT_KEY, pickled);
|
||||
txn.commit();
|
||||
|
||||
return std::string(pickled.data(), pickled.size());
|
||||
}
|
||||
|
||||
//
|
||||
// Media Management
|
||||
//
|
||||
|
||||
void
|
||||
Cache::saveImage(const std::string &url, const std::string &img_data)
|
||||
{
|
||||
|
|
199
src/ChatPage.cc
199
src/ChatPage.cc
|
@ -25,6 +25,7 @@
|
|||
#include "Logging.hpp"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Olm.hpp"
|
||||
#include "OverlayModal.h"
|
||||
#include "QuickSwitcher.h"
|
||||
#include "RoomList.h"
|
||||
|
@ -43,8 +44,12 @@
|
|||
#include "dialogs/ReadReceipts.h"
|
||||
#include "timeline/TimelineViewManager.h"
|
||||
|
||||
// TODO: Needs to be updated with an actual secret.
|
||||
static const std::string STORAGE_SECRET_KEY("secret");
|
||||
|
||||
ChatPage *ChatPage::instance_ = nullptr;
|
||||
constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
|
||||
constexpr size_t MAX_ONETIME_KEYS = 50;
|
||||
|
||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
|
@ -612,6 +617,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
|
||||
connect(this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync);
|
||||
connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync);
|
||||
connect(this, &ChatPage::tryDelayedSyncCb, this, [this]() {
|
||||
QTimer::singleShot(5000, this, &ChatPage::trySync);
|
||||
});
|
||||
|
||||
connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
|
||||
|
||||
|
@ -728,6 +736,11 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
|||
});
|
||||
// TODO http::client()->getOwnCommunities();
|
||||
|
||||
// The Olm client needs the user_id & device_id that will be included
|
||||
// in the generated payloads & keys.
|
||||
olm::client()->set_user_id(http::v2::client()->user_id().to_string());
|
||||
olm::client()->set_device_id(http::v2::client()->device_id());
|
||||
|
||||
cache::init(userid);
|
||||
|
||||
try {
|
||||
|
@ -741,6 +754,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
|||
|
||||
if (cache::client()->isInitialized()) {
|
||||
loadStateFromCache();
|
||||
// TODO: Bootstrap olm client with saved data.
|
||||
return;
|
||||
}
|
||||
} catch (const lmdb::error &e) {
|
||||
|
@ -749,6 +763,22 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
|||
log::net()->info("falling back to initial sync");
|
||||
}
|
||||
|
||||
try {
|
||||
// It's the first time syncing with this device
|
||||
// There isn't a saved olm account to restore.
|
||||
log::crypto()->info("creating new olm account");
|
||||
olm::client()->create_new_account();
|
||||
cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
|
||||
} catch (const lmdb::error &e) {
|
||||
log::crypto()->critical("failed to save olm account {}", e.what());
|
||||
emit dropToLoginPageCb(QString::fromStdString(e.what()));
|
||||
return;
|
||||
} catch (const mtx::crypto::olm_exception &e) {
|
||||
log::crypto()->critical("failed to create new olm account {}", e.what());
|
||||
emit dropToLoginPageCb(QString::fromStdString(e.what()));
|
||||
return;
|
||||
}
|
||||
|
||||
tryInitialSync();
|
||||
}
|
||||
|
||||
|
@ -826,16 +856,29 @@ ChatPage::loadStateFromCache()
|
|||
|
||||
QtConcurrent::run([this]() {
|
||||
try {
|
||||
cache::client()->restoreSessions();
|
||||
olm::client()->load(cache::client()->restoreOlmAccount(),
|
||||
STORAGE_SECRET_KEY);
|
||||
|
||||
cache::client()->populateMembers();
|
||||
|
||||
emit initializeEmptyViews(cache::client()->joinedRooms());
|
||||
emit initializeRoomList(cache::client()->roomInfo());
|
||||
} catch (const mtx::crypto::olm_exception &e) {
|
||||
log::crypto()->critical("failed to restore olm account: {}", e.what());
|
||||
emit dropToLoginPageCb(
|
||||
tr("Failed to restore OLM account. Please login again."));
|
||||
return;
|
||||
} catch (const lmdb::error &e) {
|
||||
std::cout << "load cache error:" << e.what() << '\n';
|
||||
// TODO Clear cache and restart.
|
||||
log::db()->critical("failed to restore cache: {}", e.what());
|
||||
emit dropToLoginPageCb(
|
||||
tr("Failed to restore save data. Please login again."));
|
||||
return;
|
||||
}
|
||||
|
||||
log::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
|
||||
log::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
|
||||
|
||||
// Start receiving events.
|
||||
emit trySyncCb();
|
||||
|
||||
|
@ -1008,49 +1051,40 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
|
|||
void
|
||||
ChatPage::tryInitialSync()
|
||||
{
|
||||
mtx::http::SyncOpts opts;
|
||||
opts.timeout = 0;
|
||||
log::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
|
||||
log::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519);
|
||||
|
||||
log::net()->info("trying initial sync");
|
||||
// Upload one time keys for the device.
|
||||
log::crypto()->info("generating one time keys");
|
||||
olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS);
|
||||
|
||||
http::v2::client()->sync(
|
||||
opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
|
||||
http::v2::client()->upload_keys(
|
||||
olm::client()->create_upload_keys_request(),
|
||||
[this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
const auto error = QString::fromStdString(err->matrix_error.error);
|
||||
const auto msg = tr("Please try to login again: %1").arg(error);
|
||||
const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
|
||||
const int status_code = static_cast<int>(err->status_code);
|
||||
|
||||
log::net()->error("sync error: {} {}", status_code, err_code);
|
||||
|
||||
switch (status_code) {
|
||||
case 502:
|
||||
case 504:
|
||||
case 524: {
|
||||
emit tryInitialSyncCb();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
emit dropToLoginPageCb(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::net()->info("initial sync completed");
|
||||
|
||||
try {
|
||||
cache::client()->saveState(res);
|
||||
emit initializeViews(std::move(res.rooms));
|
||||
emit initializeRoomList(cache::client()->roomInfo());
|
||||
} catch (const lmdb::error &e) {
|
||||
log::db()->error("{}", e.what());
|
||||
log::crypto()->critical("failed to upload one time keys: {} {}",
|
||||
err->matrix_error.error,
|
||||
status_code);
|
||||
// TODO We should have a timeout instead of keeping hammering the server.
|
||||
emit tryInitialSyncCb();
|
||||
return;
|
||||
}
|
||||
|
||||
emit trySyncCb();
|
||||
emit contentLoaded();
|
||||
olm::client()->mark_keys_as_published();
|
||||
for (const auto &entry : res.one_time_key_counts)
|
||||
log::net()->info(
|
||||
"uploaded {} {} one-time keys", entry.second, entry.first);
|
||||
|
||||
log::net()->info("trying initial sync");
|
||||
|
||||
mtx::http::SyncOpts opts;
|
||||
opts.timeout = 0;
|
||||
http::v2::client()->sync(opts,
|
||||
std::bind(&ChatPage::initialSyncHandler,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1079,24 +1113,31 @@ ChatPage::trySync()
|
|||
|
||||
log::net()->error("sync error: {} {}", status_code, err_code);
|
||||
|
||||
if (status_code <= 0 || status_code >= 600) {
|
||||
if (!http::v2::is_logged_in())
|
||||
return;
|
||||
|
||||
emit dropToLoginPageCb(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status_code) {
|
||||
case 502:
|
||||
case 504:
|
||||
case 524: {
|
||||
emit trySync();
|
||||
emit trySyncCb();
|
||||
return;
|
||||
}
|
||||
case 401:
|
||||
case 403: {
|
||||
// We are logged out.
|
||||
if (http::v2::client()->access_token().empty())
|
||||
if (!http::v2::is_logged_in())
|
||||
return;
|
||||
|
||||
emit dropToLoginPageCb(msg);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
emit trySync();
|
||||
emit tryDelayedSyncCb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1104,9 +1145,14 @@ ChatPage::trySync()
|
|||
|
||||
log::net()->debug("sync completed: {}", res.next_batch);
|
||||
|
||||
// Ensure that we have enough one-time keys available.
|
||||
ensureOneTimeKeyCount(res.device_one_time_keys_count);
|
||||
|
||||
// TODO: fine grained error handling
|
||||
try {
|
||||
cache::client()->saveState(res);
|
||||
olm::handle_to_device_messages(res.to_device);
|
||||
|
||||
emit syncUI(res.rooms);
|
||||
|
||||
auto updates = cache::client()->roomUpdates(res);
|
||||
|
@ -1194,3 +1240,74 @@ ChatPage::sendTypingNotifications()
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
|
||||
{
|
||||
if (err) {
|
||||
const auto error = QString::fromStdString(err->matrix_error.error);
|
||||
const auto msg = tr("Please try to login again: %1").arg(error);
|
||||
const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
|
||||
const int status_code = static_cast<int>(err->status_code);
|
||||
|
||||
log::net()->error("sync error: {} {}", status_code, err_code);
|
||||
|
||||
switch (status_code) {
|
||||
case 502:
|
||||
case 504:
|
||||
case 524: {
|
||||
emit tryInitialSyncCb();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
emit dropToLoginPageCb(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::net()->info("initial sync completed");
|
||||
|
||||
try {
|
||||
cache::client()->saveState(res);
|
||||
|
||||
olm::handle_to_device_messages(res.to_device);
|
||||
|
||||
emit initializeViews(std::move(res.rooms));
|
||||
emit initializeRoomList(cache::client()->roomInfo());
|
||||
} catch (const lmdb::error &e) {
|
||||
log::db()->error("{}", e.what());
|
||||
emit tryInitialSyncCb();
|
||||
return;
|
||||
}
|
||||
|
||||
emit trySyncCb();
|
||||
emit contentLoaded();
|
||||
}
|
||||
|
||||
void
|
||||
ChatPage::ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts)
|
||||
{
|
||||
for (const auto &entry : counts) {
|
||||
if (entry.second < MAX_ONETIME_KEYS) {
|
||||
const int nkeys = MAX_ONETIME_KEYS - entry.second;
|
||||
|
||||
log::crypto()->info("uploading {} {} keys", nkeys, entry.first);
|
||||
olm::client()->generate_one_time_keys(nkeys);
|
||||
|
||||
http::v2::client()->upload_keys(
|
||||
olm::client()->create_upload_keys_request(),
|
||||
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::crypto()->warn(
|
||||
"failed to update one-time keys: {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
olm::client()->mark_keys_as_published();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,9 @@ CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUr
|
|||
return;
|
||||
}
|
||||
|
||||
if (avatarUrl.isEmpty())
|
||||
return;
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = avatarUrl.toStdString();
|
||||
http::v2::client()->get_thumbnail(
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
#include <spdlog/sinks/file_sinks.h>
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<spdlog::logger> db_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> net_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> main_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> db_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> net_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> crypto_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> main_logger = nullptr;
|
||||
|
||||
constexpr auto MAX_FILE_SIZE = 1024 * 1024 * 6;
|
||||
constexpr auto MAX_LOG_FILES = 3;
|
||||
|
@ -28,6 +29,8 @@ init(const std::string &file_path)
|
|||
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
|
||||
main_logger = std::make_shared<spdlog::logger>("main", std::begin(sinks), std::end(sinks));
|
||||
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
|
||||
crypto_logger =
|
||||
std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
|
@ -47,4 +50,10 @@ db()
|
|||
{
|
||||
return db_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
crypto()
|
||||
{
|
||||
return crypto_logger;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,15 +46,6 @@
|
|||
|
||||
MainWindow *MainWindow::instance_ = nullptr;
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (http::v2::client() != nullptr) {
|
||||
http::v2::client()->shutdown();
|
||||
// TODO: find out why waiting for the threads to join is slow.
|
||||
http::v2::client()->close();
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, progressModal_{nullptr}
|
||||
|
@ -154,9 +145,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
QString token = settings.value("auth/access_token").toString();
|
||||
QString home_server = settings.value("auth/home_server").toString();
|
||||
QString user_id = settings.value("auth/user_id").toString();
|
||||
QString device_id = settings.value("auth/device_id").toString();
|
||||
|
||||
http::v2::client()->set_access_token(token.toStdString());
|
||||
http::v2::client()->set_server(home_server.toStdString());
|
||||
http::v2::client()->set_device_id(device_id.toStdString());
|
||||
|
||||
try {
|
||||
using namespace mtx::identifiers;
|
||||
|
@ -228,6 +221,7 @@ void
|
|||
MainWindow::showChatPage()
|
||||
{
|
||||
auto userid = QString::fromStdString(http::v2::client()->user_id().to_string());
|
||||
auto device_id = QString::fromStdString(http::v2::client()->device_id());
|
||||
auto homeserver = QString::fromStdString(http::v2::client()->server() + ":" +
|
||||
std::to_string(http::v2::client()->port()));
|
||||
auto token = QString::fromStdString(http::v2::client()->access_token());
|
||||
|
@ -236,6 +230,7 @@ MainWindow::showChatPage()
|
|||
settings.setValue("auth/access_token", token);
|
||||
settings.setValue("auth/home_server", homeserver);
|
||||
settings.setValue("auth/user_id", userid);
|
||||
settings.setValue("auth/device_id", device_id);
|
||||
|
||||
showOverlayProgressBar();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <memory>
|
||||
|
||||
namespace {
|
||||
auto v2_client_ = std::make_shared<mtx::http::Client>("matrix.org");
|
||||
auto v2_client_ = std::make_shared<mtx::http::Client>();
|
||||
}
|
||||
|
||||
namespace http {
|
||||
|
@ -15,6 +15,12 @@ client()
|
|||
return v2_client_.get();
|
||||
}
|
||||
|
||||
bool
|
||||
is_logged_in()
|
||||
{
|
||||
return !v2_client_->access_token().empty();
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
|
||||
void
|
||||
|
|
139
src/Olm.cpp
Normal file
139
src/Olm.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#include "Olm.hpp"
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.hpp"
|
||||
|
||||
using namespace mtx::crypto;
|
||||
|
||||
namespace {
|
||||
auto client_ = std::make_unique<mtx::crypto::OlmClient>();
|
||||
}
|
||||
|
||||
namespace olm {
|
||||
|
||||
mtx::crypto::OlmClient *
|
||||
client()
|
||||
{
|
||||
return client_.get();
|
||||
}
|
||||
|
||||
void
|
||||
handle_to_device_messages(const std::vector<nlohmann::json> &msgs)
|
||||
{
|
||||
if (msgs.empty())
|
||||
return;
|
||||
|
||||
log::crypto()->info("received {} to_device messages", msgs.size());
|
||||
|
||||
for (const auto &msg : msgs) {
|
||||
try {
|
||||
OlmMessage olm_msg = msg;
|
||||
handle_olm_message(std::move(olm_msg));
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
log::crypto()->warn(
|
||||
"parsing error for olm message: {} {}", e.what(), msg.dump(2));
|
||||
} catch (const std::invalid_argument &e) {
|
||||
log::crypto()->warn(
|
||||
"validation error for olm message: {} {}", e.what(), msg.dump(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_olm_message(const OlmMessage &msg)
|
||||
{
|
||||
log::crypto()->info("sender : {}", msg.sender);
|
||||
log::crypto()->info("sender_key: {}", msg.sender_key);
|
||||
|
||||
const auto my_key = olm::client()->identity_keys().curve25519;
|
||||
|
||||
for (const auto &cipher : msg.ciphertext) {
|
||||
// We skip messages not meant for the current device.
|
||||
if (cipher.first != my_key)
|
||||
continue;
|
||||
|
||||
const auto type = cipher.second.type;
|
||||
log::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE");
|
||||
|
||||
if (type == OLM_MESSAGE_TYPE_PRE_KEY)
|
||||
handle_pre_key_olm_message(msg.sender, msg.sender_key, cipher.second);
|
||||
else
|
||||
handle_olm_normal_message(msg.sender, msg.sender_key, cipher.second);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_pre_key_olm_message(const std::string &sender,
|
||||
const std::string &sender_key,
|
||||
const OlmCipherContent &content)
|
||||
{
|
||||
log::crypto()->info("opening olm session with {}", sender);
|
||||
|
||||
OlmSessionPtr inbound_session = nullptr;
|
||||
try {
|
||||
inbound_session = olm::client()->create_inbound_session(content.body);
|
||||
} catch (const olm_exception &e) {
|
||||
log::crypto()->critical(
|
||||
"failed to create inbound session with {}: {}", sender, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matches_inbound_session_from(inbound_session.get(), sender_key, content.body)) {
|
||||
log::crypto()->warn("inbound olm session doesn't match sender's key ({})", sender);
|
||||
return;
|
||||
}
|
||||
|
||||
mtx::crypto::BinaryBuf output;
|
||||
try {
|
||||
output = olm::client()->decrypt_message(
|
||||
inbound_session.get(), OLM_MESSAGE_TYPE_PRE_KEY, content.body);
|
||||
} catch (const olm_exception &e) {
|
||||
log::crypto()->critical(
|
||||
"failed to decrypt olm message {}: {}", content.body, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
auto plaintext = json::parse(std::string((char *)output.data(), output.size()));
|
||||
log::crypto()->info("decrypted message: \n {}", plaintext.dump(2));
|
||||
|
||||
std::string room_id, session_id, session_key;
|
||||
try {
|
||||
room_id = plaintext.at("content").at("room_id");
|
||||
session_id = plaintext.at("content").at("session_id");
|
||||
session_key = plaintext.at("content").at("session_key");
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
log::crypto()->critical(
|
||||
"failed to parse plaintext olm message: {} {}", e.what(), plaintext.dump(2));
|
||||
return;
|
||||
}
|
||||
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id;
|
||||
index.session_id = session_id;
|
||||
index.sender_key = sender_key;
|
||||
|
||||
if (!cache::client()->inboundMegolmSessionExists(index)) {
|
||||
auto megolm_session = olm::client()->init_inbound_group_session(session_key);
|
||||
|
||||
try {
|
||||
cache::client()->saveInboundMegolmSession(index, std::move(megolm_session));
|
||||
} catch (const lmdb::error &e) {
|
||||
log::crypto()->critical("failed to save inbound megolm session: {}",
|
||||
e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
log::crypto()->info("established inbound megolm session ({}, {})", room_id, sender);
|
||||
} else {
|
||||
log::crypto()->warn(
|
||||
"inbound megolm session already exists ({}, {})", room_id, sender);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_olm_normal_message(const std::string &, const std::string &, const OlmCipherContent &)
|
||||
{
|
||||
log::crypto()->warn("olm(1) not implemeted yet");
|
||||
}
|
||||
|
||||
} // namespace olm
|
|
@ -96,7 +96,7 @@ RoomList::updateAvatar(const QString &room_id, const QString &url)
|
|||
opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn(
|
||||
"failed to download thumbnail: {}, {} - {}",
|
||||
"failed to download room avatar: {} {} {}",
|
||||
opts.mxc_url,
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error);
|
||||
|
|
|
@ -149,7 +149,13 @@ main(int argc, char *argv[])
|
|||
!settings.value("user/window/tray", true).toBool())
|
||||
w.show();
|
||||
|
||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize);
|
||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
|
||||
w.saveCurrentWindowSize();
|
||||
if (http::v2::client() != nullptr) {
|
||||
http::v2::client()->shutdown();
|
||||
http::v2::client()->close();
|
||||
}
|
||||
});
|
||||
|
||||
log::main()->info("starting nheko {}", nheko::version);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "Config.h"
|
||||
#include "FloatingButton.h"
|
||||
#include "Logging.hpp"
|
||||
#include "Olm.hpp"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -235,19 +236,19 @@ TimelineItem *
|
|||
TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &event,
|
||||
TimelineDirection direction)
|
||||
{
|
||||
namespace msg = mtx::events::msg;
|
||||
using AudioEvent = mtx::events::RoomEvent<msg::Audio>;
|
||||
using EmoteEvent = mtx::events::RoomEvent<msg::Emote>;
|
||||
using FileEvent = mtx::events::RoomEvent<msg::File>;
|
||||
using ImageEvent = mtx::events::RoomEvent<msg::Image>;
|
||||
using NoticeEvent = mtx::events::RoomEvent<msg::Notice>;
|
||||
using TextEvent = mtx::events::RoomEvent<msg::Text>;
|
||||
using VideoEvent = mtx::events::RoomEvent<msg::Video>;
|
||||
using namespace mtx::events;
|
||||
|
||||
if (mpark::holds_alternative<mtx::events::RedactionEvent<msg::Redaction>>(event)) {
|
||||
auto redaction_event =
|
||||
mpark::get<mtx::events::RedactionEvent<msg::Redaction>>(event);
|
||||
const auto event_id = QString::fromStdString(redaction_event.redacts);
|
||||
using AudioEvent = RoomEvent<msg::Audio>;
|
||||
using EmoteEvent = RoomEvent<msg::Emote>;
|
||||
using FileEvent = RoomEvent<msg::File>;
|
||||
using ImageEvent = RoomEvent<msg::Image>;
|
||||
using NoticeEvent = RoomEvent<msg::Notice>;
|
||||
using TextEvent = RoomEvent<msg::Text>;
|
||||
using VideoEvent = RoomEvent<msg::Video>;
|
||||
|
||||
if (mpark::holds_alternative<RedactionEvent<msg::Redaction>>(event)) {
|
||||
auto redaction_event = mpark::get<RedactionEvent<msg::Redaction>>(event);
|
||||
const auto event_id = QString::fromStdString(redaction_event.redacts);
|
||||
|
||||
QTimer::singleShot(0, this, [event_id, this]() {
|
||||
if (eventIds_.contains(event_id))
|
||||
|
@ -255,35 +256,88 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &
|
|||
});
|
||||
|
||||
return nullptr;
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Audio>>(event)) {
|
||||
auto audio = mpark::get<mtx::events::RoomEvent<msg::Audio>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Audio>>(event)) {
|
||||
auto audio = mpark::get<RoomEvent<msg::Audio>>(event);
|
||||
return processMessageEvent<AudioEvent, AudioItem>(audio, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Emote>>(event)) {
|
||||
auto emote = mpark::get<mtx::events::RoomEvent<msg::Emote>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Emote>>(event)) {
|
||||
auto emote = mpark::get<RoomEvent<msg::Emote>>(event);
|
||||
return processMessageEvent<EmoteEvent>(emote, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::File>>(event)) {
|
||||
auto file = mpark::get<mtx::events::RoomEvent<msg::File>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::File>>(event)) {
|
||||
auto file = mpark::get<RoomEvent<msg::File>>(event);
|
||||
return processMessageEvent<FileEvent, FileItem>(file, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Image>>(event)) {
|
||||
auto image = mpark::get<mtx::events::RoomEvent<msg::Image>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Image>>(event)) {
|
||||
auto image = mpark::get<RoomEvent<msg::Image>>(event);
|
||||
return processMessageEvent<ImageEvent, ImageItem>(image, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Notice>>(event)) {
|
||||
auto notice = mpark::get<mtx::events::RoomEvent<msg::Notice>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Notice>>(event)) {
|
||||
auto notice = mpark::get<RoomEvent<msg::Notice>>(event);
|
||||
return processMessageEvent<NoticeEvent>(notice, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Text>>(event)) {
|
||||
auto text = mpark::get<mtx::events::RoomEvent<msg::Text>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Text>>(event)) {
|
||||
auto text = mpark::get<RoomEvent<msg::Text>>(event);
|
||||
return processMessageEvent<TextEvent>(text, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::RoomEvent<msg::Video>>(event)) {
|
||||
auto video = mpark::get<mtx::events::RoomEvent<msg::Video>>(event);
|
||||
} else if (mpark::holds_alternative<RoomEvent<msg::Video>>(event)) {
|
||||
auto video = mpark::get<RoomEvent<msg::Video>>(event);
|
||||
return processMessageEvent<VideoEvent, VideoItem>(video, direction);
|
||||
} else if (mpark::holds_alternative<mtx::events::Sticker>(event)) {
|
||||
return processMessageEvent<mtx::events::Sticker, StickerItem>(
|
||||
mpark::get<mtx::events::Sticker>(event), direction);
|
||||
} else if (mpark::holds_alternative<Sticker>(event)) {
|
||||
return processMessageEvent<Sticker, StickerItem>(mpark::get<Sticker>(event),
|
||||
direction);
|
||||
} else if (mpark::holds_alternative<EncryptedEvent<msg::Encrypted>>(event)) {
|
||||
auto decrypted =
|
||||
parseEncryptedEvent(mpark::get<EncryptedEvent<msg::Encrypted>>(event));
|
||||
return parseMessageEvent(decrypted, direction);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TimelineEvent
|
||||
TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
|
||||
{
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id_.toStdString();
|
||||
index.session_id = e.content.session_id;
|
||||
index.sender_key = e.content.sender_key;
|
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::Text> dummy;
|
||||
dummy.origin_server_ts = e.origin_server_ts;
|
||||
dummy.event_id = e.event_id;
|
||||
dummy.sender = e.sender;
|
||||
dummy.content.body = "-- Encrypted Event (No keys found for decryption) --";
|
||||
|
||||
if (!cache::client()->inboundMegolmSessionExists(index)) {
|
||||
log::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
|
||||
index.room_id,
|
||||
index.session_id,
|
||||
e.sender);
|
||||
// TODO: request megolm session_id & session_key from the sender.
|
||||
return dummy;
|
||||
}
|
||||
|
||||
auto session = cache::client()->getInboundMegolmSession(index);
|
||||
auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext);
|
||||
|
||||
const auto msg_str = std::string((char *)res.data.data(), res.data.size());
|
||||
|
||||
// Add missing fields for the event.
|
||||
json body = json::parse(msg_str);
|
||||
body["event_id"] = e.event_id;
|
||||
body["sender"] = e.sender;
|
||||
body["origin_server_ts"] = e.origin_server_ts;
|
||||
|
||||
log::crypto()->info("decrypted data: \n {}", body.dump(2));
|
||||
|
||||
json event_array = json::array();
|
||||
event_array.push_back(body);
|
||||
|
||||
std::vector<TimelineEvent> events;
|
||||
mtx::responses::utils::parse_timeline_events(event_array, events);
|
||||
|
||||
if (events.size() == 1)
|
||||
return events.at(0);
|
||||
|
||||
dummy.content.body = "-- Encrypted Event (Unknown event type) --";
|
||||
return dummy;
|
||||
}
|
||||
|
||||
void
|
||||
TimelineView::renderBottomEvents(const std::vector<TimelineEvent> &events)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue